{"version":3,"file":"table-BifavDVW.mjs","names":[],"sources":["../src/worksheet/conditional-formatting.ts","../src/worksheet/data-validations.ts","../src/worksheet/table.ts"],"sourcesContent":["// Conditional formatting.\n//\n// Stage-1 covers the **value-based** rule kinds (cellIs / expression / top10 /\n// aboveAverage / containsText family / containsBlanks family / duplicateValues\n// / uniqueValues / timePeriod). The visual rule kinds — colorScale / dataBar /\n// iconSet — round-trip as opaque inner XML (`innerXml` field) so the data\n// survives a save / load cycle without our needing to model cfvo / colors /\n// iconSets fully.\n\nimport { escapeXmlAttr } from '../utils/escape';\nimport { OpenXmlSchemaError } from '../utils/exceptions';\nimport { type MultiCellRange, parseMultiCellRange } from './cell-range';\n\nexport type ConditionalFormattingRuleType =\n  | 'expression'\n  | 'cellIs'\n  | 'colorScale'\n  | 'dataBar'\n  | 'iconSet'\n  | 'top10'\n  | 'aboveAverage'\n  | 'uniqueValues'\n  | 'duplicateValues'\n  | 'containsText'\n  | 'notContainsText'\n  | 'beginsWith'\n  | 'endsWith'\n  | 'containsBlanks'\n  | 'notContainsBlanks'\n  | 'containsErrors'\n  | 'notContainsErrors'\n  | 'timePeriod';\n\nexport type CellIsOperator =\n  | 'lessThan'\n  | 'lessThanOrEqual'\n  | 'equal'\n  | 'notEqual'\n  | 'greaterThanOrEqual'\n  | 'greaterThan'\n  | 'between'\n  | 'notBetween';\n\nexport type TextOperator = 'containsText' | 'notContains' | 'beginsWith' | 'endsWith';\n\nexport type TimePeriod =\n  | 'today'\n  | 'yesterday'\n  | 'tomorrow'\n  | 'last7Days'\n  | 'thisMonth'\n  | 'lastMonth'\n  | 'nextMonth'\n  | 'thisWeek'\n  | 'lastWeek'\n  | 'nextWeek';\n\nexport interface ConditionalFormattingRule {\n  /** Wire-level rule kind. */\n  type: ConditionalFormattingRuleType;\n  /** 1-based priority — Excel evaluates lower priority first. */\n  priority: number;\n  /** Index into Stylesheet.dxfs for the cell-format applied when the rule fires. */\n  dxfId?: number;\n  /** Stop evaluating subsequent rules on the same cell when this rule matches. */\n  stopIfTrue?: boolean;\n  /** cellIs operator. */\n  operator?: CellIsOperator | TextOperator | string;\n  /** Comparison string for the contains-text family. */\n  text?: string;\n  /** top10: rank percentage flag. */\n  percent?: boolean;\n  /** top10: bottom-N flag (false = top-N, true = bottom-N). */\n  bottom?: boolean;\n  /** top10: rank value (defaults to 10). */\n  rank?: number;\n  /** aboveAverage: aboveAverage=\"0\" → below average. */\n  aboveAverage?: boolean;\n  /** aboveAverage: equalAverage flag. */\n  equalAverage?: boolean;\n  /** aboveAverage: stdDev. */\n  stdDev?: number;\n  /** timePeriod token. */\n  timePeriod?: TimePeriod;\n  /** 0..3 formula strings — varies by rule type. */\n  formulas: string[];\n  /**\n   * Raw inner XML for colorScale / dataBar / iconSet rules. Stage-1 stores the\n   * verbatim child markup so saves round-trip without our needing to model cfvo\n   * / colors / iconSets fully.\n   */\n  innerXml?: string;\n}\n\nexport interface ConditionalFormatting {\n  sqref: MultiCellRange;\n  rules: ConditionalFormattingRule[];\n  pivot?: boolean;\n}\n\nexport function makeConditionalFormatting(opts: {\n  sqref: MultiCellRange | string;\n  rules?: ConditionalFormattingRule[];\n  pivot?: boolean;\n}): ConditionalFormatting {\n  return {\n    sqref: typeof opts.sqref === 'string' ? parseMultiCellRange(opts.sqref) : opts.sqref,\n    rules: opts.rules ?? [],\n    ...(opts.pivot !== undefined ? { pivot: opts.pivot } : {}),\n  };\n}\n\nexport function makeCfRule(\n  opts: Partial<ConditionalFormattingRule> & {\n    type: ConditionalFormattingRuleType;\n    priority: number;\n  },\n): ConditionalFormattingRule {\n  return {\n    type: opts.type,\n    priority: opts.priority,\n    formulas: opts.formulas ?? [],\n    ...(opts.dxfId !== undefined ? { dxfId: opts.dxfId } : {}),\n    ...(opts.stopIfTrue !== undefined ? { stopIfTrue: opts.stopIfTrue } : {}),\n    ...(opts.operator !== undefined ? { operator: opts.operator } : {}),\n    ...(opts.text !== undefined ? { text: opts.text } : {}),\n    ...(opts.percent !== undefined ? { percent: opts.percent } : {}),\n    ...(opts.bottom !== undefined ? { bottom: opts.bottom } : {}),\n    ...(opts.rank !== undefined ? { rank: opts.rank } : {}),\n    ...(opts.aboveAverage !== undefined ? { aboveAverage: opts.aboveAverage } : {}),\n    ...(opts.equalAverage !== undefined ? { equalAverage: opts.equalAverage } : {}),\n    ...(opts.stdDev !== undefined ? { stdDev: opts.stdDev } : {}),\n    ...(opts.timePeriod !== undefined ? { timePeriod: opts.timePeriod } : {}),\n    ...(opts.innerXml !== undefined ? { innerXml: opts.innerXml } : {}),\n  };\n}\n\n// ---- Worksheet ergonomic builders ---------------------------------------\n\nimport type { Worksheet } from './worksheet';\n\nconst resolveCfSqref = (sqref: MultiCellRange | string): MultiCellRange =>\n  typeof sqref === 'string' ? parseMultiCellRange(sqref) : sqref;\n\nconst nextCfPriority = (ws: Worksheet): number => {\n  let max = 0;\n  for (const cf of ws.conditionalFormatting) {\n    for (const r of cf.rules) {\n      if (r.priority > max) max = r.priority;\n    }\n  }\n  return max + 1;\n};\n\n/** Push one CF rule onto the worksheet, wrapping it in a ConditionalFormatting block keyed off `sqref`. */\nconst pushRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  rule: ConditionalFormattingRule,\n): ConditionalFormattingRule => {\n  ws.conditionalFormatting.push(makeConditionalFormatting({ sqref: resolveCfSqref(sqref), rules: [rule] }));\n  return rule;\n};\n\n/**\n * \"If cell value [op] formula → apply dxf\". Mirrors Excel's \"Highlight Cell\n * Rules → ...\" UI.\n */\nexport const addCellIsRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: {\n    operator: CellIsOperator;\n    formula1: string;\n    formula2?: string;\n    dxfId?: number;\n    stopIfTrue?: boolean;\n    priority?: number;\n  },\n): ConditionalFormattingRule => {\n  const formulas = opts.formula2 !== undefined ? [opts.formula1, opts.formula2] : [opts.formula1];\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: 'cellIs',\n      priority: opts.priority ?? nextCfPriority(ws),\n      operator: opts.operator,\n      formulas,\n      ...(opts.dxfId !== undefined ? { dxfId: opts.dxfId } : {}),\n      ...(opts.stopIfTrue !== undefined ? { stopIfTrue: opts.stopIfTrue } : {}),\n    }),\n  );\n};\n\n/** Top-N or bottom-N rule. `bottom: true` → bottom-N. `percent: true` → percentile. */\nexport const addTopNRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: { rank?: number; bottom?: boolean; percent?: boolean; dxfId?: number; priority?: number },\n): ConditionalFormattingRule => {\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: 'top10',\n      priority: opts.priority ?? nextCfPriority(ws),\n      formulas: [],\n      ...(opts.rank !== undefined ? { rank: opts.rank } : {}),\n      ...(opts.bottom !== undefined ? { bottom: opts.bottom } : {}),\n      ...(opts.percent !== undefined ? { percent: opts.percent } : {}),\n      ...(opts.dxfId !== undefined ? { dxfId: opts.dxfId } : {}),\n    }),\n  );\n};\n\n/** Above/below-average rule. `aboveAverage: false` → below-average; provide `stdDev` for ±N stddev. */\nexport const addAverageRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: {\n    aboveAverage?: boolean;\n    equalAverage?: boolean;\n    stdDev?: number;\n    dxfId?: number;\n    priority?: number;\n  },\n): ConditionalFormattingRule => {\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: 'aboveAverage',\n      priority: opts.priority ?? nextCfPriority(ws),\n      formulas: [],\n      ...(opts.aboveAverage !== undefined ? { aboveAverage: opts.aboveAverage } : {}),\n      ...(opts.equalAverage !== undefined ? { equalAverage: opts.equalAverage } : {}),\n      ...(opts.stdDev !== undefined ? { stdDev: opts.stdDev } : {}),\n      ...(opts.dxfId !== undefined ? { dxfId: opts.dxfId } : {}),\n    }),\n  );\n};\n\n/** Duplicate-values rule (each cell whose value appears more than once gets the dxf). */\nexport const addDuplicateValuesRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: { dxfId?: number; priority?: number; unique?: boolean } = {},\n): ConditionalFormattingRule => {\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: opts.unique ? 'uniqueValues' : 'duplicateValues',\n      priority: opts.priority ?? nextCfPriority(ws),\n      formulas: [],\n      ...(opts.dxfId !== undefined ? { dxfId: opts.dxfId } : {}),\n    }),\n  );\n};\n\n/** Free-form formula rule — any `=ISNUMBER(A1)`-style boolean expression. */\nexport const addFormulaRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: { formula: string; dxfId?: number; stopIfTrue?: boolean; priority?: number },\n): ConditionalFormattingRule => {\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: 'expression',\n      priority: opts.priority ?? nextCfPriority(ws),\n      formulas: [opts.formula],\n      ...(opts.dxfId !== undefined ? { dxfId: opts.dxfId } : {}),\n      ...(opts.stopIfTrue !== undefined ? { stopIfTrue: opts.stopIfTrue } : {}),\n    }),\n  );\n};\n\n/** Text-based rule (containsText / notContains / beginsWith / endsWith). */\nexport const addTextRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: { operator: TextOperator; text: string; dxfId?: number; priority?: number },\n): ConditionalFormattingRule => {\n  // Excel uses different `type` tokens for the four text operators.\n  const typeMap: Record<TextOperator, ConditionalFormattingRuleType> = {\n    containsText: 'containsText',\n    notContains: 'notContainsText',\n    beginsWith: 'beginsWith',\n    endsWith: 'endsWith',\n  };\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: typeMap[opts.operator],\n      priority: opts.priority ?? nextCfPriority(ws),\n      operator: opts.operator,\n      text: opts.text,\n      formulas: [],\n      ...(opts.dxfId !== undefined ? { dxfId: opts.dxfId } : {}),\n    }),\n  );\n};\n\n// ---- Visual rule builders (colorScale / dataBar / iconSet) --------------\n\nexport type CfvoType = 'min' | 'max' | 'num' | 'percent' | 'percentile' | 'formula';\n\nexport interface Cfvo {\n  type: CfvoType;\n  /** Required for num / percent / percentile / formula; ignored for min / max. */\n  val?: string;\n}\n\nexport type IconSetStyle =\n  | '3Arrows'\n  | '3ArrowsGray'\n  | '3Flags'\n  | '3Signs'\n  | '3Symbols'\n  | '3Symbols2'\n  | '3TrafficLights1'\n  | '3TrafficLights2'\n  | '4Arrows'\n  | '4ArrowsGray'\n  | '4Rating'\n  | '4RedToBlack'\n  | '4TrafficLights'\n  | '5Arrows'\n  | '5ArrowsGray'\n  | '5Quarters'\n  | '5Rating';\n\nconst escapeAttr = escapeXmlAttr;\n\nconst renderCfvo = (c: Cfvo): string => {\n  if (c.type === 'min' || c.type === 'max') {\n    if (c.val === undefined) return `<cfvo type=\"${c.type}\"/>`;\n    return `<cfvo type=\"${c.type}\" val=\"${escapeAttr(c.val)}\"/>`;\n  }\n  if (c.val === undefined) {\n    throw new OpenXmlSchemaError(`cfvo type \"${c.type}\" requires val`);\n  }\n  return `<cfvo type=\"${c.type}\" val=\"${escapeAttr(c.val)}\"/>`;\n};\n\nconst renderColor = (hex: string): string => `<color rgb=\"${escapeAttr(hex)}\"/>`;\n\n/**\n * Color-scale rule — gradient between 2 or 3 reference points. Each cfvo pairs\n * with one color.\n */\nexport const addColorScaleRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: {\n    cfvos: ReadonlyArray<Cfvo>;\n    /** Hex strings, e.g. `'FFFF0000'`; one per cfvo. */\n    colors: ReadonlyArray<string>;\n    priority?: number;\n    stopIfTrue?: boolean;\n  },\n): ConditionalFormattingRule => {\n  if (opts.cfvos.length !== 2 && opts.cfvos.length !== 3) {\n    throw new OpenXmlSchemaError(`addColorScaleRule: cfvos must be length 2 or 3; got ${opts.cfvos.length}`);\n  }\n  if (opts.colors.length !== opts.cfvos.length) {\n    throw new OpenXmlSchemaError(\n      `addColorScaleRule: colors length (${opts.colors.length}) must match cfvos length (${opts.cfvos.length})`,\n    );\n  }\n  const inner = `<colorScale>${opts.cfvos.map(renderCfvo).join('')}${opts.colors.map(renderColor).join('')}</colorScale>`;\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: 'colorScale',\n      priority: opts.priority ?? nextCfPriority(ws),\n      formulas: [],\n      innerXml: inner,\n      ...(opts.stopIfTrue !== undefined ? { stopIfTrue: opts.stopIfTrue } : {}),\n    }),\n  );\n};\n\n/**\n * Data-bar rule — a gradient bar inside each cell sized to the value. Defaults\n * to `min`/`max` cfvos so the bar spans the visible range.\n */\nexport const addDataBarRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: {\n    color: string;\n    minCfvo?: Cfvo;\n    maxCfvo?: Cfvo;\n    minLength?: number;\n    maxLength?: number;\n    showValue?: boolean;\n    priority?: number;\n    stopIfTrue?: boolean;\n  },\n): ConditionalFormattingRule => {\n  const min = opts.minCfvo ?? { type: 'min' };\n  const max = opts.maxCfvo ?? { type: 'max' };\n  let attrs = '';\n  if (opts.minLength !== undefined) attrs += ` minLength=\"${opts.minLength}\"`;\n  if (opts.maxLength !== undefined) attrs += ` maxLength=\"${opts.maxLength}\"`;\n  if (opts.showValue !== undefined) attrs += ` showValue=\"${opts.showValue ? '1' : '0'}\"`;\n  const inner = `<dataBar${attrs}>${renderCfvo(min)}${renderCfvo(max)}${renderColor(opts.color)}</dataBar>`;\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: 'dataBar',\n      priority: opts.priority ?? nextCfPriority(ws),\n      formulas: [],\n      innerXml: inner,\n      ...(opts.stopIfTrue !== undefined ? { stopIfTrue: opts.stopIfTrue } : {}),\n    }),\n  );\n};\n\n/**\n * Icon-set rule — visual icons (arrows / lights / flags) drawn next to each\n * cell value. The number of cfvos depends on the icon set (3 for `3Arrows`, 4\n * for `4Arrows`, 5 for `5Arrows`, etc).\n */\nexport const addIconSetRule = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  opts: {\n    iconSet: IconSetStyle | string;\n    cfvos: ReadonlyArray<Cfvo>;\n    reverse?: boolean;\n    showValue?: boolean;\n    percent?: boolean;\n    priority?: number;\n    stopIfTrue?: boolean;\n  },\n): ConditionalFormattingRule => {\n  if (opts.cfvos.length < 3 || opts.cfvos.length > 5) {\n    throw new OpenXmlSchemaError(`addIconSetRule: cfvos must be length 3..5; got ${opts.cfvos.length}`);\n  }\n  let attrs = ` iconSet=\"${escapeAttr(opts.iconSet)}\"`;\n  if (opts.reverse !== undefined) attrs += ` reverse=\"${opts.reverse ? '1' : '0'}\"`;\n  if (opts.showValue !== undefined) attrs += ` showValue=\"${opts.showValue ? '1' : '0'}\"`;\n  if (opts.percent !== undefined) attrs += ` percent=\"${opts.percent ? '1' : '0'}\"`;\n  const inner = `<iconSet${attrs}>${opts.cfvos.map(renderCfvo).join('')}</iconSet>`;\n  return pushRule(\n    ws,\n    sqref,\n    makeCfRule({\n      type: 'iconSet',\n      priority: opts.priority ?? nextCfPriority(ws),\n      formulas: [],\n      innerXml: inner,\n      ...(opts.stopIfTrue !== undefined ? { stopIfTrue: opts.stopIfTrue } : {}),\n    }),\n  );\n};\n","// Data validations.\n//\n// A DataValidation entry attaches a constraint (one of seven type kinds,\n// optional operator, two formula slots) to a sqref-style MultiCellRange.\n// Stage-1 maps every OOXML attribute we have a use for; imeMode + numeric/value\n// clamps land later when phase 7's Asian-locale support catches up.\n\nimport { type MultiCellRange, parseMultiCellRange } from './cell-range';\n\nexport type DataValidationType = 'whole' | 'decimal' | 'list' | 'date' | 'time' | 'textLength' | 'custom';\nexport type DataValidationOperator =\n  | 'between'\n  | 'notBetween'\n  | 'equal'\n  | 'notEqual'\n  | 'greaterThan'\n  | 'greaterThanOrEqual'\n  | 'lessThan'\n  | 'lessThanOrEqual';\nexport type DataValidationErrorStyle = 'stop' | 'warning' | 'information';\n\nexport interface DataValidation {\n  /** Constraint kind. `'list'` is the dropdown form (formula1 = comma list or range). */\n  type: DataValidationType;\n  /** Comparison operator — only meaningful for whole/decimal/date/time/textLength. */\n  operator?: DataValidationOperator;\n  /** Lower-bound formula or list source. Always required for list/whole/decimal/date/time. */\n  formula1?: string;\n  /** Upper-bound formula. Used by between / notBetween only. */\n  formula2?: string;\n  /** Allow empty cells. */\n  allowBlank?: boolean;\n  /** Show the input message popover when the cell is selected. */\n  showInputMessage?: boolean;\n  /** Show the error message popover on invalid entry. */\n  showErrorMessage?: boolean;\n  errorTitle?: string;\n  error?: string;\n  errorStyle?: DataValidationErrorStyle;\n  promptTitle?: string;\n  prompt?: string;\n  /** Excel inverts this attribute: `showDropDown=\"1\"` actually *hides* the dropdown arrow on list-type validators. We mirror the wire form. */\n  showDropDown?: boolean;\n  /** Apply-to range (sqref). */\n  sqref: MultiCellRange;\n}\n\nexport function makeDataValidation(\n  opts: Omit<Partial<DataValidation>, 'sqref'> & {\n    type: DataValidationType;\n    sqref: MultiCellRange | string;\n  },\n): DataValidation {\n  return {\n    type: opts.type,\n    sqref: typeof opts.sqref === 'string' ? parseMultiCellRange(opts.sqref) : opts.sqref,\n    ...(opts.operator !== undefined ? { operator: opts.operator } : {}),\n    ...(opts.formula1 !== undefined ? { formula1: opts.formula1 } : {}),\n    ...(opts.formula2 !== undefined ? { formula2: opts.formula2 } : {}),\n    ...(opts.allowBlank !== undefined ? { allowBlank: opts.allowBlank } : {}),\n    ...(opts.showInputMessage !== undefined ? { showInputMessage: opts.showInputMessage } : {}),\n    ...(opts.showErrorMessage !== undefined ? { showErrorMessage: opts.showErrorMessage } : {}),\n    ...(opts.errorTitle !== undefined ? { errorTitle: opts.errorTitle } : {}),\n    ...(opts.error !== undefined ? { error: opts.error } : {}),\n    ...(opts.errorStyle !== undefined ? { errorStyle: opts.errorStyle } : {}),\n    ...(opts.promptTitle !== undefined ? { promptTitle: opts.promptTitle } : {}),\n    ...(opts.prompt !== undefined ? { prompt: opts.prompt } : {}),\n    ...(opts.showDropDown !== undefined ? { showDropDown: opts.showDropDown } : {}),\n  };\n}\n\n// ---- Worksheet ergonomic builders ---------------------------------------\n\nimport type { Worksheet } from './worksheet';\n\nconst resolveSqref = (sqref: MultiCellRange | string): MultiCellRange =>\n  typeof sqref === 'string' ? parseMultiCellRange(sqref) : sqref;\n\nexport interface ValidationCommon {\n  /** Show the dropdown / input prompt when the cell is selected. */\n  prompt?: string;\n  promptTitle?: string;\n  /** Show an error dialog when the user types an invalid value. */\n  error?: string;\n  errorTitle?: string;\n  errorStyle?: DataValidationErrorStyle;\n  allowBlank?: boolean;\n}\n\n/**\n * Add a list-type dropdown validation to a range. `values` may be an inline\n * list (`['Red', 'Green', 'Blue']`) or a sheet reference\n * (`'=Sheet1!$A$1:$A$10'`).\n */\nexport const addListValidation = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  values: ReadonlyArray<string> | string,\n  opts: ValidationCommon = {},\n): DataValidation => {\n  const formula1 = Array.isArray(values)\n    ? `\"${(values as ReadonlyArray<string>).join(',')}\"`\n    : (values as string);\n  const dv = makeDataValidation({\n    type: 'list',\n    sqref: resolveSqref(sqref),\n    formula1,\n    allowBlank: opts.allowBlank ?? true,\n    showInputMessage: opts.prompt !== undefined,\n    showErrorMessage: opts.error !== undefined || opts.errorStyle !== undefined,\n    ...(opts.errorStyle !== undefined ? { errorStyle: opts.errorStyle } : {}),\n    ...(opts.error !== undefined ? { error: opts.error } : {}),\n    ...(opts.errorTitle !== undefined ? { errorTitle: opts.errorTitle } : {}),\n    ...(opts.prompt !== undefined ? { prompt: opts.prompt } : {}),\n    ...(opts.promptTitle !== undefined ? { promptTitle: opts.promptTitle } : {}),\n  });\n  ws.dataValidations.push(dv);\n  return dv;\n};\n\n/**\n * Add a number-range validation. `between(min, max)` matches Excel's \"Whole\n * Number\" → \"between\" form by default. Use `kind: 'decimal'` for decimal\n * (default 'whole').\n */\nexport const addNumberValidation = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  range: { min: number; max?: number; operator?: DataValidationOperator; kind?: 'whole' | 'decimal' },\n  opts: ValidationCommon = {},\n): DataValidation => {\n  const operator: DataValidationOperator =\n    range.operator ?? (range.max !== undefined ? 'between' : 'greaterThanOrEqual');\n  const dv = makeDataValidation({\n    type: range.kind ?? 'whole',\n    sqref: resolveSqref(sqref),\n    operator,\n    formula1: String(range.min),\n    ...(range.max !== undefined ? { formula2: String(range.max) } : {}),\n    allowBlank: opts.allowBlank ?? true,\n    showInputMessage: opts.prompt !== undefined,\n    showErrorMessage: opts.error !== undefined || opts.errorStyle !== undefined,\n    ...(opts.errorStyle !== undefined ? { errorStyle: opts.errorStyle } : {}),\n    ...(opts.error !== undefined ? { error: opts.error } : {}),\n    ...(opts.errorTitle !== undefined ? { errorTitle: opts.errorTitle } : {}),\n    ...(opts.prompt !== undefined ? { prompt: opts.prompt } : {}),\n    ...(opts.promptTitle !== undefined ? { promptTitle: opts.promptTitle } : {}),\n  });\n  ws.dataValidations.push(dv);\n  return dv;\n};\n\n/**\n * Add a date-range validation. Dates are passed as Excel serial numbers (use\n * `dateToExcel` to convert from JS `Date`).\n */\nexport const addDateValidation = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  range: { min: number; max?: number; operator?: DataValidationOperator },\n  opts: ValidationCommon = {},\n): DataValidation => {\n  const operator: DataValidationOperator =\n    range.operator ?? (range.max !== undefined ? 'between' : 'greaterThanOrEqual');\n  const dv = makeDataValidation({\n    type: 'date',\n    sqref: resolveSqref(sqref),\n    operator,\n    formula1: String(range.min),\n    ...(range.max !== undefined ? { formula2: String(range.max) } : {}),\n    allowBlank: opts.allowBlank ?? true,\n    showInputMessage: opts.prompt !== undefined,\n    showErrorMessage: opts.error !== undefined || opts.errorStyle !== undefined,\n    ...(opts.errorStyle !== undefined ? { errorStyle: opts.errorStyle } : {}),\n    ...(opts.error !== undefined ? { error: opts.error } : {}),\n    ...(opts.errorTitle !== undefined ? { errorTitle: opts.errorTitle } : {}),\n    ...(opts.prompt !== undefined ? { prompt: opts.prompt } : {}),\n    ...(opts.promptTitle !== undefined ? { promptTitle: opts.promptTitle } : {}),\n  });\n  ws.dataValidations.push(dv);\n  return dv;\n};\n\n/** Add a custom-formula validation (`formula1` evaluated for each cell). */\nexport const addCustomValidation = (\n  ws: Worksheet,\n  sqref: MultiCellRange | string,\n  formula: string,\n  opts: ValidationCommon = {},\n): DataValidation => {\n  const dv = makeDataValidation({\n    type: 'custom',\n    sqref: resolveSqref(sqref),\n    formula1: formula,\n    allowBlank: opts.allowBlank ?? true,\n    showInputMessage: opts.prompt !== undefined,\n    showErrorMessage: opts.error !== undefined || opts.errorStyle !== undefined,\n    ...(opts.errorStyle !== undefined ? { errorStyle: opts.errorStyle } : {}),\n    ...(opts.error !== undefined ? { error: opts.error } : {}),\n    ...(opts.errorTitle !== undefined ? { errorTitle: opts.errorTitle } : {}),\n    ...(opts.prompt !== undefined ? { prompt: opts.prompt } : {}),\n    ...(opts.promptTitle !== undefined ? { promptTitle: opts.promptTitle } : {}),\n  });\n  ws.dataValidations.push(dv);\n  return dv;\n};\n","// Excel Table object (xl/tables/tableN.xml).\n//\n// Tables ride on top of a worksheet range, give it a name + structured column\n// references, and own their own AutoFilter. Each table sits in a separate part\n// — the worksheet only carries a `<tableParts>` block pointing at the\n// workbook-rels rId. Stage-1 covers the table shell + columns + styleInfo +\n// autoFilter; sortState / totals row formulas / calculated column formulas /\n// xml extlst are reserved for later.\n\nimport type { Workbook } from '../workbook/workbook';\nimport type { AutoFilter } from './auto-filter';\nimport type { Worksheet } from './worksheet';\n\nexport interface TableColumn {\n  /** 1-based column id (per-table). */\n  id: number;\n  /** Header name. */\n  name: string;\n  /** Totals-row aggregation function. */\n  totalsRowFunction?: 'sum' | 'min' | 'max' | 'count' | 'countNums' | 'average' | 'stdDev' | 'var' | 'custom';\n  /** Override label for the totals row. */\n  totalsRowLabel?: string;\n  /** Custom totals-row formula text. */\n  totalsRowFormula?: string;\n  /** Calculated-column formula. */\n  calculatedColumnFormula?: string;\n}\n\nexport interface TableStyleInfo {\n  /** Built-in style name (TableStyleMedium2, etc) or custom. */\n  name?: string;\n  showFirstColumn?: boolean;\n  showLastColumn?: boolean;\n  showRowStripes?: boolean;\n  showColumnStripes?: boolean;\n}\n\nexport interface TableDefinition {\n  /** Workbook-unique id (`<table id=\"N\">`). */\n  id: number;\n  /** Workbook-unique displayName — Excel surfaces this in formulas. */\n  displayName: string;\n  /** Optional friendly name; usually matches `displayName`. */\n  name?: string;\n  /** Range covered by the table, e.g. \"A1:E10\". */\n  ref: string;\n  /** Number of header rows. Defaults to 1; 0 means a header-less table. */\n  headerRowCount?: number;\n  /** Number of totals rows. */\n  totalsRowCount?: number;\n  /** Whether the totals row is currently visible. */\n  totalsRowShown?: boolean;\n  styleInfo?: TableStyleInfo;\n  columns: TableColumn[];\n  autoFilter?: AutoFilter;\n  /** Worksheet-rels rId — populated on read; the writer assigns its own. */\n  rId?: string;\n}\n\nexport function makeTableColumn(opts: { id: number; name: string }): TableColumn {\n  return { id: opts.id, name: opts.name };\n}\n\nexport function makeTableDefinition(opts: {\n  id: number;\n  displayName: string;\n  ref: string;\n  name?: string;\n  columns?: TableColumn[];\n  headerRowCount?: number;\n  totalsRowCount?: number;\n  totalsRowShown?: boolean;\n  styleInfo?: TableStyleInfo;\n  autoFilter?: AutoFilter;\n}): TableDefinition {\n  return {\n    id: opts.id,\n    displayName: opts.displayName,\n    ref: opts.ref,\n    columns: opts.columns ?? [],\n    ...(opts.name !== undefined ? { name: opts.name } : {}),\n    ...(opts.headerRowCount !== undefined ? { headerRowCount: opts.headerRowCount } : {}),\n    ...(opts.totalsRowCount !== undefined ? { totalsRowCount: opts.totalsRowCount } : {}),\n    ...(opts.totalsRowShown !== undefined ? { totalsRowShown: opts.totalsRowShown } : {}),\n    ...(opts.styleInfo ? { styleInfo: opts.styleInfo } : {}),\n    ...(opts.autoFilter ? { autoFilter: opts.autoFilter } : {}),\n  };\n}\n\nconst nextTableId = (wb: Workbook): number => {\n  let max = 0;\n  for (const ref of wb.sheets) {\n    if (ref.kind !== 'worksheet') continue;\n    for (const t of ref.sheet.tables) {\n      if (t.id > max) max = t.id;\n    }\n  }\n  return max + 1;\n};\n\n/**\n * High-level wrapper that builds a TableDefinition + pushes it onto `ws.tables`\n * in one call. Auto-assigns the workbook-unique `id`, derives `displayName`\n * from the supplied `name`, and constructs `TableColumn` records (1-based ids)\n * from a string-array shorthand.\n */\nexport const addExcelTable = (\n  wb: Workbook,\n  ws: Worksheet,\n  opts: {\n    name: string;\n    ref: string;\n    columns: ReadonlyArray<string | TableColumn>;\n    style?: string;\n    styleInfo?: TableStyleInfo;\n    headerRowCount?: number;\n    totalsRowCount?: number;\n    totalsRowShown?: boolean;\n    autoFilter?: AutoFilter;\n    displayName?: string;\n  },\n): TableDefinition => {\n  const cols: TableColumn[] = opts.columns.map((c, i): TableColumn =>\n    typeof c === 'string' ? { id: i + 1, name: c } : c,\n  );\n  const styleInfo: TableStyleInfo | undefined =\n    opts.styleInfo ??\n    (opts.style !== undefined ? { name: opts.style, showRowStripes: true, showColumnStripes: false } : undefined);\n  const def: TableDefinition = makeTableDefinition({\n    id: nextTableId(wb),\n    displayName: opts.displayName ?? opts.name,\n    name: opts.name,\n    ref: opts.ref,\n    columns: cols,\n    ...(opts.headerRowCount !== undefined ? { headerRowCount: opts.headerRowCount } : {}),\n    ...(opts.totalsRowCount !== undefined ? { totalsRowCount: opts.totalsRowCount } : {}),\n    ...(opts.totalsRowShown !== undefined ? { totalsRowShown: opts.totalsRowShown } : {}),\n    ...(styleInfo ? { styleInfo } : {}),\n    ...(opts.autoFilter ? { autoFilter: opts.autoFilter } : {}),\n  });\n  ws.tables.push(def);\n  return def;\n};\n"],"mappings":"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"}