@dou-ui/pivot 0.0.0-stage → 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/assets/default-PXde2wzm.js.map +1 -0
  4. package/dist/core/format.js +116 -0
  5. package/dist/core/format.js.map +1 -0
  6. package/dist/core/labels.js +28 -0
  7. package/dist/core/labels.js.map +1 -0
  8. package/dist/core/layout.js +190 -0
  9. package/dist/core/layout.js.map +1 -0
  10. package/dist/core/source.js +46 -0
  11. package/dist/core/source.js.map +1 -0
  12. package/dist/core/table.js +154 -0
  13. package/dist/core/table.js.map +1 -0
  14. package/dist/engine/aggregate.js +51 -0
  15. package/dist/engine/aggregate.js.map +1 -0
  16. package/dist/engine/compute.js +735 -0
  17. package/dist/engine/compute.js.map +1 -0
  18. package/dist/engine/engine.js +46 -0
  19. package/dist/engine/engine.js.map +1 -0
  20. package/dist/engine/formula.js +225 -0
  21. package/dist/engine/formula.js.map +1 -0
  22. package/dist/engine/group.js +107 -0
  23. package/dist/engine/group.js.map +1 -0
  24. package/dist/engine/hash.js +108 -0
  25. package/dist/engine/hash.js.map +1 -0
  26. package/dist/engine/index.js +8 -0
  27. package/dist/engine/show.js +132 -0
  28. package/dist/engine/show.js.map +1 -0
  29. package/dist/engine/source.js +339 -0
  30. package/dist/engine/source.js.map +1 -0
  31. package/dist/engine/tree.js +226 -0
  32. package/dist/engine/tree.js.map +1 -0
  33. package/dist/fields/fields.js +6 -0
  34. package/dist/fields/fields.js.map +1 -0
  35. package/dist/fields/fields.vue_vue_type_script_setup_true_lang.js +881 -0
  36. package/dist/fields/fields.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/fields/filter-list.js +6 -0
  38. package/dist/fields/filter-list.js.map +1 -0
  39. package/dist/fields/filter-list.vue_vue_type_script_setup_true_lang.js +184 -0
  40. package/dist/fields/filter-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/fields/formula-dialog.js +6 -0
  42. package/dist/fields/formula-dialog.js.map +1 -0
  43. package/dist/fields/formula-dialog.vue_vue_type_script_setup_true_lang.js +165 -0
  44. package/dist/fields/formula-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/fields/model.js +200 -0
  46. package/dist/fields/model.js.map +1 -0
  47. package/dist/fields/top-dialog.js +6 -0
  48. package/dist/fields/top-dialog.js.map +1 -0
  49. package/dist/fields/top-dialog.vue_vue_type_script_setup_true_lang.js +174 -0
  50. package/dist/fields/top-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/index.js +16 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/locale.js +508 -0
  54. package/dist/locale.js.map +1 -0
  55. package/dist/pivot.js +6 -0
  56. package/dist/pivot.js.map +1 -0
  57. package/dist/pivot.vue_vue_type_script_setup_true_lang.js +1711 -0
  58. package/dist/pivot.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/style.css +1 -0
  60. package/dist/style.d.ts +1 -0
  61. package/dist/style.js +13 -0
  62. package/dist/types/core/format.d.ts +26 -0
  63. package/dist/types/core/labels.d.ts +14 -0
  64. package/dist/types/core/layout.d.ts +72 -0
  65. package/dist/types/core/source.d.ts +7 -0
  66. package/dist/types/core/table.d.ts +73 -0
  67. package/dist/types/engine/aggregate.d.ts +35 -0
  68. package/dist/types/engine/compute.d.ts +173 -0
  69. package/dist/types/engine/engine.d.ts +29 -0
  70. package/dist/types/engine/formula.d.ts +44 -0
  71. package/dist/types/engine/group.d.ts +34 -0
  72. package/dist/types/engine/hash.d.ts +36 -0
  73. package/dist/types/engine/index.d.ts +15 -0
  74. package/dist/types/engine/show.d.ts +27 -0
  75. package/dist/types/engine/source.d.ts +108 -0
  76. package/dist/types/engine/tree.d.ts +87 -0
  77. package/dist/types/engine/types.d.ts +148 -0
  78. package/dist/types/fields/fields.vue.d.ts +26 -0
  79. package/dist/types/fields/filter-list.vue.d.ts +20 -0
  80. package/dist/types/fields/formula-dialog.vue.d.ts +30 -0
  81. package/dist/types/fields/model.d.ts +52 -0
  82. package/dist/types/fields/top-dialog.vue.d.ts +25 -0
  83. package/dist/types/index.d.ts +458 -0
  84. package/dist/types/locale.d.ts +184 -0
  85. package/dist/types/pivot.vue.d.ts +29 -0
  86. package/dist/types/types.d.ts +199 -0
  87. package/dist/types/worker/client.d.ts +19 -0
  88. package/dist/types/worker/default.d.ts +1 -0
  89. package/dist/types/worker/index.d.ts +5 -0
  90. package/dist/types/worker/protocol.d.ts +60 -0
  91. package/dist/types/worker/serve.d.ts +22 -0
  92. package/dist/worker/client.js +191 -0
  93. package/dist/worker/client.js.map +1 -0
  94. package/dist/worker/default.js +20 -0
  95. package/dist/worker/default.js.map +1 -0
  96. package/dist/worker/index.js +3 -0
  97. package/dist/worker/serve.js +101 -0
  98. package/dist/worker/serve.js.map +1 -0
  99. package/package.json +64 -4
@@ -0,0 +1,20 @@
1
+ // src/worker/default.ts?worker&inline
2
+ var jsContent = "(function() {\n // src/engine/aggregate.ts\n const FAST_AGGREGATES = /* @__PURE__ */ new Set([\n \"sum\",\n \"count\",\n \"avg\",\n \"min\",\n \"max\"\n ]);\n /** Counts each value once; the state holds group numbers, so it never compares values. */\n const distinctAggregator = {\n name: \"distinct\",\n input: \"value\",\n integer: true,\n init: () => /* @__PURE__ */ new Set(),\n add: (state, code) => state.add(code),\n merge: (into, from) => {\n for (const code of from) into.add(code);\n return into;\n },\n result: (state) => state.size\n };\n const BUILTIN_AGGREGATORS = {\n distinct: distinctAggregator,\n median: {\n name: \"median\",\n input: \"number\",\n init: () => [],\n add: (state, value) => (state.push(value), state),\n merge: (into, from) => {\n for (let i = 0; i < from.length; i++) into.push(from[i]);\n return into;\n },\n result: (state) => {\n if (state.length === 0) return null;\n const sorted = Float64Array.from(state).sort();\n const mid = sorted.length >> 1;\n return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n }\n }\n };\n // src/engine/formula.ts\n const MESSAGES = {\n empty: \"公式是空的\",\n incomplete: \"公式不完整\",\n unknownChar: \"无法识别“{detail}”\",\n unexpected: \"这里不应出现“{detail}”\",\n expected: \"这里应当是“{detail}”\",\n unclosedBracket: \"缺少“]”\",\n emptyName: \"方括号里没有字段名\",\n unknownFunction: \"没有名为“{detail}”的函数\",\n needsField: \"{detail}() 需要一个字段\",\n needsName: \"这里应当是字段名\",\n unknownField: \"没有名为“{detail}”的字段\",\n notNumber: \"“{detail}”不是数字字段\"\n };\n /**\n * Why a formula could not be read, and where: `at` is a character offset,\n * `detail` the text the message is about. `code` lets a page word it its own way.\n */\n var PivotFormulaError = class extends Error {\n code;\n at;\n detail;\n constructor(code, at, detail = \"\") {\n super(`[@dou-ui/pivot] 计算值:${MESSAGES[code].replace(\"{detail}\", detail)}`);\n this.code = code;\n this.at = at;\n this.detail = detail;\n this.name = \"PivotFormulaError\";\n }\n };\n const FUNCTIONS = /* @__PURE__ */ new Set([\n \"sum\",\n \"avg\",\n \"min\",\n \"max\",\n \"count\"\n ]);\n const NAME_START = /[\\p{L}_$]/u;\n const NAME_PART = /[\\p{L}\\p{N}_$.]/u;\n function tokenize(text) {\n const out = [];\n let i = 0;\n while (i < text.length) {\n const ch = text[i];\n if (/\\s/.test(ch)) {\n i++;\n continue;\n }\n const at = i;\n if (/[0-9.]/.test(ch)) {\n const match = /^(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/.exec(text.slice(i));\n if (!match) throw new PivotFormulaError(\"unknownChar\", at, ch);\n out.push({\n type: \"number\",\n text: match[0],\n at\n });\n i += match[0].length;\n } else if (ch === \"[\") {\n const end = text.indexOf(\"]\", i + 1);\n if (end < 0) throw new PivotFormulaError(\"unclosedBracket\", at);\n const name = text.slice(i + 1, end).trim();\n if (!name) throw new PivotFormulaError(\"emptyName\", at);\n out.push({\n type: \"name\",\n text: name,\n at,\n quoted: true\n });\n i = end + 1;\n } else if (NAME_START.test(ch)) {\n let j = i + 1;\n while (j < text.length && NAME_PART.test(text[j])) j++;\n out.push({\n type: \"name\",\n text: text.slice(i, j),\n at\n });\n i = j;\n } else if (\"+-*/(),\".includes(ch)) {\n out.push({\n type: \"op\",\n text: ch,\n at\n });\n i++;\n } else throw new PivotFormulaError(\"unknownChar\", at, ch);\n }\n out.push({\n type: \"end\",\n text: \"\",\n at: text.length\n });\n return out;\n }\n /**\n * Reads a formula. Throws a `PivotFormulaError` when it is not well formed.\n * Field names are not checked here (see `computePivot`).\n */\n function parsePivotFormula(text) {\n const tokens = tokenize(text);\n const terms = [];\n let pos = 0;\n const peek = () => tokens[pos];\n const next = () => tokens[pos++];\n const expect = (op) => {\n const t = next();\n if (t.type !== \"op\" || t.text !== op) throw new PivotFormulaError(\"expected\", t.at, op);\n };\n const term = (aggregate, field) => {\n let index = terms.findIndex((t) => t.aggregate === aggregate && t.field === field);\n if (index < 0) index = terms.push({\n aggregate,\n field\n }) - 1;\n return {\n kind: \"term\",\n index\n };\n };\n function expression() {\n let left = product();\n for (let t = peek(); t.type === \"op\" && (t.text === \"+\" || t.text === \"-\"); t = peek()) {\n next();\n left = {\n kind: \"binary\",\n op: t.text,\n left,\n right: product()\n };\n }\n return left;\n }\n function product() {\n let left = unary();\n for (let t = peek(); t.type === \"op\" && (t.text === \"*\" || t.text === \"/\"); t = peek()) {\n next();\n left = {\n kind: \"binary\",\n op: t.text,\n left,\n right: unary()\n };\n }\n return left;\n }\n function unary() {\n const t = peek();\n if (t.type === \"op\" && (t.text === \"-\" || t.text === \"+\")) {\n next();\n const operand = unary();\n return t.text === \"-\" ? {\n kind: \"negate\",\n operand\n } : operand;\n }\n return primary();\n }\n function primary() {\n const t = next();\n if (t.type === \"number\") return {\n kind: \"number\",\n value: Number(t.text)\n };\n if (t.type === \"op\" && t.text === \"(\") {\n const inner = expression();\n expect(\")\");\n return inner;\n }\n if (t.type === \"name\") {\n const after = peek();\n if (!t.quoted && after.type === \"op\" && after.text === \"(\") {\n const name = t.text.toLowerCase();\n if (!FUNCTIONS.has(name)) throw new PivotFormulaError(\"unknownFunction\", t.at, t.text);\n next();\n const close = peek();\n if (close.type === \"op\" && close.text === \")\") {\n if (name !== \"count\") throw new PivotFormulaError(\"needsField\", close.at, t.text);\n next();\n return term(\"count\", null);\n }\n const arg = next();\n if (arg.type !== \"name\") throw new PivotFormulaError(\"needsName\", arg.at);\n expect(\")\");\n return term(name, arg.text);\n }\n return term(\"sum\", t.text);\n }\n if (t.type === \"end\") throw new PivotFormulaError(\"incomplete\", t.at);\n throw new PivotFormulaError(\"unexpected\", t.at, t.text);\n }\n if (peek().type === \"end\") throw new PivotFormulaError(\"empty\", 0);\n const root = expression();\n const rest = peek();\n if (rest.type !== \"end\") throw new PivotFormulaError(\"unexpected\", rest.at, rest.text);\n const run = (node, values) => {\n switch (node.kind) {\n case \"number\": return node.value;\n case \"term\": return values[node.index];\n case \"negate\": return -run(node.operand, values);\n case \"binary\": {\n const a = run(node.left, values);\n const b = run(node.right, values);\n switch (node.op) {\n case \"+\": return a + b;\n case \"-\": return a - b;\n case \"*\": return a * b;\n case \"/\": return b === 0 ? NaN : a / b;\n }\n }\n }\n };\n return {\n source: text,\n terms,\n evaluate: (values) => {\n const v = run(root, values);\n return Number.isFinite(v) ? v : NaN;\n }\n };\n }\n // src/engine/hash.ts\n /**\n * A table from whole numbers (0 to 2^53) to the order they were first seen\n * in: open addressing over typed arrays, so millions of lookups allocate\n * nothing and never touch a `Map`.\n */\n var IntIndex = class IntIndex {\n /** The keys by slot; `-1` marks a free slot. */\n table;\n /** What each slot's key maps to. */\n ids;\n mask;\n /** The keys in the order they were added. */\n keys;\n size = 0;\n constructor(expected = 16) {\n let capacity = 16;\n while (capacity < expected * 2) capacity *= 2;\n this.table = new Float64Array(capacity).fill(-1);\n this.ids = new Int32Array(capacity);\n this.mask = capacity - 1;\n this.keys = new Float64Array(Math.max(8, expected));\n }\n /** The id of `key`, or -1. */\n get(key) {\n const { table, mask } = this;\n let slot = hash(key) & mask;\n for (;;) {\n const at = table[slot];\n if (at === key) return this.ids[slot];\n if (at === -1) return -1;\n slot = slot + 1 & mask;\n }\n }\n /** The id of `key`, adding it with the next id when it is new. */\n add(key) {\n const { table, mask } = this;\n let slot = hash(key) & mask;\n for (;;) {\n const at = table[slot];\n if (at === key) return this.ids[slot];\n if (at === -1) break;\n slot = slot + 1 & mask;\n }\n const id = this.size++;\n table[slot] = key;\n this.ids[slot] = id;\n if (id >= this.keys.length) {\n const keys = new Float64Array(this.keys.length * 2 || 8);\n keys.set(this.keys);\n this.keys = keys;\n }\n this.keys[id] = key;\n if (this.size * 2 > table.length) this.grow();\n return id;\n }\n /** Its arrays, to send to another thread (moved, not copied) or save. */\n pack() {\n return {\n table: this.table,\n ids: this.ids,\n keys: this.keys,\n size: this.size\n };\n }\n /** An index made from `pack()`'s output; plain arrays (from JSON) work too. */\n static unpack(packet) {\n const index = new IntIndex(0);\n index.table = typed(Float64Array, packet.table);\n index.ids = typed(Int32Array, packet.ids);\n index.keys = typed(Float64Array, packet.keys);\n index.mask = index.table.length - 1;\n index.size = packet.size;\n return index;\n }\n grow() {\n const capacity = this.table.length * 2;\n const table = new Float64Array(capacity).fill(-1);\n const ids = new Int32Array(capacity);\n const mask = capacity - 1;\n const { keys, size } = this;\n for (let id = 0; id < size; id++) {\n const key = keys[id];\n let slot = hash(key) & mask;\n while (table[slot] !== -1) slot = slot + 1 & mask;\n table[slot] = key;\n ids[slot] = id;\n }\n this.table = table;\n this.ids = ids;\n this.mask = mask;\n }\n };\n /** The array as it is when it is already of that type (moved from a worker), else a typed copy (from JSON). */\n function typed(Type, array) {\n return array instanceof Type ? array : Type.from(array);\n }\n const HIGH = 4294967296;\n /** Mixes both halves of a key up to 2^53 into 32 bits. */\n function hash(key) {\n let h = Math.imul(key >>> 0 ^ Math.imul(key / HIGH | 0, 2654435761), 2246822507);\n h ^= h >>> 13;\n h = Math.imul(h, 3266489909);\n return h ^ h >>> 16;\n }\n // src/engine/show.ts\n const ON_COLUMNS = /* @__PURE__ */ new Set([\n \"difference\",\n \"percentDifference\",\n \"runningTotal\"\n ]);\n /** The axis a way of showing works along: as given, or the default when that axis has fields. */\n function resolveShow(show, rows, columns) {\n let axis = show.axis ?? (ON_COLUMNS.has(show.as) ? \"columns\" : \"rows\");\n const has = (on) => (on === \"rows\" ? rows : columns).levels.length > 0;\n if (!show.axis && !has(axis) && has(axis === \"rows\" ? \"columns\" : \"rows\")) axis = axis === \"rows\" ? \"columns\" : \"rows\";\n return {\n as: show.as,\n axis\n };\n }\n /** Where each group sits among all the children (its index in `children`). */\n const places = /* @__PURE__ */ new WeakMap();\n function placeOf(tree) {\n let place = places.get(tree);\n if (!place) {\n place = new Int32Array(tree.size);\n tree.children.forEach((node, at) => place[node] = at);\n places.set(tree, place);\n }\n return place;\n }\n /**\n * Works out a value as it is shown, from the summed-up values (`get`).\n * Groups come in their label order, whatever order the rows are shown in.\n */\n var PivotShown = class {\n show;\n rows;\n columns;\n get;\n running = /* @__PURE__ */ new Map();\n sorted = /* @__PURE__ */ new Map();\n constructor(show, rows, columns, get) {\n this.show = show;\n this.rows = rows;\n this.columns = columns;\n this.get = get;\n }\n value(row, column) {\n const { get, show } = this;\n const v = get(row, column);\n switch (show.as) {\n case \"percentOfTotal\": return ratio(v, get(0, 0));\n case \"percentOfRow\": return ratio(v, get(row, 0));\n case \"percentOfColumn\": return ratio(v, get(0, column));\n case \"percentOfParent\":\n if (show.axis === \"rows\") return ratio(v, get(row > 0 ? this.rows.parent[row] : 0, column));\n return ratio(v, get(row, column > 0 ? this.columns.parent[column] : 0));\n case \"difference\":\n case \"percentDifference\": {\n const prev = this.previous(row, column);\n if (prev === null) return NaN;\n const p = show.axis === \"rows\" ? get(prev, column) : get(row, prev);\n if (show.as === \"difference\") return v - p;\n return p === 0 ? NaN : (v - p) / Math.abs(p);\n }\n case \"runningTotal\": return this.runningTotal(row, column);\n case \"rank\": return this.rank(row, column, v);\n }\n }\n /** The group before this one on the axis, or null for the first and for totals. */\n previous(row, column) {\n const tree = this.show.axis === \"rows\" ? this.rows : this.columns;\n const node = this.show.axis === \"rows\" ? row : column;\n if (node <= 0) return null;\n const at = placeOf(tree)[node];\n return at > tree.childStart[tree.parent[node]] ? tree.children[at - 1] : null;\n }\n runningTotal(row, column) {\n const onRows = this.show.axis === \"rows\";\n const tree = onRows ? this.rows : this.columns;\n const node = onRows ? row : column;\n const other = onRows ? column : row;\n const read = (n) => onRows ? this.get(n, other) : this.get(other, n);\n if (node <= 0) return read(node);\n const key = (n) => n * (onRows ? this.columns.size : this.rows.size) + other;\n const cached = this.running.get(key(node));\n if (cached !== void 0) return cached;\n const place = placeOf(tree);\n const first = tree.childStart[tree.parent[node]];\n let at = place[node];\n while (at > first && !this.running.has(key(tree.children[at - 1]))) at--;\n let total = at > first ? this.running.get(key(tree.children[at - 1])) : NaN;\n for (; at <= place[node]; at++) {\n const n = tree.children[at];\n const v = read(n);\n if (v === v) total = total === total ? total + v : v;\n this.running.set(key(n), total);\n }\n return total;\n }\n rank(row, column, v) {\n if (v !== v) return NaN;\n const onRows = this.show.axis === \"rows\";\n const tree = onRows ? this.rows : this.columns;\n const node = onRows ? row : column;\n if (node <= 0) return NaN;\n const other = onRows ? column : row;\n const parent = tree.parent[node];\n const key = parent * (onRows ? this.columns.size : this.rows.size) + other;\n let values = this.sorted.get(key);\n if (!values) {\n const list = [];\n for (let k = tree.childStart[parent]; k < tree.childStart[parent + 1]; k++) {\n const n = tree.children[k];\n const x = onRows ? this.get(n, other) : this.get(other, n);\n if (x === x) list.push(x);\n }\n values = Float64Array.from(list);\n values.sort();\n this.sorted.set(key, values);\n }\n let lo = 0;\n let hi = values.length;\n while (lo < hi) {\n const mid = lo + hi >> 1;\n if (values[mid] <= v) lo = mid + 1;\n else hi = mid;\n }\n return values.length - lo + 1;\n }\n };\n const ratio = (a, b) => b === 0 || b !== b || a !== a ? NaN : a / b;\n // src/engine/group.ts\n /** A name for a grouping, to cache the grouped field under. */\n function groupKey(by) {\n if (by === void 0) return \"\";\n if (typeof by === \"string\") return by;\n return `step:${by.step}:${by.start ?? 0}`;\n }\n const pad = (n, width = 2) => String(n).padStart(width, \"0\");\n /**\n * A date out of a value: a `Date`, a time in ms, or text such as\n * `'2026-03-15'` (read in local time, like the other parts of a day).\n */\n function toDate(value) {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\") return Number.isFinite(value) ? new Date(value) : null;\n if (typeof value === \"string\") {\n const day = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/.exec(value.trim());\n const date = day ? new Date(+day[1], +day[2] - 1, +day[3]) : new Date(value);\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n }\n /** The ISO week a day is in, and the year that week belongs to. */\n function isoWeek(date) {\n const day = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n const weekday = (day.getDay() + 6) % 7;\n day.setDate(day.getDate() - weekday + 3);\n const year = day.getFullYear();\n const first = new Date(year, 0, 4);\n const firstThursday = new Date(year, 0, 4 - (first.getDay() + 6) % 7 + 3);\n return [year, 1 + Math.round((day.getTime() - firstThursday.getTime()) / 6048e5)];\n }\n /**\n * The group a value falls in: plain data that sorts in order and can be\n * saved in the config (filters, collapsed groups): a year is a number\n * (`2026`); a quarter, month, week or day is text (`'2026-Q1'`,\n * `'2026-03'`, `'2026-W12'`, `'2026-03-15'`); a range is its lowest\n * number. `null` when the value has no group (empty, or not a date / number).\n */\n function bucketer(by) {\n if (typeof by === \"object\") {\n const step = by.step > 0 ? by.step : 1;\n const start = by.start ?? 0;\n return (value) => {\n const n = typeof value === \"number\" ? value : value instanceof Date ? value.getTime() : NaN;\n if (!Number.isFinite(n)) return null;\n return +(Math.floor((n - start) / step) * step + start).toPrecision(12);\n };\n }\n return (value) => {\n const date = toDate(value);\n if (!date) return null;\n const year = date.getFullYear();\n switch (by) {\n case \"year\": return year;\n case \"quarter\": return `${year}-Q${Math.floor(date.getMonth() / 3) + 1}`;\n case \"month\": return `${year}-${pad(date.getMonth() + 1)}`;\n case \"week\": {\n const [y, w] = isoWeek(date);\n return `${y}-W${pad(w)}`;\n }\n default: return `${year}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n }\n };\n }\n // src/engine/source.ts\n const SAMPLE = 100;\n /** Records read between looks at the clock while reading in slices. */\n const STEP = 4096;\n const isColumns = (data) => !!data && typeof data === \"object\" && !Array.isArray(data);\n /**\n * The records, read field by field into typed arrays the first time each\n * field is used, and kept until the records change.\n */\n var PivotSource = class {\n /** The records as given: objects, or columns. */\n data;\n length;\n fields;\n locale;\n collator;\n dimensions = /* @__PURE__ */ new Map();\n measures = /* @__PURE__ */ new Map();\n /** Groups read elsewhere (in the page, for a worker), waiting to be put in order. */\n given = /* @__PURE__ */ new Map();\n constructor(data, options = {}) {\n this.data = data;\n this.length = isColumns(data) ? options.length ?? Math.max(0, ...Object.values(data).map((c) => c.length)) : data.length;\n this.locale = options.locale;\n this.collator = new Intl.Collator(options.locale, { numeric: true });\n this.fields = /* @__PURE__ */ new Map();\n for (const field of options.fields ?? inferFields(data)) this.fields.set(field.name, this.resolve(field));\n }\n /** The record at `index`: the object given, or one made from the columns. */\n record(index) {\n const { data } = this;\n if (!isColumns(data)) return data[index];\n const row = {};\n for (const name in data) row[name] = data[name][index];\n return row;\n }\n /** How to read a field from record `i`. */\n reader(field) {\n const { data } = this;\n const read = field.value;\n if (read) {\n if (!isColumns(data)) return (i) => read(data[i], i);\n return (i) => read(this.record(i), i);\n }\n if (isColumns(data)) {\n const column = data[field.name];\n return column ? (i) => column[i] : () => void 0;\n }\n const name = field.name;\n return (i) => data[i]?.[name];\n }\n /** Describes a field (or describes it again), as `fields` would have. */\n define(field) {\n this.fields.set(field.name, this.resolve(field));\n }\n /** Hands over groups read elsewhere, such as in the page for a worker. */\n provide(name, column) {\n this.given.set(name, column);\n for (const key of this.dimensions.keys()) if (key === name || key.startsWith(`${name}\\u0000`)) this.dimensions.delete(key);\n }\n /** Hands over a field's numbers read elsewhere. */\n provideMeasure(name, values) {\n this.measures.set(name, values);\n }\n /** The field called `name`; one is made up for keys the fields do not list. */\n field(name) {\n let field = this.fields.get(name);\n if (!field) {\n field = this.resolve({ name });\n this.fields.set(name, field);\n }\n return field;\n }\n /** The field's values, one per record. */\n values(name) {\n const read = this.reader(this.field(name));\n const out = Array.from({ length: this.length });\n for (let i = 0; i < this.length; i++) out[i] = read(i);\n return out;\n }\n /**\n * The field grouped by value, or by `by`: dates by a period, numbers in\n * ranges. A grouping is worked out from the field's groups, not from the\n * records, so it costs one pass over the group numbers.\n */\n dimension(name, by) {\n const key = by === void 0 ? name : `${name}\\u0000${groupKey(by)}`;\n let column = this.dimensions.get(key);\n if (!column) {\n column = by === void 0 ? this.encode(name) : this.group(name, by);\n this.dimensions.set(key, column);\n }\n return column;\n }\n group(name, by) {\n const base = this.dimension(name);\n const bucketOf = bucketer(by);\n const labels = [];\n const byValue = /* @__PURE__ */ new Map();\n let empty = -1;\n const map = new Uint32Array(base.labels.length);\n base.labels.forEach((label, code) => {\n const bucket = code === base.empty ? null : bucketOf(label);\n let to;\n if (bucket === null) {\n if (empty < 0) {\n empty = labels.length;\n labels.push(null);\n }\n to = empty;\n } else {\n to = byValue.get(bucket);\n if (to === void 0) {\n to = labels.length;\n labels.push(bucket);\n byValue.set(bucket, to);\n }\n }\n map[code] = to;\n });\n const from = base.codes;\n const codes = new Uint32Array(from.length);\n for (let i = 0; i < from.length; i++) codes[i] = map[from[i]];\n const find = (value) => isEmpty(value) ? empty : byValue.get(value) ?? -1;\n return {\n codes,\n labels,\n empty,\n rank: this.rank(labels, { name }, find),\n find\n };\n }\n /** The field as numbers, `NaN` where a record has none. */\n measure(name) {\n let column = this.measures.get(name);\n if (!column) {\n column = run(this.measureSteps(name));\n this.measures.set(name, column);\n }\n return column;\n }\n /**\n * Reads a field's numbers a slice at a time, for reading a large field\n * without holding up the page. The array is new: the source keeps none.\n */\n *measureSteps(name, copy = false) {\n const field = this.field(name);\n const given = isColumns(this.data) && !field.value ? this.data[name] : void 0;\n if (given instanceof Float64Array && given.length === this.length) return copy ? given.slice() : given;\n const read = this.reader(field);\n const column = new Float64Array(this.length);\n for (let i = 0; i < this.length; i++) {\n if (i % STEP === 0 && i) yield;\n column[i] = toNumber(read(i));\n }\n return column;\n }\n resolve(field) {\n return {\n ...field,\n label: field.label ?? field.name,\n type: field.type ?? this.guess(field)\n };\n }\n guess(field) {\n const read = this.reader(field);\n let seen = 0;\n let type;\n for (let i = 0; i < this.length && seen < SAMPLE; i++) {\n const value = read(i);\n if (isEmpty(value)) continue;\n seen++;\n const kind = typeOf(value);\n if (type && type !== kind) return \"string\";\n type = kind;\n }\n return type ?? \"string\";\n }\n encode(name) {\n const field = this.field(name);\n const { codes, labels, empty } = this.given.get(name) ?? run(this.encodeSteps(name));\n const find = finder(labels, empty);\n return {\n codes,\n labels,\n empty,\n rank: this.rank(labels, field, find),\n find\n };\n }\n /**\n * Reads a field's groups a slice at a time, for reading a large field\n * without holding up the page: each record's group number and each\n * group's value, not yet in order.\n */\n *encodeSteps(name) {\n const read = this.reader(this.field(name));\n const n = this.length;\n const codes = new Uint32Array(n);\n const labels = [];\n const byValue = /* @__PURE__ */ new Map();\n const byTime = /* @__PURE__ */ new Map();\n let empty = -1;\n let last = void 0;\n let lastCode = -1;\n const codeOf = (value) => {\n if (isEmpty(value)) {\n if (empty < 0) {\n empty = labels.length;\n labels.push(null);\n }\n return empty;\n }\n if (value instanceof Date) {\n const time = value.getTime();\n let code = byTime.get(time);\n if (code === void 0) {\n code = labels.length;\n labels.push(value);\n byTime.set(time, code);\n }\n return code;\n }\n let code = byValue.get(value);\n if (code === void 0) {\n code = labels.length;\n labels.push(value);\n byValue.set(value, code);\n }\n return code;\n };\n for (let i = 0; i < n; i++) {\n if (i % STEP === 0 && i) yield;\n const value = read(i);\n if (value === last && lastCode >= 0) {\n codes[i] = lastCode;\n continue;\n }\n lastCode = codeOf(value);\n last = value;\n codes[i] = lastCode;\n }\n return {\n codes,\n labels,\n empty\n };\n }\n /** Each group's place: the field's `order` first, then by value, the empty group last. */\n rank(labels, field, find) {\n const order = new Int32Array(labels.length).fill(-1);\n field.order?.forEach((value, at) => {\n const code = find(value);\n if (code >= 0 && order[code] === -1) order[code] = at;\n });\n const codes = Array.from(labels.keys());\n const { collator } = this;\n codes.sort((a, b) => {\n const oa = order[a];\n const ob = order[b];\n if (oa >= 0 || ob >= 0) {\n if (oa < 0) return 1;\n if (ob < 0) return -1;\n return oa - ob;\n }\n return compareValues(labels[a], labels[b], collator);\n });\n const rank = new Uint32Array(labels.length);\n codes.forEach((code, at) => rank[code] = at);\n return rank;\n }\n };\n /** Empty: `null`, `undefined`, `''`, `NaN` and invalid dates all go in the empty group. */\n const isEmpty = (value) => value === null || value === void 0 || value === \"\" || typeof value === \"number\" && Number.isNaN(value) || value instanceof Date && Number.isNaN(value.getTime());\n const typeOf = (value) => typeof value === \"number\" ? \"number\" : typeof value === \"boolean\" ? \"boolean\" : value instanceof Date ? \"date\" : \"string\";\n const KIND = {\n number: 0,\n date: 1,\n string: 2,\n boolean: 3\n };\n /** Natural order: numbers and dates by size, text by the locale (digits as numbers), empty last. */\n function compareValues(a, b, collator) {\n const ea = isEmpty(a);\n const eb = isEmpty(b);\n if (ea || eb) return ea === eb ? 0 : ea ? 1 : -1;\n const ka = KIND[typeOf(a)];\n const kb = KIND[typeOf(b)];\n if (ka !== kb) return ka - kb;\n if (ka === 2) return collator.compare(a, b);\n return Number(a) - Number(b);\n }\n /** A value as a number: numbers, dates and numeric text; `NaN` for anything else. */\n function toNumber(value) {\n if (typeof value === \"number\") return value;\n if (value instanceof Date) return value.getTime();\n if (typeof value === \"string\") {\n const text = value.trim();\n return text === \"\" ? NaN : Number(text);\n }\n return NaN;\n }\n /** Runs reading in slices to the end, at once. */\n function run(steps) {\n for (;;) {\n const step = steps.next();\n if (step.done) return step.value;\n }\n }\n /** Finds a group by its value; dates by their time. */\n function finder(labels, empty) {\n const byValue = /* @__PURE__ */ new Map();\n const byTime = /* @__PURE__ */ new Map();\n labels.forEach((label, code) => {\n if (code === empty) return;\n if (label instanceof Date) byTime.set(label.getTime(), code);\n else byValue.set(label, code);\n });\n return (value) => {\n if (isEmpty(value)) return empty;\n if (value instanceof Date) return byTime.get(value.getTime()) ?? -1;\n return byValue.get(value) ?? -1;\n };\n }\n /** The fields of records nobody described: every key of the first records, or every column. */\n function inferFields(data) {\n if (isColumns(data)) return Object.keys(data).map((name) => ({ name }));\n const rows = data;\n const names = /* @__PURE__ */ new Set();\n for (let i = 0; i < Math.min(rows.length, SAMPLE); i++) {\n const row = rows[i];\n if (row && typeof row === \"object\") for (const key of Object.keys(row)) names.add(key);\n }\n return [...names].map((name) => ({ name }));\n }\n // src/engine/tree.ts\n /**\n * The groups along one axis. Node 0 is the whole axis (the grand total);\n * a node at depth `d` is a group of the `d`-th field inside its parent.\n * Children are kept in label order, as compressed lists: the children of\n * `node` are `children[childStart[node]]` up to `children[childStart[node + 1]]`.\n */\n var PivotTree = class PivotTree {\n /** The root: the grand total, as a row or column group. */\n total = 0;\n size;\n levels;\n parent;\n depth;\n /** The group number within its field (`-1` for the root). */\n code;\n childStart;\n children;\n /** Where each node starts and ends in a walk of the tree, to tell descendants apart. */\n enter;\n exit;\n index;\n stride;\n constructor(builder, levels) {\n const size = builder.size;\n this.size = size;\n this.levels = levels;\n this.parent = builder.parent.slice(0, size);\n this.depth = builder.depth.slice(0, size);\n this.code = builder.code.slice(0, size);\n this.index = builder.index;\n this.stride = builder.stride;\n const childStart = new Int32Array(size + 1);\n for (let node = 1; node < size; node++) childStart[this.parent[node] + 1]++;\n for (let node = 0; node < size; node++) childStart[node + 1] += childStart[node];\n const fill = childStart.slice(0, size);\n const children = new Int32Array(Math.max(0, size - 1));\n for (let node = 1; node < size; node++) children[fill[this.parent[node]]++] = node;\n this.childStart = childStart;\n this.children = children;\n for (let node = 0; node < size; node++) {\n const from = childStart[node];\n const to = childStart[node + 1];\n if (to - from < 2) continue;\n const level = levels[this.depth[node]];\n const { rank } = level.column;\n const sign = level.sort === \"desc\" ? -1 : 1;\n const code = this.code;\n children.subarray(from, to).sort((a, b) => {\n const ra = rank[code[a]];\n const rb = rank[code[b]];\n if (level.column.empty >= 0) {\n if (code[a] === level.column.empty) return 1;\n if (code[b] === level.column.empty) return -1;\n }\n return sign * (ra - rb);\n });\n }\n this.enter = new Int32Array(size);\n this.exit = new Int32Array(size);\n this.walk();\n }\n /** The tree as data: typed arrays to move to another thread, or to write as JSON. */\n pack() {\n return {\n size: this.size,\n stride: this.stride,\n parent: this.parent,\n depth: this.depth,\n code: this.code,\n childStart: this.childStart,\n children: this.children,\n enter: this.enter,\n exit: this.exit,\n index: this.index.pack(),\n levels: this.levels.map((l) => ({\n field: l.field.name,\n sort: l.sort,\n subtotal: l.subtotal,\n ...l.by === void 0 ? {} : { by: l.by },\n labels: l.column.labels,\n empty: l.column.empty,\n rank: l.column.rank\n }))\n };\n }\n /** A tree made from `pack()`'s output, with fields described by `field`. */\n static unpack(packet, field) {\n const tree = Object.create(PivotTree.prototype);\n const levels = packet.levels.map((l) => {\n const f = field(l.field);\n const labels = f.type === \"date\" && l.by === void 0 ? l.labels.map((v) => typeof v === \"string\" ? new Date(v) : v) : l.labels;\n return {\n field: f,\n sort: l.sort,\n subtotal: l.subtotal,\n by: l.by,\n column: {\n codes: /* @__PURE__ */ new Uint32Array(0),\n labels,\n empty: l.empty,\n rank: typed(Uint32Array, l.rank),\n find: finder(labels, l.empty)\n }\n };\n });\n Object.assign(tree, {\n total: 0,\n size: packet.size,\n levels,\n parent: typed(Int32Array, packet.parent),\n depth: typed(Uint8Array, packet.depth),\n code: typed(Int32Array, packet.code),\n childStart: typed(Int32Array, packet.childStart),\n children: typed(Int32Array, packet.children),\n enter: typed(Int32Array, packet.enter),\n exit: typed(Int32Array, packet.exit),\n index: IntIndex.unpack(packet.index),\n stride: packet.stride\n });\n return tree;\n }\n /** The node's own value (its group label); `undefined` for the root. */\n label(node) {\n if (node <= 0) return void 0;\n return this.levels[this.depth[node] - 1].column.labels[this.code[node]];\n }\n /** The labels from the outermost group down to `node`. */\n path(node) {\n const path = [];\n for (let at = node; at > 0; at = this.parent[at]) path.push(this.label(at));\n return path.reverse();\n }\n /** The node with these labels, or -1. */\n find(path) {\n let node = 0;\n for (let d = 0; d < path.length; d++) {\n const level = this.levels[d];\n if (!level) return -1;\n const code = level.column.find(path[d]);\n if (code < 0) return -1;\n const id = this.index.get(node * this.stride + code);\n if (id < 0) return -1;\n node = id + 1;\n }\n return node;\n }\n /** Whether `node` has groups inside it. */\n isGroup(node) {\n return this.childStart[node + 1] > this.childStart[node];\n }\n /** Whether `node` is `ancestor` or inside it. */\n within(node, ancestor) {\n return this.enter[node] >= this.enter[ancestor] && this.enter[node] < this.exit[ancestor];\n }\n walk() {\n const { childStart, children, enter, exit } = this;\n const stack = new Int32Array(this.levels.length + 2);\n const next = new Int32Array(this.levels.length + 2);\n let top = 0;\n let clock = 0;\n stack[0] = 0;\n next[0] = childStart[0];\n enter[0] = clock++;\n while (top >= 0) {\n const node = stack[top];\n if (next[top] < childStart[node + 1]) {\n const child = children[next[top]++];\n top++;\n stack[top] = child;\n next[top] = childStart[child];\n enter[child] = clock++;\n } else {\n exit[node] = clock;\n top--;\n }\n }\n }\n };\n /** Grows a tree one record at a time: each record walks down its groups. */\n var PivotTreeBuilder = class {\n stride;\n parent = /* @__PURE__ */ new Int32Array(64);\n depth = /* @__PURE__ */ new Uint8Array(64);\n code = /* @__PURE__ */ new Int32Array(64);\n size = 1;\n index;\n /** `stride` is more than any group number, so (parent, group) makes a unique key. */\n constructor(stride) {\n this.stride = stride;\n this.parent[0] = -1;\n this.code[0] = -1;\n this.index = new IntIndex(256);\n }\n /** The child of `node` for group `code`, made when new. */\n child(node, code) {\n const index = this.index;\n const before = index.size;\n const child = index.add(node * this.stride + code) + 1;\n if (index.size !== before) {\n if (child >= this.parent.length) this.grow();\n this.parent[child] = node;\n this.depth[child] = this.depth[node] + 1;\n this.code[child] = code;\n this.size = child + 1;\n }\n return child;\n }\n grow() {\n const capacity = this.parent.length * 2;\n const parent = new Int32Array(capacity);\n parent.set(this.parent);\n const depth = new Uint8Array(capacity);\n depth.set(this.depth);\n const code = new Int32Array(capacity);\n code.set(this.code);\n this.parent = parent;\n this.depth = depth;\n this.code = code;\n }\n };\n // src/engine/compute.ts\n /** The ways of showing a value that write it as a percentage. */\n const PERCENT_SHOWS = /* @__PURE__ */ new Set([\n \"percentOfTotal\",\n \"percentOfRow\",\n \"percentOfColumn\",\n \"percentOfParent\",\n \"percentDifference\"\n ]);\n /** A `show` as an object. */\n const showOf = (show) => show === void 0 ? void 0 : typeof show === \"string\" ? { as: show } : show;\n /** A `mark` with its tone filled in. */\n const markOf = (mark) => mark === void 0 ? void 0 : typeof mark === \"string\" ? {\n type: mark,\n tone: \"neutral\"\n } : {\n type: mark.type,\n tone: mark.tone ?? \"neutral\"\n };\n /**\n * Reads a calculated value's formula and finds its fields by name (or by\n * label). Throws a `PivotFormulaError` when it is not well formed, names a\n * field that is not there, or sums up one that is not a number.\n */\n function checkPivotFormula(source, text) {\n const formula = parsePivotFormula(text);\n const byLabel = /* @__PURE__ */ new Map();\n for (const f of source.fields.values()) if (!byLabel.has(f.label)) byLabel.set(f.label, f.name);\n for (const term of formula.terms) {\n if (term.field === null) continue;\n const name = source.fields.has(term.field) ? term.field : byLabel.get(term.field);\n if (name === void 0) {\n const at = text.indexOf(term.field);\n throw new PivotFormulaError(\"unknownField\", Math.max(0, at), term.field);\n }\n if (term.aggregate !== \"count\" && source.field(name).type !== \"number\") throw new PivotFormulaError(\"notNumber\", Math.max(0, text.indexOf(term.field)), term.field);\n term.field = name;\n }\n return formula;\n }\n /** The fields of the config resolved against a source, without reading any records. */\n function resolveConfig(source, config, options = {}) {\n const level = (item) => {\n const spec = typeof item === \"string\" ? { field: item } : item;\n return {\n field: source.field(spec.field),\n sort: spec.sort ?? \"asc\",\n subtotal: spec.subtotal ?? true,\n by: spec.by\n };\n };\n const formulas = [];\n const values = (config.values ?? []).map((item) => {\n const spec = typeof item === \"string\" ? { field: item } : item;\n const show = showOf(spec.show);\n const mark = markOf(spec.mark);\n const ownFormat = show && (PERCENT_SHOWS.has(show.as) || show.as === \"rank\");\n if (spec.formula !== void 0) {\n formulas.push(checkPivotFormula(source, spec.formula));\n return {\n field: null,\n aggregate: \"formula\",\n label: spec.label,\n format: spec.format,\n integer: false,\n formula: spec.formula,\n ...show ? { show } : {},\n ...mark ? { mark } : {}\n };\n }\n formulas.push(null);\n const field = spec.field === void 0 ? null : source.field(spec.field);\n let name = spec.aggregate ?? (field?.type === \"number\" ? \"sum\" : \"count\");\n if (!field && name !== \"count\") name = \"count\";\n const aggregator = FAST_AGGREGATES.has(name) ? void 0 : options.aggregators?.[name] ?? BUILTIN_AGGREGATORS[name];\n if (!FAST_AGGREGATES.has(name) && !aggregator) throw new Error(`[@dou-ui/pivot] there is no aggregate named \"${name}\"`);\n return {\n field,\n aggregate: name,\n aggregator,\n label: spec.label,\n format: spec.format ?? (ownFormat ? void 0 : field?.format),\n integer: name === \"count\" || !!aggregator?.integer,\n ...show ? { show } : {},\n ...mark ? { mark } : {}\n };\n });\n if (values.length === 0) {\n values.push({\n field: null,\n aggregate: \"count\",\n integer: true,\n implicit: true\n });\n formulas.push(null);\n }\n return {\n rows: (config.rows ?? []).map(level),\n columns: (config.columns ?? []).map(level),\n values,\n formulas\n };\n }\n /**\n * Groups the records by the config's row and column fields and sums up\n * its values for every group, subtotal and total.\n *\n * Each record is placed once, in its innermost row group and innermost\n * column group; the values are summed per such pair, and every subtotal\n * is then merged from those pairs, never from the records again.\n */\n function computePivot(source, config, options = {}) {\n const src = source;\n if (Object.values(config.filters ?? {}).some((f) => f.top || f.between)) return computePivot(source, {\n ...config,\n filters: rankedFilters(src, config, options)\n }, options);\n const resolved = resolveConfig(src, config, options);\n const rowLevels = resolved.rows.map((l) => ({\n ...l,\n column: src.dimension(l.field.name, l.by)\n }));\n const colLevels = resolved.columns.map((l) => ({\n ...l,\n column: src.dimension(l.field.name, l.by)\n }));\n const n = source.length;\n const rowTree = new PivotTreeBuilder(stride(rowLevels));\n const colTree = new PivotTreeBuilder(stride(colLevels));\n const rowCodes = rowLevels.map((l) => l.column.codes);\n const colCodes = colLevels.map((l) => l.column.codes);\n const width = n * Math.max(1, colLevels.length) + 2;\n const filters = Object.entries(config.filters ?? {}).flatMap(([name, filter]) => {\n if (!filter.include && !filter.exclude) return [];\n const by = filter.by ?? [...resolved.rows, ...resolved.columns].find((l) => l.field.name === name)?.by;\n const column = src.dimension(name, by);\n const allow = new Uint8Array(column.labels.length).fill(filter.include ? 0 : 1);\n for (const value of filter.include ?? []) {\n const code = column.find(value);\n if (code >= 0) allow[code] = 1;\n }\n for (const value of filter.exclude ?? []) {\n const code = column.find(value);\n if (code >= 0) allow[code] = 0;\n }\n return [{\n codes: column.codes,\n allow\n }];\n });\n const slotOf = new Int32Array(n);\n const cells = new IntIndex(1024);\n let matched = 0;\n const rowCards = rowLevels.map((l) => l.column.labels.length);\n const colCards = colLevels.map((l) => l.column.labels.length);\n const rowSpace = rowCards.reduce((a, b) => a * b, 1);\n const colSpace = colCards.reduce((a, b) => a * b, 1);\n const skip = (i) => {\n for (let f = 0; f < filters.length; f++) {\n const filter = filters[f];\n if (filter.allow[filter.codes[i]] === 0) return true;\n }\n return false;\n };\n if (rowSpace * colSpace <= Number.MAX_SAFE_INTEGER) {\n const space = rowSpace * colSpace;\n const dense = space <= DENSE_PAIRS ? new Int32Array(space).fill(-1) : null;\n const index = dense ? null : new IntIndex(1024);\n let pairRow = /* @__PURE__ */ new Float64Array(1024);\n let pairCol = /* @__PURE__ */ new Float64Array(1024);\n let pairs = 0;\n for (let i = 0; i < n; i++) {\n if (filters.length && skip(i)) {\n slotOf[i] = -1;\n continue;\n }\n let rk = 0;\n for (let d = 0; d < rowCodes.length; d++) rk = rk * rowCards[d] + rowCodes[d][i];\n let ck = 0;\n for (let d = 0; d < colCodes.length; d++) ck = ck * colCards[d] + colCodes[d][i];\n const key = rk * colSpace + ck;\n let slot;\n if (dense) {\n slot = dense[key];\n if (slot < 0) slot = dense[key] = pairs;\n } else slot = index.add(key);\n if (slot === pairs) {\n if (pairs === pairRow.length) {\n const grownRow = new Float64Array(pairs * 2);\n grownRow.set(pairRow);\n pairRow = grownRow;\n const grownCol = new Float64Array(pairs * 2);\n grownCol.set(pairCol);\n pairCol = grownCol;\n }\n pairRow[pairs] = rk;\n pairCol[pairs] = ck;\n pairs++;\n }\n slotOf[i] = slot;\n matched++;\n }\n const rowLeaf = /* @__PURE__ */ new Map();\n const colLeaf = /* @__PURE__ */ new Map();\n const rowDigits = new Int32Array(rowCards.length);\n const colDigits = new Int32Array(colCards.length);\n for (let p = 0; p < pairs; p++) {\n const row = leaf(pairRow[p], rowLeaf, rowTree, rowCards, rowDigits);\n const col = leaf(pairCol[p], colLeaf, colTree, colCards, colDigits);\n cells.add(row * width + col);\n }\n } else for (let i = 0; i < n; i++) {\n if (filters.length && skip(i)) {\n slotOf[i] = -1;\n continue;\n }\n let row = 0;\n for (let d = 0; d < rowCodes.length; d++) row = rowTree.child(row, rowCodes[d][i]);\n let col = 0;\n for (let d = 0; d < colCodes.length; d++) col = colTree.child(col, colCodes[d][i]);\n slotOf[i] = cells.add(row * width + col);\n matched++;\n }\n const leaves = cells.size;\n const rowDepth = rowLevels.length;\n const colDepth = colLevels.length;\n const fan = (rowDepth + 1) * (colDepth + 1) - 1;\n const targets = new Int32Array(leaves * fan);\n const rowUp = new Int32Array(rowDepth + 1);\n const colUp = new Int32Array(colDepth + 1);\n const leafRow = new Int32Array(leaves);\n const leafCol = new Int32Array(leaves);\n for (let s = 0; s < leaves; s++) {\n const key = cells.keys[s];\n const row = Math.floor(key / width);\n const col = key - row * width;\n leafRow[s] = row;\n leafCol[s] = col;\n for (let d = 0, at = row; d <= rowDepth; d++, at = rowTree.parent[at]) rowUp[d] = at;\n for (let d = 0, at = col; d <= colDepth; d++, at = colTree.parent[at]) colUp[d] = at;\n let k = s * fan;\n for (let a = 0; a <= rowDepth; a++) for (let b = 0; b <= colDepth; b++) {\n if (a === 0 && b === 0) continue;\n targets[k++] = cells.add(rowUp[a] * width + colUp[b]);\n }\n }\n const size = cells.size;\n const sum = (value) => aggregate(src, value, slotOf, leaves, size, targets, fan);\n const data = resolved.values.map((value, i) => {\n const formula = resolved.formulas[i];\n return formula ? calculate(src, formula, sum, size) : sum(value);\n });\n return new PivotResult({\n rows: new PivotTree(rowTree, rowLevels),\n columns: new PivotTree(colTree, colLevels),\n values: resolved.values,\n matched,\n cells,\n width,\n data,\n slotOf,\n leafRow,\n leafCol\n });\n }\n /**\n * The filters with `top` and `between` turned into lists of the groups they\n * keep: each field's groups are summed up once on their own, over the\n * records that the `include` and `exclude` lists keep.\n */\n function rankedFilters(source, config, options) {\n const given = config.filters ?? {};\n const plain = {};\n for (const [name, f] of Object.entries(given)) if (f.include || f.exclude) plain[name] = {\n include: f.include,\n exclude: f.exclude,\n by: f.by\n };\n const out = { ...plain };\n const axes = [...config.rows ?? [], ...config.columns ?? []];\n for (const [name, f] of Object.entries(given)) {\n if (!f.top && !f.between) continue;\n const onAxis = axes.find((d) => typeof d === \"object\" && d.field === name);\n const by = f.by ?? (typeof onAxis === \"object\" ? onAxis.by : void 0);\n const index = f.top?.valueIndex ?? f.between?.valueIndex ?? 0;\n const value = config.values?.[index];\n const plainValue = value === void 0 ? [] : [typeof value === \"string\" ? value : {\n ...value,\n show: void 0\n }];\n const totals = computePivot(source, {\n rows: [{\n field: name,\n by,\n subtotal: false\n }],\n values: plainValue,\n filters: plain\n }, options);\n const tree = totals.rows;\n let groups = [];\n for (let k = tree.childStart[0]; k < tree.childStart[1]; k++) {\n const node = tree.children[k];\n groups.push({\n label: tree.label(node),\n total: totals.get(node, 0)\n });\n }\n if (f.between) {\n const { min = -Infinity, max = Infinity } = f.between;\n groups = groups.filter((g) => g.total >= min && g.total <= max);\n }\n if (f.top) {\n const sign = f.top.order === \"asc\" ? 1 : -1;\n const ranked = groups.filter((g) => g.total === g.total);\n ranked.sort((a, b) => sign * (a.total - b.total));\n groups = ranked.slice(0, Math.max(0, f.top.count));\n }\n out[name] = {\n include: groups.map((g) => g.label),\n by\n };\n }\n return out;\n }\n /** A calculated value: each term summed up as usual, then the formula per cell. */\n function calculate(source, formula, sum, size) {\n const columns = formula.terms.map((term) => sum({\n field: term.field === null ? null : source.field(term.field),\n aggregate: term.aggregate,\n integer: term.aggregate === \"count\"\n }));\n const out = new Float64Array(size);\n const values = new Float64Array(columns.length);\n for (let s = 0; s < size; s++) {\n for (let t = 0; t < columns.length; t++) values[t] = columns[t][s];\n out[s] = formula.evaluate(values);\n }\n return out;\n }\n /** More than any group number of the axis, so (parent, group) makes a unique key. */\n const stride = (levels) => Math.max(1, ...levels.map((l) => l.column.labels.length)) + 1;\n /** The innermost group whose group numbers are the digits of `key`, made when new. */\n function leaf(key, memo, tree, cards, digits) {\n let node = memo.get(key);\n if (node !== void 0) return node;\n for (let d = cards.length - 1, rest = key; d >= 0; d--) {\n const card = cards[d];\n const digit = rest % card;\n digits[d] = digit;\n rest = (rest - digit) / card;\n }\n node = 0;\n for (let d = 0; d < cards.length; d++) node = tree.child(node, digits[d]);\n memo.set(key, node);\n return node;\n }\n /** Pairs of innermost groups up to this many are found in a plain array instead of a hash. */\n const DENSE_PAIRS = 1 << 22;\n /** One value for every cell: summed per innermost pair, then merged up into the totals. */\n function aggregate(source, value, slotOf, leaves, size, targets, fan) {\n const n = slotOf.length;\n const out = new Float64Array(size);\n const kind = value.aggregate;\n const roll = (fn) => {\n for (let s = 0; s < leaves; s++) {\n const base = s * fan;\n for (let k = 0; k < fan; k++) fn(targets[base + k], s);\n }\n };\n if (kind === \"count\") {\n if (value.field) {\n const { codes, empty } = source.dimension(value.field.name);\n for (let i = 0; i < n; i++) {\n const s = slotOf[i];\n if (s >= 0 && codes[i] !== empty) out[s]++;\n }\n } else for (let i = 0; i < n; i++) {\n const s = slotOf[i];\n if (s >= 0) out[s]++;\n }\n roll((t, s) => out[t] += out[s]);\n return out;\n }\n if (FAST_AGGREGATES.has(kind)) {\n const values = source.measure(value.field.name);\n if (kind === \"min\" || kind === \"max\") {\n out.fill(NaN);\n const max = kind === \"max\";\n for (let i = 0; i < n; i++) {\n const s = slotOf[i];\n const v = values[i];\n if (s < 0 || v !== v) continue;\n const at = out[s];\n if (max ? !(at >= v) : !(at <= v)) out[s] = v;\n }\n roll((t, s) => {\n const v = out[s];\n if (v !== v) return;\n const at = out[t];\n if (max ? !(at >= v) : !(at <= v)) out[t] = v;\n });\n return out;\n }\n const count = new Float64Array(size);\n for (let i = 0; i < n; i++) {\n const s = slotOf[i];\n const v = values[i];\n if (s < 0 || v !== v) continue;\n out[s] += v;\n count[s]++;\n }\n roll((t, s) => {\n out[t] += out[s];\n count[t] += count[s];\n });\n for (let s = 0; s < size; s++) {\n const c = count[s];\n out[s] = c === 0 ? NaN : kind === \"avg\" ? out[s] / c : out[s];\n }\n return out;\n }\n const aggregator = value.aggregator;\n const field = value.field;\n const states = Array.from({ length: size });\n const { init, merge } = aggregator;\n const add = aggregator.add;\n if (aggregator.input === \"number\") {\n const values = source.measure(field.name);\n for (let i = 0; i < n; i++) {\n const s = slotOf[i];\n const v = values[i];\n if (s < 0 || v !== v) continue;\n states[s] = add(states[s] ?? init(), v);\n }\n } else {\n const { codes, labels, empty } = source.dimension(field.name);\n const byCode = aggregator === distinctAggregator;\n for (let i = 0; i < n; i++) {\n const s = slotOf[i];\n const code = codes[i];\n if (s < 0 || code === empty) continue;\n states[s] = add(states[s] ?? init(), byCode ? code : labels[code]);\n }\n }\n roll((t, s) => {\n const from = states[s];\n if (from !== void 0) states[t] = merge(states[t] ?? init(), from);\n });\n const { result } = aggregator;\n for (let s = 0; s < size; s++) {\n const state = states[s];\n out[s] = state === void 0 ? NaN : result(state) ?? NaN;\n }\n return out;\n }\n const plainArray = (a) => Array.from(a);\n /** A packet with plain arrays in place of typed ones, ready for JSON. */\n function toPlain(packet) {\n const tree = (t) => ({\n ...t,\n parent: plainArray(t.parent),\n depth: plainArray(t.depth),\n code: plainArray(t.code),\n childStart: plainArray(t.childStart),\n children: plainArray(t.children),\n enter: plainArray(t.enter),\n exit: plainArray(t.exit),\n index: index(t.index),\n levels: t.levels.map((l) => ({\n ...l,\n rank: plainArray(l.rank)\n }))\n });\n const index = (i) => ({\n size: i.size,\n table: plainArray(i.table),\n ids: plainArray(i.ids),\n keys: plainArray(i.keys).slice(0, i.size)\n });\n return {\n ...packet,\n rows: tree(packet.rows),\n columns: tree(packet.columns),\n cells: index(packet.cells),\n data: packet.data.map((d) => Array.from(d, (v) => v === v ? v : null)),\n slotOf: plainArray(packet.slotOf),\n leafRow: plainArray(packet.leafRow),\n leafCol: plainArray(packet.leafCol)\n };\n }\n /** The groups on both axes and every cell's values. */\n var PivotResult = class PivotResult {\n rows;\n columns;\n values;\n /** How many records passed the filters. */\n matched;\n cells;\n width;\n data;\n /** Each record's innermost cell, -1 when filtered out; and each such cell's groups. */\n slotOf;\n leafRow;\n leafCol;\n constructor(init) {\n this.rows = init.rows;\n this.columns = init.columns;\n this.values = init.values;\n this.matched = init.matched;\n this.cells = init.cells;\n this.width = init.width;\n this.data = init.data;\n this.slotOf = init.slotOf;\n this.leafRow = init.leafRow;\n this.leafCol = init.leafCol;\n }\n /**\n * The result as data, to send from a worker or a server. With `plain`,\n * plain arrays for `JSON.stringify` (typed arrays do not survive it);\n * empty cells go as `null`.\n */\n pack({ plain = false } = {}) {\n const packet = this.packTyped();\n return plain ? toPlain(packet) : packet;\n }\n packTyped() {\n return {\n rows: this.rows.pack(),\n columns: this.columns.pack(),\n values: this.values.map((v) => ({\n field: v.field?.name ?? null,\n aggregate: v.aggregate,\n integer: v.integer,\n ...v.implicit ? { implicit: true } : {},\n ...v.label === void 0 ? {} : { label: v.label },\n ...v.formula === void 0 ? {} : { formula: v.formula },\n ...v.show ? { show: v.show } : {},\n ...v.mark ? { mark: v.mark } : {}\n })),\n matched: this.matched,\n cells: this.cells.pack(),\n width: this.width,\n data: this.data,\n slotOf: this.slotOf,\n leafRow: this.leafRow,\n leafCol: this.leafCol\n };\n }\n /** The typed arrays in a packet, to move them instead of copying (`postMessage`'s transfer list). */\n static transferables(packet) {\n const out = /* @__PURE__ */ new Set();\n const add = (a) => {\n if (ArrayBuffer.isView(a) && a.buffer instanceof ArrayBuffer) out.add(a.buffer);\n };\n for (const tree of [packet.rows, packet.columns]) {\n for (const a of [\n tree.parent,\n tree.depth,\n tree.code,\n tree.childStart,\n tree.children\n ]) add(a);\n for (const a of [\n tree.enter,\n tree.exit,\n tree.index.table,\n tree.index.ids,\n tree.index.keys\n ]) add(a);\n }\n for (const a of [\n packet.cells.table,\n packet.cells.ids,\n packet.cells.keys\n ]) add(a);\n for (const a of [\n ...packet.data,\n packet.slotOf,\n packet.leafRow,\n packet.leafCol\n ]) add(a);\n return [...out];\n }\n /**\n * A result made from `pack()`'s output. Fields are described by\n * `source` (no record is read), formats come from `config`.\n */\n static unpack(packet, source, config) {\n const field = (name) => source.field(name);\n const given = config?.values ?? [];\n return new PivotResult({\n rows: PivotTree.unpack(packet.rows, field),\n columns: PivotTree.unpack(packet.columns, field),\n values: packet.values.map((v, i) => {\n const f = v.field === null ? null : field(v.field);\n const spec = v.implicit ? void 0 : given[i];\n const format = typeof spec === \"object\" ? spec.format : void 0;\n const own = v.formula !== void 0 || v.show && (PERCENT_SHOWS.has(v.show.as) || v.show.as === \"rank\");\n return {\n ...v,\n field: f,\n format: format ?? (own ? void 0 : f?.format)\n };\n }),\n matched: packet.matched,\n cells: IntIndex.unpack(packet.cells),\n width: packet.width,\n data: packet.data.map((d) => d instanceof Float64Array ? d : Float64Array.from(d, (v) => v ?? NaN)),\n slotOf: typed(Int32Array, packet.slotOf),\n leafRow: typed(Int32Array, packet.leafRow),\n leafCol: typed(Int32Array, packet.leafCol)\n });\n }\n showing = /* @__PURE__ */ new Map();\n ranges = /* @__PURE__ */ new Map();\n shownBy(valueIndex, show) {\n const spec = show === void 0 ? this.values[valueIndex]?.show : show;\n const key = `${valueIndex}|${spec ? `${spec.as}|${spec.axis ?? \"\"}` : \"\"}`;\n let shown = this.showing.get(key);\n if (shown === void 0) {\n shown = spec ? new PivotShown(resolveShow(spec, this.rows, this.columns), this.rows, this.columns, (r, c) => this.get(r, c, valueIndex)) : null;\n this.showing.set(key, shown);\n }\n return shown;\n }\n /**\n * How a value is shown, its `show` with the axis worked out; null when as\n * it is. `show` stands in for the value's own (`null` for none).\n */\n show(valueIndex = 0, show) {\n return this.shownBy(valueIndex, show)?.show ?? null;\n }\n /**\n * The value as it is shown: a share, a difference, a running total or a\n * rank when the value has a `show` (or one is given), the summed-up value\n * otherwise. Nothing is summed up again.\n */\n shown(row, column, valueIndex = 0, show) {\n const shown = this.shownBy(valueIndex, show);\n return shown ? shown.value(row, column) : this.get(row, column, valueIndex);\n }\n /**\n * The smallest and largest shown values of the innermost cells (those\n * under no subtotal or total), which marks are drawn against.\n */\n range(valueIndex = 0, show) {\n const spec = show === void 0 ? this.values[valueIndex]?.show : show;\n const key = `${valueIndex}|${spec ? `${spec.as}|${spec.axis ?? \"\"}` : \"\"}`;\n let range = this.ranges.get(key);\n if (!range) {\n const { rows, columns, cells, width } = this;\n const rowDepth = rows.levels.length;\n const colDepth = columns.levels.length;\n let min = Infinity;\n let max = -Infinity;\n for (let s = 0; s < cells.size; s++) {\n const cell = cells.keys[s];\n const row = Math.floor(cell / width);\n const col = cell - row * width;\n if (rows.depth[row] !== rowDepth || columns.depth[col] !== colDepth) continue;\n const v = this.shown(row, col, valueIndex, spec ?? null);\n if (v !== v) continue;\n if (v < min) min = v;\n if (v > max) max = v;\n }\n range = min > max ? {\n min: NaN,\n max: NaN\n } : {\n min,\n max\n };\n this.ranges.set(key, range);\n }\n return range;\n }\n /**\n * The value at `valueIndex` (its place in `values`) of a row group and a\n * column group; `NaN` when no record falls in both. The trees' `total`\n * is the grand total.\n */\n get(row, column, valueIndex = 0) {\n const slot = this.cells.get(row * this.width + column);\n return slot < 0 ? NaN : this.data[valueIndex][slot];\n }\n /** The indexes of the records in a row group and a column group, for showing the details. */\n indices(row, column) {\n const { slotOf, leafRow, leafCol, rows, columns } = this;\n const inside = new Uint8Array(leafRow.length);\n for (let s = 0; s < leafRow.length; s++) inside[s] = rows.within(leafRow[s], row) && columns.within(leafCol[s], column) ? 1 : 0;\n const out = [];\n for (let i = 0; i < slotOf.length; i++) {\n const s = slotOf[i];\n if (s >= 0 && inside[s]) out.push(i);\n }\n return out;\n }\n /**\n * The children of every row group, reordered by a column's values (or as\n * they are when `sort` is empty). Nothing is summed up again.\n */\n rowOrder(sort) {\n const { rows } = this;\n if (!sort || !this.data[sort.valueIndex]) return rows.children;\n const column = this.columns.find(sort.column ?? []);\n if (column < 0) return rows.children;\n const children = rows.children.slice();\n const keys = new Float64Array(rows.size);\n for (let node = 0; node < rows.size; node++) keys[node] = this.get(node, column, sort.valueIndex);\n const sign = sort.order === \"desc\" ? -1 : 1;\n const place = new Int32Array(rows.size);\n rows.children.forEach((node, at) => place[node] = at);\n for (let node = 0; node < rows.size; node++) {\n const from = rows.childStart[node];\n const to = rows.childStart[node + 1];\n if (to - from < 2) continue;\n children.subarray(from, to).sort((a, b) => {\n const ka = keys[a];\n const kb = keys[b];\n if (ka !== ka || kb !== kb) return ka !== ka ? kb !== kb ? place[a] - place[b] : 1 : -1;\n return ka === kb ? place[a] - place[b] : sign * (ka - kb);\n });\n }\n return children;\n }\n };\n // src/engine/engine.ts\n /**\n * The fields a config reads, and how: grouped (`dimensions`: on an axis,\n * filtered, counted) or as numbers (`measures`: summed and the like).\n * No record is read.\n */\n function neededColumns(source, config, aggregators = {}) {\n const src = source;\n const dimensions = /* @__PURE__ */ new Set();\n const measures = /* @__PURE__ */ new Set();\n for (const d of [...config.rows ?? [], ...config.columns ?? []]) dimensions.add(typeof d === \"string\" ? d : d.field);\n for (const name of Object.keys(config.filters ?? {})) {\n const f = config.filters[name];\n if (f.include || f.exclude || f.top || f.between) dimensions.add(name);\n }\n for (const v of config.values ?? []) {\n const spec = typeof v === \"string\" ? { field: v } : v;\n if (spec.formula !== void 0) {\n let formula;\n try {\n formula = checkPivotFormula(src, spec.formula);\n } catch {\n continue;\n }\n for (const term of formula.terms) {\n if (term.field === null) continue;\n if (term.aggregate === \"count\") dimensions.add(term.field);\n else measures.add(term.field);\n }\n continue;\n }\n if (spec.field === void 0) continue;\n const name = spec.aggregate ?? (src.field(spec.field).type === \"number\" ? \"sum\" : \"count\");\n if ((name === \"count\" ? \"value\" : aggregators[name]?.input ?? BUILTIN_AGGREGATORS[name]?.input ?? \"number\") === \"value\") dimensions.add(spec.field);\n else measures.add(spec.field);\n }\n return {\n dimensions: [...dimensions],\n measures: [...measures]\n };\n }\n // src/worker/serve.ts\n /**\n * Sums up for the page in a worker. Call it in your own worker file when\n * configs use custom aggregators; `DouPivot`'s built-in worker calls it\n * with none.\n *\n * ```ts\n * // pivot.worker.ts\n * import { servePivot } from '@dou-ui/pivot/worker'\n * servePivot({ aggregators: [weightedAverage] })\n * ```\n */\n function servePivot(options = {}, scope = globalThis) {\n const aggregators = {};\n for (const a of options.aggregators ?? []) aggregators[a.name] = a;\n let source = null;\n let sourceId = -1;\n const have = {\n dimensions: /* @__PURE__ */ new Set(),\n measures: /* @__PURE__ */ new Set()\n };\n let pending = null;\n let scheduled = false;\n const send = (message, transfer) => scope.postMessage(message, transfer);\n const run = () => {\n scheduled = false;\n const job = pending;\n pending = null;\n if (!job || !source || job.source !== sourceId) return;\n const s = source;\n const need = neededColumns(s, job.config, aggregators);\n const dimensions = need.dimensions.filter((n) => !have.dimensions.has(n));\n const measures = need.measures.filter((n) => !have.measures.has(n));\n if (dimensions.length || measures.length) {\n send({\n type: \"need\",\n id: job.id,\n dimensions,\n measures\n });\n return;\n }\n try {\n const packet = computePivot(s, job.config, { aggregators }).pack();\n send({\n type: \"result\",\n id: job.id,\n packet\n }, PivotResult.transferables(packet));\n } catch (error) {\n send({\n type: \"error\",\n id: job.id,\n message: error.message\n });\n }\n };\n scope.addEventListener(\"message\", (event) => {\n const m = event.data;\n if (m.type === \"source\") {\n sourceId = m.source;\n source = new PivotSource({}, {\n fields: m.fields,\n locale: m.locale,\n length: m.length\n });\n have.dimensions.clear();\n have.measures.clear();\n return;\n }\n if (m.source !== sourceId || !source) return;\n if (m.type === \"fields\") for (const f of m.fields) source.define(f);\n else if (m.type === \"dimension\") {\n source.provide(m.name, {\n codes: m.codes,\n labels: m.labels,\n empty: m.empty\n });\n have.dimensions.add(m.name);\n } else if (m.type === \"measure\") {\n source.provideMeasure(m.name, m.values);\n have.measures.add(m.name);\n } else {\n if (pending) send({\n type: \"skipped\",\n id: pending.id\n });\n pending = m;\n if (!scheduled) {\n scheduled = true;\n setTimeout(run);\n }\n }\n });\n }\n // src/worker/default.ts\n servePivot();\n})();\n\n//# sourceMappingURL=default-PXde2wzm.js.map";
3
+ var blob = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent], { type: "text/javascript;charset=utf-8" });
4
+ function WorkerWrapper(options) {
5
+ let objURL;
6
+ try {
7
+ objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob);
8
+ if (!objURL) throw "";
9
+ const worker = new Worker(objURL, { name: options?.name });
10
+ worker.addEventListener("error", () => {
11
+ (self.URL || self.webkitURL).revokeObjectURL(objURL);
12
+ });
13
+ return worker;
14
+ } catch (e) {
15
+ return new Worker("data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent), { name: options?.name });
16
+ }
17
+ }
18
+ export { WorkerWrapper as default };
19
+
20
+ //# sourceMappingURL=default.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default.js","names":[],"sources":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import { createPivotWorker } from "./client.js";
2
+ import { servePivot } from "./serve.js";
3
+ export { createPivotWorker, servePivot };
@@ -0,0 +1,101 @@
1
+ import { PivotSource } from "../engine/source.js";
2
+ import { PivotResult, computePivot } from "../engine/compute.js";
3
+ import { neededColumns } from "../engine/engine.js";
4
+ // src/worker/serve.ts
5
+ /**
6
+ * Sums up for the page in a worker. Call it in your own worker file when
7
+ * configs use custom aggregators; `DouPivot`'s built-in worker calls it
8
+ * with none.
9
+ *
10
+ * ```ts
11
+ * // pivot.worker.ts
12
+ * import { servePivot } from '@dou-ui/pivot/worker'
13
+ * servePivot({ aggregators: [weightedAverage] })
14
+ * ```
15
+ */
16
+ function servePivot(options = {}, scope = globalThis) {
17
+ const aggregators = {};
18
+ for (const a of options.aggregators ?? []) aggregators[a.name] = a;
19
+ let source = null;
20
+ let sourceId = -1;
21
+ const have = {
22
+ dimensions: /* @__PURE__ */ new Set(),
23
+ measures: /* @__PURE__ */ new Set()
24
+ };
25
+ let pending = null;
26
+ let scheduled = false;
27
+ const send = (message, transfer) => scope.postMessage(message, transfer);
28
+ const run = () => {
29
+ scheduled = false;
30
+ const job = pending;
31
+ pending = null;
32
+ if (!job || !source || job.source !== sourceId) return;
33
+ const s = source;
34
+ const need = neededColumns(s, job.config, aggregators);
35
+ const dimensions = need.dimensions.filter((n) => !have.dimensions.has(n));
36
+ const measures = need.measures.filter((n) => !have.measures.has(n));
37
+ if (dimensions.length || measures.length) {
38
+ send({
39
+ type: "need",
40
+ id: job.id,
41
+ dimensions,
42
+ measures
43
+ });
44
+ return;
45
+ }
46
+ try {
47
+ const packet = computePivot(s, job.config, { aggregators }).pack();
48
+ send({
49
+ type: "result",
50
+ id: job.id,
51
+ packet
52
+ }, PivotResult.transferables(packet));
53
+ } catch (error) {
54
+ send({
55
+ type: "error",
56
+ id: job.id,
57
+ message: error.message
58
+ });
59
+ }
60
+ };
61
+ scope.addEventListener("message", (event) => {
62
+ const m = event.data;
63
+ if (m.type === "source") {
64
+ sourceId = m.source;
65
+ source = new PivotSource({}, {
66
+ fields: m.fields,
67
+ locale: m.locale,
68
+ length: m.length
69
+ });
70
+ have.dimensions.clear();
71
+ have.measures.clear();
72
+ return;
73
+ }
74
+ if (m.source !== sourceId || !source) return;
75
+ if (m.type === "fields") for (const f of m.fields) source.define(f);
76
+ else if (m.type === "dimension") {
77
+ source.provide(m.name, {
78
+ codes: m.codes,
79
+ labels: m.labels,
80
+ empty: m.empty
81
+ });
82
+ have.dimensions.add(m.name);
83
+ } else if (m.type === "measure") {
84
+ source.provideMeasure(m.name, m.values);
85
+ have.measures.add(m.name);
86
+ } else {
87
+ if (pending) send({
88
+ type: "skipped",
89
+ id: pending.id
90
+ });
91
+ pending = m;
92
+ if (!scheduled) {
93
+ scheduled = true;
94
+ setTimeout(run);
95
+ }
96
+ }
97
+ });
98
+ }
99
+ export { servePivot };
100
+
101
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","names":[],"sources":["../../src/worker/serve.ts"],"sourcesContent":["import type { PivotAggregator } from '../engine/aggregate'\nimport { computePivot, PivotResult } from '../engine/compute'\nimport { neededColumns } from '../engine/engine'\nimport { PivotSource } from '../engine/source'\nimport type { PivotFromWorker, PivotToWorker } from './protocol'\n\nexport interface ServePivotOptions {\n /** Custom aggregators (`defineAggregator`) the configs may name; functions cannot be sent, so they live here. */\n aggregators?: readonly PivotAggregator[]\n}\n\n/** The worker's side of the messages: anything with `postMessage` and `onmessage`. */\nexport interface PivotWorkerScope {\n postMessage(message: unknown, transfer?: Transferable[]): void\n addEventListener(type: 'message', listener: (event: MessageEvent) => void): void\n}\n\n/**\n * Sums up for the page in a worker. Call it in your own worker file when\n * configs use custom aggregators; `DouPivot`'s built-in worker calls it\n * with none.\n *\n * ```ts\n * // pivot.worker.ts\n * import { servePivot } from '@dou-ui/pivot/worker'\n * servePivot({ aggregators: [weightedAverage] })\n * ```\n */\nexport function servePivot(\n options: ServePivotOptions = {},\n scope: PivotWorkerScope = globalThis as unknown as PivotWorkerScope,\n) {\n const aggregators: Record<string, PivotAggregator> = {}\n for (const a of options.aggregators ?? []) aggregators[a.name] = a\n let source: PivotSource<unknown> | null = null\n let sourceId = -1\n const have = { dimensions: new Set<string>(), measures: new Set<string>() }\n // only the newest compute is worth doing: the ones that queued up behind it are skipped\n let pending: Extract<PivotToWorker, { type: 'compute' }> | null = null\n let scheduled = false\n const send = (message: PivotFromWorker, transfer?: Transferable[]) =>\n scope.postMessage(message, transfer)\n\n const run = () => {\n scheduled = false\n const job = pending\n pending = null\n if (!job || !source || job.source !== sourceId) return\n const s = source\n const need = neededColumns(s, job.config, aggregators)\n const dimensions = need.dimensions.filter((n) => !have.dimensions.has(n))\n const measures = need.measures.filter((n) => !have.measures.has(n))\n if (dimensions.length || measures.length) {\n send({ type: 'need', id: job.id, dimensions, measures })\n return\n }\n try {\n const packet = computePivot(s, job.config, { aggregators }).pack()\n send({ type: 'result', id: job.id, packet }, PivotResult.transferables(packet))\n } catch (error) {\n send({ type: 'error', id: job.id, message: (error as Error).message })\n }\n }\n\n scope.addEventListener('message', (event) => {\n const m = event.data as PivotToWorker\n if (m.type === 'source') {\n sourceId = m.source\n source = new PivotSource({}, { fields: m.fields, locale: m.locale, length: m.length })\n have.dimensions.clear()\n have.measures.clear()\n return\n }\n if (m.source !== sourceId || !source) return\n if (m.type === 'fields') {\n for (const f of m.fields) source.define(f)\n } else if (m.type === 'dimension') {\n source.provide(m.name, { codes: m.codes, labels: m.labels, empty: m.empty })\n have.dimensions.add(m.name)\n } else if (m.type === 'measure') {\n source.provideMeasure(m.name, m.values)\n have.measures.add(m.name)\n } else {\n if (pending) send({ type: 'skipped', id: pending.id })\n pending = m\n if (!scheduled) {\n scheduled = true\n setTimeout(run)\n }\n }\n })\n}\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,SAAgB,WACd,UAA6B,CAAC,GAC9B,QAA0B,YAC1B;CACA,MAAM,cAA+C,CAAC;CACtD,KAAK,MAAM,KAAK,QAAQ,eAAe,CAAC,GAAG,YAAY,EAAE,QAAQ;CACjE,IAAI,SAAsC;CAC1C,IAAI,WAAW;CACf,MAAM,OAAO;EAAE,4BAAY,IAAI,IAAY;EAAG,0BAAU,IAAI,IAAY;CAAE;CAE1E,IAAI,UAA8D;CAClE,IAAI,YAAY;CAChB,MAAM,QAAQ,SAA0B,aACtC,MAAM,YAAY,SAAS,QAAQ;CAErC,MAAM,YAAY;EAChB,YAAY;EACZ,MAAM,MAAM;EACZ,UAAU;EACV,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,WAAW,UAAU;EAChD,MAAM,IAAI;EACV,MAAM,OAAO,cAAc,GAAG,IAAI,QAAQ,WAAW;EACrD,MAAM,aAAa,KAAK,WAAW,QAAQ,MAAM,CAAC,KAAK,WAAW,IAAI,CAAC,CAAC;EACxE,MAAM,WAAW,KAAK,SAAS,QAAQ,MAAM,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC;EAClE,IAAI,WAAW,UAAU,SAAS,QAAQ;GACxC,KAAK;IAAE,MAAM;IAAQ,IAAI,IAAI;IAAI;IAAY;GAAS,CAAC;GACvD;EACF;EACA,IAAI;GACF,MAAM,SAAS,aAAa,GAAG,IAAI,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK;GACjE,KAAK;IAAE,MAAM;IAAU,IAAI,IAAI;IAAI;GAAO,GAAG,YAAY,cAAc,MAAM,CAAC;EAChF,SAAS,OAAO;GACd,KAAK;IAAE,MAAM;IAAS,IAAI,IAAI;IAAI,SAAU,MAAgB;GAAQ,CAAC;EACvE;CACF;CAEA,MAAM,iBAAiB,YAAY,UAAU;EAC3C,MAAM,IAAI,MAAM;EAChB,IAAI,EAAE,SAAS,UAAU;GACvB,WAAW,EAAE;GACb,SAAS,IAAI,YAAY,CAAC,GAAG;IAAE,QAAQ,EAAE;IAAQ,QAAQ,EAAE;IAAQ,QAAQ,EAAE;GAAO,CAAC;GACrF,KAAK,WAAW,MAAM;GACtB,KAAK,SAAS,MAAM;GACpB;EACF;EACA,IAAI,EAAE,WAAW,YAAY,CAAC,QAAQ;EACtC,IAAI,EAAE,SAAS,UACb,KAAK,MAAM,KAAK,EAAE,QAAQ,OAAO,OAAO,CAAC;OACpC,IAAI,EAAE,SAAS,aAAa;GACjC,OAAO,QAAQ,EAAE,MAAM;IAAE,OAAO,EAAE;IAAO,QAAQ,EAAE;IAAQ,OAAO,EAAE;GAAM,CAAC;GAC3E,KAAK,WAAW,IAAI,EAAE,IAAI;EAC5B,OAAO,IAAI,EAAE,SAAS,WAAW;GAC/B,OAAO,eAAe,EAAE,MAAM,EAAE,MAAM;GACtC,KAAK,SAAS,IAAI,EAAE,IAAI;EAC1B,OAAO;GACL,IAAI,SAAS,KAAK;IAAE,MAAM;IAAW,IAAI,QAAQ;GAAG,CAAC;GACrD,UAAU;GACV,IAAI,CAAC,WAAW;IACd,YAAY;IACZ,WAAW,GAAG;GAChB;EACF;CACF,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,66 @@
1
1
  {
2
2
  "name": "@dou-ui/pivot",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "DouUI 的数据透视表:按行列维度分组汇总明细数据",
5
+ "keywords": [
6
+ "vue",
7
+ "vue3",
8
+ "pivot",
9
+ "pivot-table",
10
+ "crosstab",
11
+ "aggregation"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "daidr <daidr@daidr.me>",
15
+ "homepage": "https://github.com/daidr/dou-ui#readme",
16
+ "bugs": "https://github.com/daidr/dou-ui/issues",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/daidr/dou-ui.git",
20
+ "directory": "packages/pivot"
21
+ },
22
+ "type": "module",
23
+ "sideEffects": [
24
+ "*.css",
25
+ "./dist/style.js"
26
+ ],
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/types/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/types/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./engine": {
36
+ "types": "./dist/types/engine/index.d.ts",
37
+ "import": "./dist/engine/index.js"
38
+ },
39
+ "./worker": {
40
+ "types": "./dist/types/worker/index.d.ts",
41
+ "import": "./dist/worker/index.js"
42
+ },
43
+ "./style": {
44
+ "types": "./dist/style.d.ts",
45
+ "import": "./dist/style.js"
46
+ },
47
+ "./style.css": "./dist/style.css",
48
+ "./package.json": "./package.json"
49
+ },
50
+ "files": [
51
+ "dist"
52
+ ],
53
+ "scripts": {
54
+ "build": "vite build && vue-tsc -p tsconfig.build.json && bun scripts/build-style.ts"
55
+ },
56
+ "peerDependencies": {
57
+ "dou-ui": "0.1.0-alpha.0",
58
+ "vue": "^3.5.0"
59
+ },
60
+ "dependencies": {
61
+ "@dou-ui/icons": "0.1.0-alpha.0"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ }
66
+ }