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

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,881 @@
1
+ import { BUILTIN_AGGREGATORS } from "../engine/aggregate.js";
2
+ import { DATE_PERIODS, groupKey } from "../engine/group.js";
3
+ import { resolveConfig } from "../engine/compute.js";
4
+ import { format, usePivotMessages } from "../locale.js";
5
+ import { valueLabel } from "../core/labels.js";
6
+ import { usePivotSource } from "../core/source.js";
7
+ import filter_list_default from "./filter-list.js";
8
+ import formula_dialog_default from "./formula-dialog.js";
9
+ import top_dialog_default from "./top-dialog.js";
10
+ import { AREAS, areasOf, configOf, isFiltering, moveItem, stepsFor, withAggregate, withDimension, withFilter, withRank, withValue } from "./model.js";
11
+ import { DouButton, DouCheckbox, DouDrawer, DouDropdown, DouDropdownItem, DouDropdownRadioGroup, DouDropdownSub, DouInput, DouPopover, DouSortable, useBreakpoints } from "dou-ui";
12
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, mergeProps, nextTick, normalizeClass, onMounted, openBlock, ref, renderList, resolveDynamicComponent, toDisplayString, unref, useId, useModel, withCtx } from "vue";
13
+ import { CalendarIcon, ChevronDownIcon, FilterIcon, HashIcon, LayoutThreeColumnsIcon, LayoutThreeRowsIcon, PlusIcon, SearchIcon, SigmaIcon, TablePivotIcon, ToggleLeftIcon, TypeIcon } from "@dou-ui/icons";
14
+ // src/fields/fields.vue?vue&type=script&setup=true&lang.ts
15
+ var _hoisted_1 = ["role", "aria-label"];
16
+ var _hoisted_2 = ["aria-label"];
17
+ var _hoisted_3 = { class: "dou-pivot-fields__field" };
18
+ var _hoisted_4 = { class: "dou-pivot-fields__name" };
19
+ var _hoisted_5 = { class: "dou-pivot-fields__none" };
20
+ var _hoisted_6 = { class: "dou-pivot-fields__areas" };
21
+ var _hoisted_7 = ["aria-label"];
22
+ var _hoisted_8 = { class: "dou-pivot-fields__area-title" };
23
+ var _hoisted_9 = ["title"];
24
+ var _hoisted_10 = { class: "dou-pivot-fields__chip-name" };
25
+ var _hoisted_11 = {
26
+ key: 0,
27
+ class: "dou-pivot-fields__chip-group"
28
+ };
29
+ var _hoisted_12 = ["aria-label"];
30
+ var _hoisted_13 = ["aria-label"];
31
+ var _hoisted_14 = { class: "dou-pivot-fields__drop" };
32
+ var fields_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
33
+ name: "DouPivotFields",
34
+ __name: "fields",
35
+ props: /*@__PURE__*/ mergeModels({
36
+ data: {},
37
+ fields: {},
38
+ config: {},
39
+ aggregators: {},
40
+ drawer: {
41
+ type: [Boolean, String],
42
+ default: "auto"
43
+ },
44
+ messages: {}
45
+ }, {
46
+ "config": { default: () => ({}) },
47
+ "configModifiers": {}
48
+ }),
49
+ emits: ["update:config"],
50
+ setup(__props) {
51
+ const props = __props;
52
+ const config = useModel(__props, "config");
53
+ const messages = usePivotMessages(() => props.messages);
54
+ const source = usePivotSource(() => props.data, () => props.fields);
55
+ const group = `dou-pivot-fields-${useId()}`;
56
+ const TYPE_ICON = {
57
+ string: TypeIcon,
58
+ number: HashIcon,
59
+ date: CalendarIcon,
60
+ boolean: ToggleLeftIcon
61
+ };
62
+ const AREA_ICON = {
63
+ filters: FilterIcon,
64
+ columns: LayoutThreeColumnsIcon,
65
+ rows: LayoutThreeRowsIcon,
66
+ values: SigmaIcon
67
+ };
68
+ const allFields = computed(() => {
69
+ return [...source.value.fields.values()];
70
+ });
71
+ const field = (name) => source.value.field(name);
72
+ const query = ref("");
73
+ const listItems = computed({
74
+ get: () => {
75
+ const q = query.value.trim().toLocaleLowerCase();
76
+ return allFields.value.filter((f) => !q || f.label.toLocaleLowerCase().includes(q) || f.name.includes(q)).map((f, index) => ({
77
+ key: `l:${f.name}`,
78
+ kind: "field",
79
+ field: f.name,
80
+ index
81
+ }));
82
+ },
83
+ set: () => {}
84
+ });
85
+ const areas = computed(() => areasOf(config.value));
86
+ const used = computed(() => {
87
+ const set = /* @__PURE__ */ new Set();
88
+ for (const area of AREAS) for (const item of areas.value[area]) set.add(item.field);
89
+ return set;
90
+ });
91
+ let pending = null;
92
+ /** One drop updates two lists (the one left and the one entered); both land in one change. */
93
+ function setArea(area, items) {
94
+ if (!pending) {
95
+ pending = { ...areas.value };
96
+ queueMicrotask(() => {
97
+ const next = pending;
98
+ pending = null;
99
+ config.value = configOf(next, config.value);
100
+ });
101
+ }
102
+ pending[area] = items;
103
+ }
104
+ const areaModel = (area) => computed({
105
+ get: () => areas.value[area],
106
+ set: (items) => setArea(area, items)
107
+ });
108
+ const models = Object.fromEntries(AREAS.map((a) => [a, areaModel(a)]));
109
+ const areaOf = (item) => AREAS.find((a) => areas.value[a].some((i) => i.key === item.key)) ?? null;
110
+ function moveTo(item, to) {
111
+ config.value = configOf(moveItem(areas.value, areaOf(item), item, to), config.value);
112
+ }
113
+ /** Moves an item one place up or down in its area. */
114
+ function shift(item, by) {
115
+ const area = areaOf(item);
116
+ if (!area) return;
117
+ const list = [...areas.value[area]];
118
+ const at = list.findIndex((i) => i.key === item.key);
119
+ const to = at + by;
120
+ if (to < 0 || to >= list.length) return;
121
+ list.splice(at, 1);
122
+ list.splice(to, 0, item);
123
+ config.value = configOf({
124
+ ...areas.value,
125
+ [area]: list
126
+ }, config.value);
127
+ }
128
+ const placeOf = (item) => {
129
+ const area = areaOf(item);
130
+ const list = area ? areas.value[area] : [];
131
+ return {
132
+ first: list[0]?.key === item.key,
133
+ last: list[list.length - 1]?.key === item.key
134
+ };
135
+ };
136
+ /** The checkbox in the list: adds a field where it fits (numbers to the values), or takes it out everywhere. */
137
+ function toggleField(name, on) {
138
+ if (!on) {
139
+ let next = areas.value;
140
+ for (const area of AREAS) next = {
141
+ ...next,
142
+ [area]: next[area].filter((i) => i.field !== name)
143
+ };
144
+ config.value = configOf(next, config.value);
145
+ return;
146
+ }
147
+ moveTo({
148
+ key: `l:${name}`,
149
+ kind: "field",
150
+ field: name,
151
+ index: 0
152
+ }, field(name).type === "number" ? "values" : "rows");
153
+ }
154
+ const itemLabel = (item) => item.field ? field(item.field).label : "";
155
+ const resolved = computed(() => {
156
+ try {
157
+ return resolveConfig(source.value, config.value, { aggregators: aggregatorMap.value });
158
+ } catch {
159
+ return null;
160
+ }
161
+ });
162
+ const aggregatorMap = computed(() => {
163
+ const map = {};
164
+ for (const a of props.aggregators ?? []) map[a.name] = a;
165
+ return map;
166
+ });
167
+ /** How a grouping reads, as in the grouping menu. */
168
+ function groupText(by) {
169
+ const m = messages.value.fields;
170
+ if (typeof by === "string") return m.periods[by];
171
+ const write = new Intl.NumberFormat(void 0, { maximumFractionDigits: 6 });
172
+ return format(m.step, { step: write.format(by.step) });
173
+ }
174
+ /** A chip's grouping, shown apart from its name so it can go on a line of its own. */
175
+ function chipGroup(item) {
176
+ if (item.kind !== "dimension") return "";
177
+ const by = dimensionOf(axisOf(item), item.field)?.by;
178
+ const top = topText(item.field);
179
+ return [by === void 0 ? "" : groupText(by), top].filter(Boolean).join(" · ");
180
+ }
181
+ /** How a field's top N or range reads ("前 10 项", "合计 ≥ 100"), or ''. */
182
+ function topText(name) {
183
+ const filter = filterOf(name);
184
+ const m = messages.value.fields;
185
+ if (filter?.top) return format(filter.top.order === "asc" ? m.bottomN : m.topN, { count: filter.top.count });
186
+ if (filter?.between) {
187
+ const write = new Intl.NumberFormat(void 0, { maximumFractionDigits: 6 });
188
+ const { min, max } = filter.between;
189
+ const range = min !== void 0 && max !== void 0 ? format(messages.value.rangeLabel, {
190
+ from: write.format(min),
191
+ to: write.format(max)
192
+ }) : min !== void 0 ? `≥ ${write.format(min)}` : `≤ ${write.format(max ?? 0)}`;
193
+ return format(m.between, { range });
194
+ }
195
+ return "";
196
+ }
197
+ const chipName = (item) => item.kind === "dimension" ? itemLabel(item) : chipText(item);
198
+ function chipText(item) {
199
+ if (item.kind === "dimension") {
200
+ const extra = chipGroup(item);
201
+ return extra ? format(messages.value.fields.grouped, {
202
+ field: itemLabel(item),
203
+ group: extra
204
+ }) : itemLabel(item);
205
+ }
206
+ if (item.kind !== "value") return itemLabel(item);
207
+ const value = resolved.value?.values[item.index];
208
+ const given = config.value.values?.[item.index];
209
+ if (!value) return item.field ? itemLabel(item) : messages.value.aggregates.count;
210
+ return valueLabel(value, typeof given === "object" ? given.label : void 0, messages.value);
211
+ }
212
+ const dimensionOf = (axis, name) => {
213
+ const d = (config.value[axis] ?? []).find((x) => (typeof x === "string" ? x : x.field) === name);
214
+ return typeof d === "string" ? { field: d } : d;
215
+ };
216
+ const filterOf = (name) => config.value.filters?.[name];
217
+ /** Whether a field's values can be filtered from its chip (not a value: it sums the field up). */
218
+ const canFilter = (item) => item.kind !== "value" && !!item.field;
219
+ const aggregatesFor = (name) => {
220
+ const type = name ? field(name).type : "string";
221
+ const builtIn = type === "number" ? [
222
+ "sum",
223
+ "count",
224
+ "avg",
225
+ "min",
226
+ "max",
227
+ "distinct",
228
+ "median"
229
+ ] : type === "date" ? [
230
+ "count",
231
+ "distinct",
232
+ "min",
233
+ "max"
234
+ ] : ["count", "distinct"];
235
+ const custom = (props.aggregators ?? []).filter((a) => type === "number" || a.input === "value").map((a) => a.name);
236
+ return [...builtIn, ...custom.filter((n) => !builtIn.includes(n))];
237
+ };
238
+ const aggregateText = (name) => messages.value.aggregates[name] ?? aggregatorMap.value[name]?.label ?? BUILTIN_AGGREGATORS[name]?.label ?? name;
239
+ const currentAggregate = (index) => resolved.value?.values[index]?.aggregate ?? "count";
240
+ /** The groupings a field offers: periods for dates, round range sizes for numbers. */
241
+ function groupingsFor(name) {
242
+ const f = field(name);
243
+ const m = messages.value.fields;
244
+ const none = {
245
+ key: "",
246
+ by: null,
247
+ text: m.noGroup
248
+ };
249
+ if (f.type === "date") return [none, ...DATE_PERIODS.map((p) => ({
250
+ key: p,
251
+ by: p,
252
+ text: m.periods[p]
253
+ }))];
254
+ if (f.type !== "number") return [];
255
+ const measure = source.value.measure(name);
256
+ let min = Infinity;
257
+ let max = -Infinity;
258
+ for (let i = 0; i < measure.length; i++) {
259
+ const v = measure[i];
260
+ if (v < min) min = v;
261
+ if (v > max) max = v;
262
+ }
263
+ const write = new Intl.NumberFormat(void 0, { maximumFractionDigits: 6 });
264
+ return [none, ...stepsFor(min, max).map((step) => ({
265
+ key: groupKey({ step }),
266
+ by: { step },
267
+ text: format(m.step, { step: write.format(step) })
268
+ }))];
269
+ }
270
+ /** The sort a row field's menu shows: by its labels, or the rows by a value's grand total. */
271
+ function sortKey(axis, name) {
272
+ const sort = config.value.sort;
273
+ if (axis === "rows" && sort && !sort.column?.length) return `v:${sort.valueIndex}:${sort.order}`;
274
+ return dimensionOf(axis, name)?.sort ?? "asc";
275
+ }
276
+ function onSort(axis, name, key) {
277
+ const by = /^v:(\d+):(asc|desc)$/.exec(String(key));
278
+ if (by) {
279
+ const order = by[2];
280
+ config.value = {
281
+ ...config.value,
282
+ sort: {
283
+ valueIndex: +by[1],
284
+ column: [],
285
+ order
286
+ }
287
+ };
288
+ return;
289
+ }
290
+ const next = withDimension(config.value, axis, name, { sort: key });
291
+ if (axis === "rows") next.sort = null;
292
+ config.value = next;
293
+ }
294
+ function onGroup(axis, name, key) {
295
+ const option = groupingsFor(name).find((g) => g.key === key);
296
+ if (!option) return;
297
+ config.value = withDimension(config.value, axis, name, { by: option.by });
298
+ }
299
+ function onSubtotal(axis, name, on) {
300
+ config.value = withDimension(config.value, axis, name, { subtotal: on });
301
+ }
302
+ function onAggregate(index, name) {
303
+ config.value = withAggregate(config.value, index, String(name));
304
+ }
305
+ const SHOWS = [
306
+ "percentOfTotal",
307
+ "percentOfRow",
308
+ "percentOfColumn",
309
+ "percentOfParent",
310
+ "difference",
311
+ "percentDifference",
312
+ "runningTotal",
313
+ "rank"
314
+ ];
315
+ const valueSpec = (index) => {
316
+ const v = config.value.values?.[index];
317
+ return typeof v === "object" ? v : void 0;
318
+ };
319
+ const showKey = (index) => {
320
+ const show = valueSpec(index)?.show;
321
+ return show === void 0 ? "" : typeof show === "string" ? show : show.as;
322
+ };
323
+ function onShow(index, key) {
324
+ const spec = valueSpec(index)?.show;
325
+ const axis = typeof spec === "object" ? spec.axis : void 0;
326
+ const show = key === "" ? null : axis ? {
327
+ as: key,
328
+ axis
329
+ } : key;
330
+ config.value = withValue(config.value, index, { show });
331
+ }
332
+ const markKey = (index) => {
333
+ const mark = valueSpec(index)?.mark;
334
+ return mark === void 0 ? "" : typeof mark === "string" ? mark : mark.type;
335
+ };
336
+ function onMark(index, key) {
337
+ config.value = withValue(config.value, index, { mark: key === "" ? null : key });
338
+ }
339
+ const TOPS = [
340
+ {
341
+ key: "t5",
342
+ count: 5,
343
+ order: "desc"
344
+ },
345
+ {
346
+ key: "t10",
347
+ count: 10,
348
+ order: "desc"
349
+ },
350
+ {
351
+ key: "t20",
352
+ count: 20,
353
+ order: "desc"
354
+ },
355
+ {
356
+ key: "b5",
357
+ count: 5,
358
+ order: "asc"
359
+ },
360
+ {
361
+ key: "b10",
362
+ count: 10,
363
+ order: "asc"
364
+ }
365
+ ];
366
+ function topKey(name) {
367
+ const filter = filterOf(name);
368
+ if (filter?.between) return "range";
369
+ if (!filter?.top) return "";
370
+ return `${filter.top.order === "asc" ? "b" : "t"}${filter.top.count}`;
371
+ }
372
+ const topOptions = (name) => {
373
+ const m = messages.value.fields;
374
+ const options = [{
375
+ key: "",
376
+ text: m.noTop
377
+ }];
378
+ for (const t of TOPS) options.push({
379
+ key: t.key,
380
+ text: format(t.order === "asc" ? m.bottomN : m.topN, { count: t.count })
381
+ });
382
+ const key = topKey(name);
383
+ if (key && !options.some((o) => o.key === key)) options.push({
384
+ key,
385
+ text: topText(name)
386
+ });
387
+ return options;
388
+ };
389
+ function onTop(item, key) {
390
+ if (key === "range") return;
391
+ const match = /^([tb])(\d+)$/.exec(String(key));
392
+ const old = filterOf(item.field)?.top;
393
+ setRank(item, match ? { top: {
394
+ ...old?.valueIndex ? { valueIndex: old.valueIndex } : {},
395
+ count: Number(match[2]),
396
+ ...match[1] === "b" ? { order: "asc" } : {}
397
+ } } : null);
398
+ }
399
+ function setRank(item, rank) {
400
+ config.value = withRank(config.value, item.field, rank, item.kind === "filter");
401
+ }
402
+ /** The field whose top N or range is set in the dialog. */
403
+ const topItem = ref(null);
404
+ const topOpen = ref(false);
405
+ function openTop(item) {
406
+ topItem.value = item;
407
+ nextTick(() => topOpen.value = true);
408
+ }
409
+ const formulaOpen = ref(false);
410
+ /** The value being changed in the dialog, or -1 for a new one. */
411
+ const formulaIndex = ref(-1);
412
+ const formulaInitial = computed(() => {
413
+ const spec = formulaIndex.value >= 0 ? valueSpec(formulaIndex.value) : void 0;
414
+ return spec?.formula === void 0 ? null : {
415
+ formula: spec.formula,
416
+ label: spec.label
417
+ };
418
+ });
419
+ function openFormula(index) {
420
+ formulaIndex.value = index;
421
+ nextTick(() => formulaOpen.value = true);
422
+ }
423
+ function applyFormula(value) {
424
+ const index = formulaIndex.value;
425
+ if (index < 0) {
426
+ config.value = {
427
+ ...config.value,
428
+ values: [...config.value.values ?? [], value]
429
+ };
430
+ return;
431
+ }
432
+ config.value = withValue(config.value, index, {
433
+ formula: value.formula,
434
+ label: value.label ?? null
435
+ });
436
+ }
437
+ const isFormula = (item) => item.kind === "value" && valueSpec(item.index)?.formula !== void 0;
438
+ const filterOpen = ref(null);
439
+ function setFilterOpen(key, open) {
440
+ if (open) filterOpen.value = key;
441
+ else if (filterOpen.value === key) filterOpen.value = null;
442
+ }
443
+ function openFilter(item) {
444
+ nextTick(() => filterOpen.value = item.key);
445
+ }
446
+ const filterBy = (item) => {
447
+ if (item.kind === "dimension") {
448
+ const axis = areas.value.rows.some((i) => i.key === item.key) ? "rows" : "columns";
449
+ return dimensionOf(axis, item.field)?.by;
450
+ }
451
+ return filterOf(item.field)?.by;
452
+ };
453
+ function applyFilter(item, filter) {
454
+ const old = filterOf(item.field);
455
+ const kept = {
456
+ top: old?.top,
457
+ between: old?.between
458
+ };
459
+ if (kept.top || kept.between) {
460
+ filter = {
461
+ ...filter,
462
+ ...kept.top ? { top: kept.top } : {},
463
+ ...kept.between ? { between: kept.between } : {}
464
+ };
465
+ if (filter.by === void 0 && old?.by !== void 0) filter.by = old.by;
466
+ }
467
+ config.value = withFilter(config.value, item.field, filter, item.kind === "filter");
468
+ filterOpen.value = null;
469
+ }
470
+ const breakpoints = useBreakpoints();
471
+ const mounted = ref(false);
472
+ onMounted(() => mounted.value = true);
473
+ const inDrawer = computed(() => props.drawer === "auto" ? mounted.value && !breakpoints.sm : props.drawer);
474
+ const drawerOpen = ref(false);
475
+ const areaTitle = (area) => messages.value.fields.areas[area];
476
+ /** Where else an item can go; a count of records has no field to group or filter by. */
477
+ const otherAreas = (item) => {
478
+ const here = areaOf(item);
479
+ return AREAS.filter((a) => a !== here && (item.field || a === "values"));
480
+ };
481
+ const axisOf = (item) => areas.value.rows.some((i) => i.key === item.key) ? "rows" : "columns";
482
+ const filtered = (item) => isFiltering(filterOf(item.field));
483
+ return (_ctx, _cache) => {
484
+ return openBlock(), createElementBlock("div", {
485
+ class: normalizeClass(inDrawer.value ? "dou-pivot-fields-trigger" : "dou-pivot-fields"),
486
+ role: inDrawer.value ? void 0 : "region",
487
+ "aria-label": inDrawer.value ? void 0 : unref(messages).fields.panel
488
+ }, [
489
+ inDrawer.value ? (openBlock(), createBlock(unref(DouButton), {
490
+ key: 0,
491
+ size: "sm",
492
+ variant: "outline",
493
+ onClick: _cache[0] || (_cache[0] = ($event) => drawerOpen.value = true)
494
+ }, {
495
+ leading: withCtx(() => [createVNode(unref(TablePivotIcon), { "aria-hidden": "true" })]),
496
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(unref(messages).fields.open), 1)]),
497
+ _: 1
498
+ })) : createCommentVNode("", true),
499
+ (openBlock(), createBlock(resolveDynamicComponent(inDrawer.value ? unref(DouDrawer) : "div"), mergeProps(inDrawer.value ? {
500
+ open: drawerOpen.value,
501
+ title: unref(messages).fields.panel,
502
+ placement: "bottom",
503
+ size: "80%"
504
+ } : { class: "dou-pivot-fields__inner" }, { "onUpdate:open": _cache[6] || (_cache[6] = ($event) => drawerOpen.value = $event) }), {
505
+ default: withCtx(() => [createElementVNode("div", { class: normalizeClass(inDrawer.value ? "dou-pivot-fields is-in-drawer" : "dou-pivot-fields__body") }, [createElementVNode("section", {
506
+ class: "dou-pivot-fields__list-part",
507
+ "aria-label": unref(messages).fields.list
508
+ }, [createVNode(unref(DouInput), {
509
+ modelValue: query.value,
510
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => query.value = $event),
511
+ size: "sm",
512
+ type: "search",
513
+ clearable: "",
514
+ placeholder: unref(messages).fields.search,
515
+ "aria-label": unref(messages).fields.search
516
+ }, {
517
+ prefix: withCtx(() => [createVNode(unref(SearchIcon), { "aria-hidden": "true" })]),
518
+ _: 1
519
+ }, 8, [
520
+ "modelValue",
521
+ "placeholder",
522
+ "aria-label"
523
+ ]), createVNode(unref(DouSortable), {
524
+ modelValue: listItems.value,
525
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => listItems.value = $event),
526
+ class: "dou-pivot-fields__list",
527
+ group,
528
+ animation: false,
529
+ "item-key": "key",
530
+ "item-label": (item) => itemLabel(item)
531
+ }, {
532
+ default: withCtx(({ item }) => [createElementVNode("div", _hoisted_3, [
533
+ createVNode(unref(DouCheckbox), {
534
+ size: "sm",
535
+ "model-value": used.value.has(item.field),
536
+ "aria-label": itemLabel(item),
537
+ "onUpdate:modelValue": ($event) => toggleField(item.field, !!$event)
538
+ }, null, 8, [
539
+ "model-value",
540
+ "aria-label",
541
+ "onUpdate:modelValue"
542
+ ]),
543
+ (openBlock(), createBlock(resolveDynamicComponent(TYPE_ICON[field(item.field).type]), {
544
+ class: "dou-pivot-fields__type",
545
+ "aria-hidden": "true"
546
+ })),
547
+ createElementVNode("span", _hoisted_4, toDisplayString(itemLabel(item)), 1)
548
+ ])]),
549
+ empty: withCtx(() => [createElementVNode("p", _hoisted_5, toDisplayString(unref(messages).fields.noFields), 1)]),
550
+ _: 1
551
+ }, 8, ["modelValue", "item-label"])], 8, _hoisted_2), createElementVNode("div", _hoisted_6, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(AREAS), (area) => {
552
+ return openBlock(), createElementBlock("section", {
553
+ key: area,
554
+ class: normalizeClass(["dou-pivot-fields__area", `is-${area}`]),
555
+ "aria-label": areaTitle(area)
556
+ }, [
557
+ createElementVNode("h3", _hoisted_8, [(openBlock(), createBlock(resolveDynamicComponent(AREA_ICON[area]), { "aria-hidden": "true" })), createTextVNode(" " + toDisplayString(areaTitle(area)), 1)]),
558
+ createVNode(unref(DouSortable), {
559
+ modelValue: unref(models)[area].value,
560
+ "onUpdate:modelValue": ($event) => unref(models)[area].value = $event,
561
+ class: "dou-pivot-fields__chips",
562
+ group,
563
+ "item-key": "key",
564
+ "item-label": (item) => chipText(item)
565
+ }, {
566
+ default: withCtx(({ item }) => [createElementVNode("div", { class: normalizeClass(["dou-pivot-fields__chip", { "is-filtered": filtered(item) }]) }, [
567
+ createElementVNode("span", {
568
+ class: "dou-pivot-fields__chip-text",
569
+ title: chipText(item)
570
+ }, [createElementVNode("span", _hoisted_10, toDisplayString(chipName(item)), 1), chipGroup(item) ? (openBlock(), createElementBlock("span", _hoisted_11, toDisplayString(chipGroup(item)), 1)) : createCommentVNode("", true)], 8, _hoisted_9),
571
+ canFilter(item) ? (openBlock(), createBlock(unref(DouPopover), {
572
+ key: 0,
573
+ open: filterOpen.value === item.key,
574
+ placement: "bottom-start",
575
+ width: 260,
576
+ "onUpdate:open": ($event) => setFilterOpen(item.key, $event)
577
+ }, {
578
+ content: withCtx(() => [(openBlock(), createBlock(filter_list_default, {
579
+ key: filterOpen.value === item.key ? "open" : "closed",
580
+ source: unref(source),
581
+ field: item.field,
582
+ by: filterBy(item),
583
+ filter: filterOf(item.field),
584
+ messages: unref(messages),
585
+ onApply: ($event) => applyFilter(item, $event),
586
+ onCancel: _cache[3] || (_cache[3] = ($event) => filterOpen.value = null)
587
+ }, null, 8, [
588
+ "source",
589
+ "field",
590
+ "by",
591
+ "filter",
592
+ "messages",
593
+ "onApply"
594
+ ]))]),
595
+ default: withCtx(() => [createElementVNode("button", {
596
+ type: "button",
597
+ class: normalizeClass(["dou-pivot-fields__chip-button", { "is-on": filtered(item) }]),
598
+ "aria-label": filtered(item) ? `${unref(messages).fields.filtered},${unref(format)(unref(messages).filter.title, { field: itemLabel(item) })}` : unref(format)(unref(messages).filter.title, { field: itemLabel(item) })
599
+ }, [createVNode(unref(FilterIcon), { "aria-hidden": "true" })], 10, _hoisted_12)]),
600
+ _: 2
601
+ }, 1032, ["open", "onUpdate:open"])) : createCommentVNode("", true),
602
+ createVNode(unref(DouDropdown), { placement: "bottom-end" }, {
603
+ menu: withCtx(() => [
604
+ item.kind === "dimension" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
605
+ createVNode(unref(DouDropdownSub), { title: unref(messages).fields.sort }, {
606
+ default: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
607
+ "model-value": sortKey(axisOf(item), item.field),
608
+ "onUpdate:modelValue": ($event) => onSort(axisOf(item), item.field, $event)
609
+ }, {
610
+ default: withCtx(() => [
611
+ createVNode(unref(DouDropdownItem), { value: "asc" }, {
612
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.asc), 1)]),
613
+ _: 1
614
+ }),
615
+ createVNode(unref(DouDropdownItem), { value: "desc" }, {
616
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.desc), 1)]),
617
+ _: 1
618
+ }),
619
+ axisOf(item) === "rows" ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(areas.value.values, (value, index) => {
620
+ return openBlock(), createElementBlock(Fragment, { key: value.key }, [createVNode(unref(DouDropdownItem), {
621
+ value: `v:${index}:desc`,
622
+ divided: index === 0
623
+ }, {
624
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(format)(unref(messages).fields.byValueDesc, { value: chipText(value) })), 1)]),
625
+ _: 2
626
+ }, 1032, ["value", "divided"]), createVNode(unref(DouDropdownItem), { value: `v:${index}:asc` }, {
627
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(format)(unref(messages).fields.byValueAsc, { value: chipText(value) })), 1)]),
628
+ _: 2
629
+ }, 1032, ["value"])], 64);
630
+ }), 128)) : createCommentVNode("", true)
631
+ ]),
632
+ _: 2
633
+ }, 1032, ["model-value", "onUpdate:modelValue"])]),
634
+ _: 2
635
+ }, 1032, ["title"]),
636
+ groupingsFor(item.field).length ? (openBlock(), createBlock(unref(DouDropdownSub), {
637
+ key: 0,
638
+ title: unref(messages).fields.groupBy
639
+ }, {
640
+ default: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
641
+ "model-value": unref(groupKey)(dimensionOf(axisOf(item), item.field)?.by),
642
+ "onUpdate:modelValue": ($event) => onGroup(axisOf(item), item.field, $event)
643
+ }, {
644
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(groupingsFor(item.field), (option) => {
645
+ return openBlock(), createBlock(unref(DouDropdownItem), {
646
+ key: option.key,
647
+ value: option.key
648
+ }, {
649
+ default: withCtx(() => [createTextVNode(toDisplayString(option.text), 1)]),
650
+ _: 2
651
+ }, 1032, ["value"]);
652
+ }), 128))]),
653
+ _: 2
654
+ }, 1032, ["model-value", "onUpdate:modelValue"])]),
655
+ _: 2
656
+ }, 1032, ["title"])) : createCommentVNode("", true),
657
+ createVNode(unref(DouDropdownSub), { title: unref(messages).fields.top }, {
658
+ default: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
659
+ "model-value": topKey(item.field),
660
+ "onUpdate:modelValue": ($event) => onTop(item, $event)
661
+ }, {
662
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(topOptions(item.field), (option) => {
663
+ return openBlock(), createBlock(unref(DouDropdownItem), {
664
+ key: option.key,
665
+ value: option.key,
666
+ divided: option.key === "b5"
667
+ }, {
668
+ default: withCtx(() => [createTextVNode(toDisplayString(option.text), 1)]),
669
+ _: 2
670
+ }, 1032, ["value", "divided"]);
671
+ }), 128))]),
672
+ _: 2
673
+ }, 1032, ["model-value", "onUpdate:modelValue"]), createVNode(unref(DouDropdownItem), {
674
+ divided: "",
675
+ onClick: ($event) => openTop(item)
676
+ }, {
677
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.customTop), 1)]),
678
+ _: 1
679
+ }, 8, ["onClick"])]),
680
+ _: 2
681
+ }, 1032, ["title"]),
682
+ createVNode(unref(DouDropdownItem), {
683
+ checked: dimensionOf(axisOf(item), item.field)?.subtotal !== false,
684
+ "onUpdate:checked": ($event) => onSubtotal(axisOf(item), item.field, $event)
685
+ }, {
686
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.subtotal), 1)]),
687
+ _: 1
688
+ }, 8, ["checked", "onUpdate:checked"])
689
+ ], 64)) : createCommentVNode("", true),
690
+ item.kind === "value" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
691
+ isFormula(item) ? (openBlock(), createBlock(unref(DouDropdownItem), {
692
+ key: 0,
693
+ onClick: ($event) => openFormula(item.index)
694
+ }, {
695
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.editFormula), 1)]),
696
+ _: 1
697
+ }, 8, ["onClick"])) : (openBlock(), createBlock(unref(DouDropdownSub), {
698
+ key: 1,
699
+ title: unref(messages).fields.aggregate
700
+ }, {
701
+ default: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
702
+ "model-value": currentAggregate(item.index),
703
+ "onUpdate:modelValue": ($event) => onAggregate(item.index, $event)
704
+ }, {
705
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(aggregatesFor(item.field), (name) => {
706
+ return openBlock(), createBlock(unref(DouDropdownItem), {
707
+ key: name,
708
+ value: name
709
+ }, {
710
+ default: withCtx(() => [createTextVNode(toDisplayString(aggregateText(name)), 1)]),
711
+ _: 2
712
+ }, 1032, ["value"]);
713
+ }), 128))]),
714
+ _: 2
715
+ }, 1032, ["model-value", "onUpdate:modelValue"])]),
716
+ _: 2
717
+ }, 1032, ["title"])),
718
+ createVNode(unref(DouDropdownSub), { title: unref(messages).fields.showAs }, {
719
+ default: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
720
+ "model-value": showKey(item.index),
721
+ "onUpdate:modelValue": ($event) => onShow(item.index, $event)
722
+ }, {
723
+ default: withCtx(() => [createVNode(unref(DouDropdownItem), { value: "" }, {
724
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.showPlain), 1)]),
725
+ _: 1
726
+ }), (openBlock(), createElementBlock(Fragment, null, renderList(SHOWS, (show, i) => {
727
+ return createVNode(unref(DouDropdownItem), {
728
+ key: show,
729
+ value: show,
730
+ divided: i === 0 || show === "difference"
731
+ }, {
732
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).shows[show]), 1)]),
733
+ _: 2
734
+ }, 1032, ["value", "divided"]);
735
+ }), 64))]),
736
+ _: 1
737
+ }, 8, ["model-value", "onUpdate:modelValue"])]),
738
+ _: 2
739
+ }, 1032, ["title"]),
740
+ createVNode(unref(DouDropdownSub), { title: unref(messages).fields.mark }, {
741
+ default: withCtx(() => [createVNode(unref(DouDropdownRadioGroup), {
742
+ "model-value": markKey(item.index),
743
+ "onUpdate:modelValue": ($event) => onMark(item.index, $event)
744
+ }, {
745
+ default: withCtx(() => [
746
+ createVNode(unref(DouDropdownItem), { value: "" }, {
747
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.noMark), 1)]),
748
+ _: 1
749
+ }),
750
+ createVNode(unref(DouDropdownItem), { value: "bar" }, {
751
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.bar), 1)]),
752
+ _: 1
753
+ }),
754
+ createVNode(unref(DouDropdownItem), { value: "scale" }, {
755
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.scale), 1)]),
756
+ _: 1
757
+ })
758
+ ]),
759
+ _: 1
760
+ }, 8, ["model-value", "onUpdate:modelValue"])]),
761
+ _: 2
762
+ }, 1032, ["title"]),
763
+ (config.value.values?.length ?? 0) > 1 ? (openBlock(), createBlock(unref(DouDropdownItem), {
764
+ key: 2,
765
+ checked: config.value.valuesOn === "rows",
766
+ "onUpdate:checked": _cache[4] || (_cache[4] = ($event) => config.value = {
767
+ ...config.value,
768
+ valuesOn: $event ? "rows" : "columns"
769
+ })
770
+ }, {
771
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.valuesOnRows), 1)]),
772
+ _: 1
773
+ }, 8, ["checked"])) : createCommentVNode("", true)
774
+ ], 64)) : createCommentVNode("", true),
775
+ canFilter(item) ? (openBlock(), createBlock(unref(DouDropdownItem), {
776
+ key: 2,
777
+ onClick: ($event) => openFilter(item)
778
+ }, {
779
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.filter), 1)]),
780
+ _: 1
781
+ }, 8, ["onClick"])) : createCommentVNode("", true),
782
+ createVNode(unref(DouDropdownItem), {
783
+ divided: "",
784
+ disabled: placeOf(item).first,
785
+ onClick: ($event) => shift(item, -1)
786
+ }, {
787
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.moveUp), 1)]),
788
+ _: 1
789
+ }, 8, ["disabled", "onClick"]),
790
+ createVNode(unref(DouDropdownItem), {
791
+ disabled: placeOf(item).last,
792
+ onClick: ($event) => shift(item, 1)
793
+ }, {
794
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.moveDown), 1)]),
795
+ _: 1
796
+ }, 8, ["disabled", "onClick"]),
797
+ createVNode(unref(DouDropdownSub), { title: unref(messages).fields.moveTo }, {
798
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(otherAreas(item), (to) => {
799
+ return openBlock(), createBlock(unref(DouDropdownItem), {
800
+ key: to,
801
+ onClick: ($event) => moveTo(item, to)
802
+ }, {
803
+ default: withCtx(() => [createTextVNode(toDisplayString(areaTitle(to)), 1)]),
804
+ _: 2
805
+ }, 1032, ["onClick"]);
806
+ }), 128))]),
807
+ _: 2
808
+ }, 1032, ["title"]),
809
+ createVNode(unref(DouDropdownItem), {
810
+ tone: "danger",
811
+ onClick: ($event) => moveTo(item, null)
812
+ }, {
813
+ default: withCtx(() => [createTextVNode(toDisplayString(unref(messages).fields.remove), 1)]),
814
+ _: 1
815
+ }, 8, ["onClick"])
816
+ ]),
817
+ default: withCtx(() => [createElementVNode("button", {
818
+ type: "button",
819
+ class: "dou-pivot-fields__chip-button",
820
+ "aria-label": unref(format)(unref(messages).fields.menu, { field: chipText(item) })
821
+ }, [createVNode(unref(ChevronDownIcon), { "aria-hidden": "true" })], 8, _hoisted_13)]),
822
+ _: 2
823
+ }, 1024)
824
+ ], 2)]),
825
+ empty: withCtx(() => [createElementVNode("p", _hoisted_14, toDisplayString(unref(messages).fields.dropHere), 1)]),
826
+ _: 1
827
+ }, 8, [
828
+ "modelValue",
829
+ "onUpdate:modelValue",
830
+ "item-label"
831
+ ]),
832
+ area === "values" ? (openBlock(), createBlock(unref(DouButton), {
833
+ key: 0,
834
+ class: "dou-pivot-fields__add",
835
+ size: "sm",
836
+ variant: "ghost",
837
+ onClick: _cache[5] || (_cache[5] = ($event) => openFormula(-1))
838
+ }, {
839
+ leading: withCtx(() => [createVNode(unref(PlusIcon), { "aria-hidden": "true" })]),
840
+ default: withCtx(() => [createTextVNode(" " + toDisplayString(unref(messages).fields.addFormula), 1)]),
841
+ _: 1
842
+ })) : createCommentVNode("", true)
843
+ ], 10, _hoisted_7);
844
+ }), 128))])], 2)]),
845
+ _: 1
846
+ }, 16)),
847
+ createVNode(formula_dialog_default, {
848
+ open: formulaOpen.value,
849
+ "onUpdate:open": _cache[7] || (_cache[7] = ($event) => formulaOpen.value = $event),
850
+ source: unref(source),
851
+ messages: unref(messages),
852
+ initial: formulaInitial.value,
853
+ onApply: applyFormula
854
+ }, null, 8, [
855
+ "open",
856
+ "source",
857
+ "messages",
858
+ "initial"
859
+ ]),
860
+ createVNode(top_dialog_default, {
861
+ open: topOpen.value,
862
+ "onUpdate:open": _cache[8] || (_cache[8] = ($event) => topOpen.value = $event),
863
+ messages: unref(messages),
864
+ field: topItem.value ? itemLabel(topItem.value) : "",
865
+ values: areas.value.values.map((v) => chipText(v)),
866
+ filter: topItem.value ? filterOf(topItem.value.field) : void 0,
867
+ onApply: _cache[9] || (_cache[9] = ($event) => topItem.value && setRank(topItem.value, $event))
868
+ }, null, 8, [
869
+ "open",
870
+ "messages",
871
+ "field",
872
+ "values",
873
+ "filter"
874
+ ])
875
+ ], 10, _hoisted_1);
876
+ };
877
+ }
878
+ });
879
+ export { fields_vue_vue_type_script_setup_true_lang_default as default };
880
+
881
+ //# sourceMappingURL=fields.vue_vue_type_script_setup_true_lang.js.map