{"version":3,"file":"pivotConfigModel-BQP3dWFf.cjs","names":["PIVOT_ZONES","EMPTY_PIVOT_CONFIG","rows","columns","measures","availableFields","fields","config","used","Set","filter","field","has","key","placed","list","index","without","existing","at","Math","min","max","length","slice","assignField","zone","Number","MAX_SAFE_INTEGER","splice","agg","other","removeField","_","i","moveField","delta","target","moved","keys","undefined","setMeasureAgg","map","measure","isPivotReady","measureLabel","label","find","candidate","name"],"sources":["../src/pivot/pivotConfigModel.ts"],"sourcesContent":["/**\n * Editing a pivot configuration: which field sits on which axis, in what\n * order, and what each measure computes.\n *\n * This is the part of the configuration UI that is not a widget. Every kit\n * draws the panel differently, but \"move Team from Available to Rows, above\n * Region\" is one answer everywhere — so it is decided here, once, and the\n * adapters are left with buttons.\n *\n * The operations are deliberately total: moving a field that is already on\n * an axis takes it off the old one rather than duplicating it, moving to an\n * index past the end appends, and moving a field that does not exist changes\n * nothing. A configuration panel whose buttons can produce an invalid pivot\n * is a panel that will produce one.\n *\n * Measures are the exception to \"a field lives on one axis\": the same column\n * can be summed and counted in the same pivot, so measures are keyed by\n * their position rather than by their column.\n */\nimport type { AggregateName } from \"../aggregate/aggregate\";\nimport type { PivotConfig, PivotMeasure } from \"./pivotModel\";\n\n/** Where a field can sit. */\nexport type PivotZone = \"rows\" | \"columns\" | \"measures\";\n\n/** The zones a field can be moved between, in panel order. */\nexport const PIVOT_ZONES: readonly PivotZone[] = [\n  \"rows\",\n  \"columns\",\n  \"measures\",\n];\n\n/** A field the user can put on an axis. */\nexport interface PivotField {\n  /** The column key. */\n  key: string;\n  /** What to call it in the panel. */\n  label: string;\n}\n\n/** An empty configuration — nothing on any axis. */\nexport const EMPTY_PIVOT_CONFIG: PivotConfig = {\n  rows: [],\n  columns: [],\n  measures: [],\n};\n\n/** The fields not yet used on either axis. */\nexport function availableFields(\n  fields: readonly PivotField[],\n  config: PivotConfig\n): PivotField[] {\n  const used = new Set([...config.rows, ...config.columns]);\n  return fields.filter((field) => !used.has(field.key));\n}\n\n/** Move a key within a list, or insert it, returning a new list. */\nfunction placed(list: readonly string[], key: string, index: number): string[] {\n  const without = list.filter((existing) => existing !== key);\n  const at = Math.min(Math.max(index, 0), without.length);\n  return [...without.slice(0, at), key, ...without.slice(at)];\n}\n\n/**\n * Put a field on an axis.\n *\n * @param config - The configuration to change.\n * @param key - The column key to place.\n * @param zone - Where it should go.\n * @param index - Position within that zone. Past the end appends.\n * @returns A new configuration.\n */\nexport function assignField(\n  config: PivotConfig,\n  key: string,\n  zone: PivotZone,\n  index = Number.MAX_SAFE_INTEGER\n): PivotConfig {\n  if (zone === \"measures\") {\n    // A measure is not an axis slot: the same column can be summed and\n    // counted at once, so this adds rather than moves.\n    const measures = [...config.measures];\n    const at = Math.min(Math.max(index, 0), measures.length);\n    measures.splice(at, 0, { key, agg: \"sum\" });\n    return { ...config, measures };\n  }\n  // A dimension lives on one axis only — placing it on the other takes it\n  // off the first, rather than pivoting the same field twice.\n  const other = zone === \"rows\" ? \"columns\" : \"rows\";\n  return {\n    ...config,\n    [zone]: placed(config[zone], key, index),\n    [other]: config[other].filter((existing) => existing !== key),\n  };\n}\n\n/**\n * Take a field off an axis.\n *\n * @param config - The configuration to change.\n * @param zone - Which axis to remove from.\n * @param index - The position to remove.\n * @returns A new configuration.\n */\nexport function removeField(\n  config: PivotConfig,\n  zone: PivotZone,\n  index: number\n): PivotConfig {\n  if (zone === \"measures\") {\n    return {\n      ...config,\n      measures: config.measures.filter((_, i) => i !== index),\n    };\n  }\n  return { ...config, [zone]: config[zone].filter((_, i) => i !== index) };\n}\n\n/**\n * Move a field one step within its zone.\n *\n * The keyboard counterpart of dragging. A step past either end is a no-op\n * rather than a wrap: wrapping makes the last press of a held key undo the\n * whole journey.\n *\n * @param config - The configuration to change.\n * @param zone - Which axis the field is on.\n * @param index - Its current position.\n * @param delta - `-1` to move it out one level, `1` to move it in.\n * @returns A new configuration.\n */\nexport function moveField(\n  config: PivotConfig,\n  zone: PivotZone,\n  index: number,\n  delta: -1 | 1\n): PivotConfig {\n  const target = index + delta;\n  const list: readonly unknown[] =\n    zone === \"measures\" ? config.measures : config[zone];\n  if (index < 0 || index >= list.length) return config;\n  if (target < 0 || target >= list.length) return config;\n  if (zone === \"measures\") {\n    const measures = [...config.measures];\n    const [moved] = measures.splice(index, 1);\n    if (moved) measures.splice(target, 0, moved);\n    return { ...config, measures };\n  }\n  const keys = [...config[zone]];\n  const [moved] = keys.splice(index, 1);\n  if (moved !== undefined) keys.splice(target, 0, moved);\n  return { ...config, [zone]: keys };\n}\n\n/**\n * Change what a measure computes.\n *\n * @param config - The configuration to change.\n * @param index - Which measure.\n * @param agg - The new aggregation.\n * @returns A new configuration.\n */\nexport function setMeasureAgg(\n  config: PivotConfig,\n  index: number,\n  agg: AggregateName\n): PivotConfig {\n  return {\n    ...config,\n    measures: config.measures.map((measure, i) =>\n      i === index ? { ...measure, agg } : measure\n    ),\n  };\n}\n\n/**\n * Whether a configuration can actually be rendered.\n *\n * A pivot with no measure has nothing to put in its cells, which is a\n * half-built configuration rather than an error — the panel shows it and the\n * table waits.\n */\nexport function isPivotReady(config: PivotConfig): boolean {\n  return config.measures.length > 0;\n}\n\n/** The display label for one measure, for the panel and the column header. */\nexport function measureLabel(\n  measure: PivotMeasure,\n  fields: readonly PivotField[]\n): string {\n  if (measure.label !== undefined) return measure.label;\n  const field = fields.find((candidate) => candidate.key === measure.key);\n  const name = field?.label ?? measure.key;\n  return typeof measure.agg === \"string\" ? `${measure.agg} ${name}` : name;\n}\n"],"mappings":"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"}