@iris-ui-kit/vue 0.2.37 → 0.2.38

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.
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var vue = require('vue');
4
+ var grid = require('@iris-ui-kit/core/grid');
4
5
  var core = require('@iris-ui-kit/core');
5
6
  var theme = require('@iris-ui-kit/theme');
6
7
  var skins = require('@iris-ui-kit/skins');
@@ -20,6 +21,378 @@ function useStoreSelector(store2, selector, equals) {
20
21
  vue.onScopeDispose(store2.subscribeWith(selector, (v) => slice.value = v, equals));
21
22
  return slice;
22
23
  }
24
+ function defaultValue(row, column) {
25
+ return row[column.dataIndex ?? column.key];
26
+ }
27
+ function defaultSetValue(row, column, value) {
28
+ return { ...row, [column.dataIndex ?? column.key]: value };
29
+ }
30
+ function useGridClipboard(core, options) {
31
+ const latest = vue.shallowRef(options);
32
+ if (!core.hasFeature("clipboard")) {
33
+ core.use(
34
+ grid.createGridClipboardFeature({
35
+ getRows: () => latest.value.getRows?.() ?? core.invoke("getRows"),
36
+ getColumns: () => latest.value.getColumns(),
37
+ rowKeyField: latest.value.rowKeyField,
38
+ overflowRows: (context) => latest.value.overflowRows?.(context),
39
+ resolveValue: (row, column) => {
40
+ const resolve4 = latest.value.resolveValue;
41
+ return resolve4 ? resolve4(row, column) : defaultValue(row, column);
42
+ },
43
+ parseValue: (text, row, column) => {
44
+ const parse = latest.value.parseValue;
45
+ return parse ? parse(text, row, column) : text;
46
+ },
47
+ setValue: (row, column, value) => latest.value.setValue?.(row, column, value) ?? defaultSetValue(row, column, value),
48
+ isCellEditable: (row, column, rowIndex, columnIndex) => latest.value.isCellEditable?.(row, column, rowIndex, columnIndex) ?? true,
49
+ reconcileRows: (sourceRows, previousRows, rows) => latest.value.reconcileRows?.(sourceRows, previousRows, rows) ?? rows,
50
+ commitOptions: () => {
51
+ const configured = latest.value.commitOptions;
52
+ return typeof configured === "function" ? configured() : configured ?? {};
53
+ },
54
+ onCopy: (change) => latest.value.onCopy?.(change),
55
+ onPaste: (change) => latest.value.onPaste?.(change)
56
+ })
57
+ );
58
+ }
59
+ const model = core.invoke("getClipboardModel");
60
+ return {
61
+ core,
62
+ model,
63
+ serialize: (format, copyWithFormat) => model.serialize(
64
+ format ?? latest.value.copyFormat ?? "tsv",
65
+ copyWithFormat ?? latest.value.copyWithFormat ?? false
66
+ ),
67
+ paste: (text, range) => model.paste(text, range)
68
+ };
69
+ }
70
+ function useGridFeature(core, name, method, create) {
71
+ if (!core.hasFeature(name)) core.use(create());
72
+ return core.invoke(method);
73
+ }
74
+ function useGridEditing(core, options) {
75
+ const latest = vue.shallowRef(options);
76
+ const model = useGridFeature(
77
+ core,
78
+ "editing",
79
+ "getEditingModel",
80
+ () => grid.createGridEditingFeature({
81
+ getRowKey: (row, index) => latest.value.getRowKey(row, index),
82
+ getRowIndex: (rowKey, row, rootRows) => latest.value.getRowIndex?.(rowKey, row, rootRows),
83
+ getRules: (columnKey) => latest.value.getRules?.(columnKey),
84
+ getValue: (row, columnKey) => latest.value.getValue?.(row, columnKey) ?? row[columnKey],
85
+ setValue: (row, columnKey, value) => latest.value.setValue?.(row, columnKey, value) ?? { ...row, [columnKey]: value },
86
+ coerce: (draft, row, columnKey) => latest.value.coerce?.(draft, row, columnKey) ?? draft,
87
+ validate: (value, row, columnKey) => latest.value.validate?.(value, row, columnKey) ?? null,
88
+ isEditable: (row, columnKey) => latest.value.isEditable?.(row, columnKey) ?? true,
89
+ missingRowMessage: options.missingRowMessage,
90
+ commitOptions: () => {
91
+ const configured = latest.value.commitOptions;
92
+ return typeof configured === "function" ? configured() : configured ?? {};
93
+ },
94
+ onStateChange: (state2) => latest.value.onStateChange?.(state2),
95
+ onValidation: (validation) => latest.value.onValidation?.(validation),
96
+ onCommit: (commit) => latest.value.onCommit?.(commit)
97
+ })
98
+ );
99
+ const state = useStore(model.store);
100
+ return {
101
+ core,
102
+ model,
103
+ state,
104
+ startCellEdit: (rowKey, columnKey, initialDraft) => model.start(rowKey, columnKey, initialDraft),
105
+ setCellDraft: (value) => model.setDraft(value),
106
+ cancelCellEdit: () => model.cancelEdit(),
107
+ commitCellEdit: (value) => model.commitEdit(value),
108
+ isCellEditing: (rowKey, columnKey) => model.isEditing(rowKey, columnKey)
109
+ };
110
+ }
111
+ function useGridRange(core, options = {}) {
112
+ const latest = vue.shallowRef(options);
113
+ if (!core.hasFeature("range")) {
114
+ core.use(
115
+ grid.createGridRangeFeature({
116
+ onChange: (change) => latest.value.onChange?.(change)
117
+ })
118
+ );
119
+ }
120
+ const model = core.invoke("getRangeModel");
121
+ const state = useStore(model);
122
+ const range = vue.computed(() => {
123
+ const { anchor, active } = state.value;
124
+ if (!anchor || !active) return null;
125
+ return {
126
+ start: {
127
+ row: Math.min(anchor.row, active.row),
128
+ col: Math.min(anchor.col, active.col)
129
+ },
130
+ end: {
131
+ row: Math.max(anchor.row, active.row),
132
+ col: Math.max(anchor.col, active.col)
133
+ }
134
+ };
135
+ });
136
+ return { model, state, range };
137
+ }
138
+
139
+ // src/grid/index.ts
140
+ function useGridCore(options = {}) {
141
+ const core = grid.createGridCore(options);
142
+ vue.onMounted(() => core.ready());
143
+ vue.onBeforeUnmount(() => core.destroy());
144
+ return core;
145
+ }
146
+ function useGridFeature2(core, name, method, create) {
147
+ if (!core.hasFeature(name)) core.use(create());
148
+ return core.invoke(method);
149
+ }
150
+ function useGridSelection(core, options = {}) {
151
+ const latest = vue.shallowRef(options);
152
+ const model = useGridFeature2(
153
+ core,
154
+ "selection",
155
+ "getSelectionModel",
156
+ () => grid.createGridSelectionFeature({
157
+ mode: options.mode,
158
+ defaultSelected: options.value ?? options.defaultValue,
159
+ getKeys: () => latest.value.getKeys?.() ?? [],
160
+ onChange: (keys) => latest.value.onChange?.(keys)
161
+ })
162
+ );
163
+ const state = useStore(model.store);
164
+ const controlled = vue.computed(() => options.value !== void 0);
165
+ vue.watch(
166
+ () => options.value,
167
+ (value) => {
168
+ if (value !== void 0) model.sync(value);
169
+ },
170
+ { immediate: true }
171
+ );
172
+ return {
173
+ model,
174
+ controlled,
175
+ selection: vue.computed(() => controlled.value ? options.value ?? [] : state.value)
176
+ };
177
+ }
178
+ function useGridExpansion(core, options = {}) {
179
+ const latest = vue.shallowRef(options);
180
+ const model = useGridFeature2(
181
+ core,
182
+ "expansion",
183
+ "getExpansionModel",
184
+ () => grid.createGridExpansionFeature({
185
+ mode: options.mode,
186
+ defaultExpanded: options.defaultValue,
187
+ getKeys: () => latest.value.getKeys?.() ?? [],
188
+ onChange: (keys) => latest.value.onChange?.(keys)
189
+ })
190
+ );
191
+ return { model, expandedKeys: useStore(model.store) };
192
+ }
193
+ function useGridRows(core, initialRows, options = {}) {
194
+ const latest = vue.shallowRef(options);
195
+ const model = useGridFeature2(
196
+ core,
197
+ "rows",
198
+ "getRowsModel",
199
+ () => grid.createGridRowsFeature({
200
+ defaultRows: initialRows,
201
+ cloneDefaultRows: options.cloneDefaultRows,
202
+ rowKeyField: options.rowKeyField,
203
+ getRowKey: (row, index) => latest.value.getRowKey?.(row, index),
204
+ getChildren: options.getChildren,
205
+ setChildren: options.setChildren,
206
+ onBeforeRowsChange: (tx) => latest.value.onBeforeRowsChange?.(tx),
207
+ onRowsChange: (tx) => latest.value.onRowsChange?.(tx)
208
+ })
209
+ );
210
+ return { model, rows: useStore(model.store) };
211
+ }
212
+ function useGridColumns(core, options = {}) {
213
+ const latest = vue.shallowRef(options);
214
+ const model = useGridFeature2(
215
+ core,
216
+ "columns",
217
+ "getColumnsModel",
218
+ () => grid.createGridColumnsFeature({
219
+ defaultVisibility: options.visibility ?? options.defaultVisibility,
220
+ defaultOrder: options.order ?? options.defaultOrder,
221
+ defaultWidths: options.widths ?? options.defaultWidths,
222
+ defaultPinned: options.pinned ?? options.defaultPinned,
223
+ onVisibilityChange: (v) => latest.value.onVisibilityChange?.(v),
224
+ onOrderChange: (v) => latest.value.onOrderChange?.(v),
225
+ onWidthsChange: (v) => latest.value.onWidthsChange?.(v),
226
+ onPinnedChange: (k, v) => latest.value.onPinnedChange?.(k, v)
227
+ })
228
+ );
229
+ const state = useStore(model.store);
230
+ vue.watch(
231
+ () => options.visibility,
232
+ (v) => v !== void 0 && model.syncVisibility(v)
233
+ );
234
+ vue.watch(
235
+ () => options.order,
236
+ (v) => v !== void 0 && model.syncOrder(v)
237
+ );
238
+ vue.watch(
239
+ () => options.widths,
240
+ (v) => v !== void 0 && model.syncWidths(v)
241
+ );
242
+ vue.watch(
243
+ () => options.pinned,
244
+ (v) => v !== void 0 && model.syncPinned(v)
245
+ );
246
+ return {
247
+ model,
248
+ state,
249
+ setVisibility: (v) => model.setVisibility(v),
250
+ toggleVisibility: (k) => model.toggleVisibility(k),
251
+ setOrder: (v) => model.setOrder(v),
252
+ clearOrder: () => model.setOrder(void 0),
253
+ setWidth: (k, v) => model.setWidth(k, v),
254
+ setWidths: (v) => model.setWidths(v),
255
+ resetWidths: () => model.setWidths({}),
256
+ setPinned: (k, v) => model.setPinned(k, v)
257
+ };
258
+ }
259
+ function useGridPagination(core, options = {}) {
260
+ const latest = vue.shallowRef(options);
261
+ const model = useGridFeature2(
262
+ core,
263
+ "pagination",
264
+ "getPaginationModel",
265
+ () => grid.createGridPaginationFeature({
266
+ defaultPage: options.page ?? options.defaultPage,
267
+ defaultPageSize: options.pageSize ?? options.defaultPageSize,
268
+ defaultTotal: options.total ?? options.defaultTotal,
269
+ onChange: (change) => latest.value.onChange?.(change)
270
+ })
271
+ );
272
+ const pagination = useStore(model.store);
273
+ vue.watch(
274
+ () => [options.page, options.pageSize, options.total],
275
+ ([page, pageSize, total]) => model.sync({ page, pageSize, total }),
276
+ { immediate: true }
277
+ );
278
+ return {
279
+ model,
280
+ pagination,
281
+ setPage: (v) => model.setPage(v),
282
+ setPageSize: (v) => model.setPageSize(v),
283
+ setPagination: (page, size) => model.set(page, size)
284
+ };
285
+ }
286
+ function useGridSorting(core, options = {}) {
287
+ const latest = vue.shallowRef(options);
288
+ const model = useGridFeature2(
289
+ core,
290
+ "sorting",
291
+ "getSortingModel",
292
+ () => grid.createGridSortingFeature({
293
+ mode: options.mode,
294
+ defaultSort: options.sort ?? options.defaultSort,
295
+ defaultMultiSort: options.multiSortState ?? options.defaultMultiSort,
296
+ onSortChange: (v) => latest.value.onSortChange?.(v),
297
+ onMultiSortChange: (v) => latest.value.onMultiSortChange?.(v)
298
+ })
299
+ );
300
+ const state = useStore(model.store);
301
+ vue.watch(
302
+ () => options.sort,
303
+ (v) => v !== void 0 && model.syncSort(v)
304
+ );
305
+ vue.watch(
306
+ () => options.multiSortState,
307
+ (v) => v !== void 0 && model.syncMultiSort(v)
308
+ );
309
+ return {
310
+ model,
311
+ sort: vue.computed(() => options.sort !== void 0 ? options.sort : state.value.sort),
312
+ multiSort: vue.computed(
313
+ () => options.multiSortState !== void 0 ? options.multiSortState : state.value.multiSort
314
+ ),
315
+ cycleSort: (key) => model.cycleSort(key),
316
+ setSort: (v) => model.setSort(v),
317
+ cycleMultiSort: (key) => model.cycleMultiSort(key),
318
+ setMultiSort: (v) => model.setMultiSort(v)
319
+ };
320
+ }
321
+ function useGridFiltering(core, options = {}) {
322
+ const latest = vue.shallowRef(options);
323
+ const model = useGridFeature2(
324
+ core,
325
+ "filtering",
326
+ "getFilteringModel",
327
+ () => grid.createGridFilteringFeature({
328
+ defaultFilters: options.filters ?? options.defaultFilters,
329
+ defaultFilterValues: options.filterValues ?? options.defaultFilterValues,
330
+ onFiltersChange: (v) => latest.value.onFiltersChange?.(v),
331
+ onFilterValuesChange: (v) => latest.value.onFilterValuesChange?.(v)
332
+ })
333
+ );
334
+ const state = useStore(model.store);
335
+ vue.watch(
336
+ () => options.filters,
337
+ (v) => v !== void 0 && model.syncFilters(v)
338
+ );
339
+ vue.watch(
340
+ () => options.filterValues,
341
+ (v) => v !== void 0 && model.syncFilterValues(v)
342
+ );
343
+ return {
344
+ model,
345
+ filters: vue.computed(() => options.filters ?? state.value.filters),
346
+ filterValues: vue.computed(() => options.filterValues ?? state.value.filterValues)
347
+ };
348
+ }
349
+ function useGridVirtual(core, options) {
350
+ const model = useGridFeature2(
351
+ core,
352
+ "virtual",
353
+ "getVirtualModel",
354
+ () => grid.createGridVirtualFeature({
355
+ count: options.items.length,
356
+ estimateSize: (index) => {
357
+ const estimate = options.estimateSize;
358
+ return typeof estimate === "function" ? estimate(index) : estimate;
359
+ },
360
+ viewportSize: options.viewportSize,
361
+ scrollOffset: options.scrollOffset,
362
+ buffer: options.buffer,
363
+ fixedSize: typeof options.estimateSize === "number" ? options.estimateSize : null,
364
+ getItemKey: (index) => {
365
+ const item = options.items[index];
366
+ return item !== void 0 && options.getItemKey ? options.getItemKey(item, index) : index;
367
+ },
368
+ onRangeChange: options.onRangeChange
369
+ })
370
+ );
371
+ vue.watch(
372
+ () => options.items,
373
+ (items) => model.setCount(items.length)
374
+ );
375
+ vue.watch(
376
+ () => options.buffer,
377
+ (buffer) => model.setBuffer(buffer ?? 0)
378
+ );
379
+ vue.watch(
380
+ () => options.estimateSize,
381
+ (estimate) => {
382
+ model.setFixedSize(typeof estimate === "number" ? estimate : null);
383
+ model.remeasure();
384
+ }
385
+ );
386
+ vue.watch(
387
+ () => options.viewportSize,
388
+ (size) => size !== void 0 && model.setViewportSize(size)
389
+ );
390
+ vue.watch(
391
+ () => options.scrollOffset,
392
+ (offset) => offset !== void 0 && model.setScroll(offset)
393
+ );
394
+ return { model, state: useStore(model.store) };
395
+ }
23
396
  function useReconnectingSource(connect, handlers, options) {
24
397
  const status = vue.ref("idle");
25
398
  const handlersRef = { current: handlers };
@@ -621,20 +994,39 @@ function read() {
621
994
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
622
995
  return window.matchMedia(QUERY).matches;
623
996
  }
624
- function usePrefersReducedMotion() {
625
- const reduced = vue.ref(read());
997
+ function usePrefersReducedMotion(enabled) {
998
+ const reduced = vue.ref(enabled?.value === false ? false : read());
626
999
  let mq = null;
1000
+ let mounted = false;
627
1001
  const onChange = () => {
628
1002
  if (mq) reduced.value = mq.matches;
629
1003
  };
630
- vue.onMounted(() => {
631
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
1004
+ const start = () => {
1005
+ if (!mounted || enabled?.value === false || mq !== null || typeof window === "undefined" || typeof window.matchMedia !== "function")
1006
+ return;
632
1007
  mq = window.matchMedia(QUERY);
633
1008
  reduced.value = mq.matches;
634
1009
  mq.addEventListener?.("change", onChange);
1010
+ };
1011
+ const stop = () => {
1012
+ mq?.removeEventListener?.("change", onChange);
1013
+ mq = null;
1014
+ reduced.value = false;
1015
+ };
1016
+ vue.onMounted(() => {
1017
+ mounted = true;
1018
+ start();
635
1019
  });
1020
+ if (enabled) {
1021
+ vue.watch(enabled, (active) => {
1022
+ if (!mounted) return;
1023
+ if (active) start();
1024
+ else stop();
1025
+ });
1026
+ }
636
1027
  vue.onBeforeUnmount(() => {
637
- mq?.removeEventListener?.("change", onChange);
1028
+ mounted = false;
1029
+ stop();
638
1030
  });
639
1031
  return reduced;
640
1032
  }
@@ -2879,7 +3271,7 @@ var IrisAdminTabs = vue.defineComponent({
2879
3271
  width: "16px",
2880
3272
  height: "16px",
2881
3273
  border: "none",
2882
- borderRadius: "4px",
3274
+ borderRadius: "var(--iris-radius-sm, 4px)",
2883
3275
  background: "transparent",
2884
3276
  color: "inherit",
2885
3277
  cursor: "pointer",
@@ -7665,7 +8057,7 @@ function badgeStyle(variant, tone, size) {
7665
8057
  fontWeight: "500",
7666
8058
  lineHeight: "1",
7667
8059
  whiteSpace: "nowrap",
7668
- fontSize: size === "sm" ? "var(--iris-font-size-xs, 12px)" : "12px",
8060
+ fontSize: "var(--iris-font-size-xs, 12px)",
7669
8061
  padding: size === "sm" ? "2px 6px" : "3px 8px"
7670
8062
  };
7671
8063
  switch (variant) {
@@ -7965,7 +8357,7 @@ var CSS5 = `
7965
8357
  border-radius: 50%;
7966
8358
  }
7967
8359
  [data-iris-skeleton-shape="text"] {
7968
- border-radius: 4px;
8360
+ border-radius: var(--iris-radius-sm, 4px);
7969
8361
  }
7970
8362
  @media (prefers-reduced-motion: reduce) {
7971
8363
  [data-iris-skeleton][data-iris-skeleton-animated="true"] {
@@ -8022,8 +8414,8 @@ var IrisSkeleton = vue.defineComponent({
8022
8414
  vue.onMounted(installSkeletonStyles);
8023
8415
  const style = vue.computed(() => {
8024
8416
  const w = props.width !== void 0 ? toCss(props.width) : defaultWidth(props.shape);
8025
- const h137 = props.height !== void 0 ? toCss(props.height) : defaultHeight(props.shape, props.width);
8026
- return { width: w, height: h137 };
8417
+ const h139 = props.height !== void 0 ? toCss(props.height) : defaultHeight(props.shape, props.width);
8418
+ return { width: w, height: h139 };
8027
8419
  });
8028
8420
  return () => vue.h("div", {
8029
8421
  ...attrs,
@@ -8762,7 +9154,7 @@ var IrisCard = vue.defineComponent({
8762
9154
  flexDirection: "column",
8763
9155
  background: "var(--iris-background)",
8764
9156
  color: "var(--iris-foreground)",
8765
- borderRadius: "var(--iris-radius-md, 8px)",
9157
+ borderRadius: "var(--iris-radius-md, 6px)",
8766
9158
  overflow: "hidden",
8767
9159
  transition: props.hover ? "transform 160ms ease, box-shadow 160ms ease" : "none"
8768
9160
  };
@@ -9868,7 +10260,7 @@ var IrisKbd = vue.defineComponent({
9868
10260
  background: "var(--iris-surface)",
9869
10261
  color: "var(--iris-foreground)",
9870
10262
  border: "1px solid var(--iris-border)",
9871
- borderRadius: "4px",
10263
+ borderRadius: "var(--iris-radius-sm, 4px)",
9872
10264
  boxShadow: "0 1px 0 var(--iris-border)",
9873
10265
  lineHeight: "1",
9874
10266
  fontWeight: "500"
@@ -10604,6 +10996,13 @@ var tableProps = {
10604
10996
  tableTabs: { type: Array, default: void 0 },
10605
10997
  /** Show a draggable boundary for the leading left-pinned columns. */
10606
10998
  pinnedDrag: { type: Boolean, default: false },
10999
+ /** Controlled per-column pin state; explicit null overrides a static column pin. */
11000
+ pinnedColumns: {
11001
+ type: Object,
11002
+ default: void 0
11003
+ },
11004
+ /** Enable the built-in column-header pin menu; default off. */
11005
+ columnPinMenu: { type: Boolean, default: false },
10607
11006
  /** Called for each column whose pin side changes. */
10608
11007
  onColumnPinnedChange: {
10609
11008
  type: Function,
@@ -10617,6 +11016,14 @@ var tableProps = {
10617
11016
  /** Below 480px, greedily hide the lowest-priority top-level columns until
10618
11017
  * the natural width fits; pinned columns survive. */
10619
11018
  responsive: { type: Boolean, default: false },
11019
+ /**
11020
+ * External row sets for `table!field` formula references. The first row of
11021
+ * the named set is read; replace the object when referenced rows change.
11022
+ */
11023
+ formulaTables: {
11024
+ type: Object,
11025
+ default: void 0
11026
+ },
10620
11027
  /** Extra bare row sets appended as named CSV segments by the imperative handle. */
10621
11028
  exportNames: {
10622
11029
  type: Array,
@@ -10635,6 +11042,8 @@ var tableProps = {
10635
11042
  error: { type: Boolean, default: false },
10636
11043
  /** Print-friendly mode: marks the root so toolbar/form chrome is hidden by print CSS. */
10637
11044
  printable: { type: Boolean, default: false },
11045
+ /** Show a token-styled back-to-top button for the table's effective scroller. */
11046
+ scrollToTop: { type: Boolean, default: false },
10638
11047
  /** Show a confirmation preview before the toolbar CSV import callback. */
10639
11048
  importPreview: { type: Boolean, default: false },
10640
11049
  onRetry: { type: Function, default: void 0 },
@@ -10643,6 +11052,16 @@ var tableProps = {
10643
11052
  cellRange: { type: Boolean, default: false },
10644
11053
  /** Range clipboard copy; copyWithFormat uses column formatter output. */
10645
11054
  clipConfig: { type: Object, default: void 0 },
11055
+ /** Batch CK (iris 独有 — vxe has no inline search highlight): a
11056
+ * case-insensitive literal-substring search over each text cell's display
11057
+ * chain (mask → formatter ?? raw); every occurrence renders as an inline
11058
+ * `<mark data-iris-search-hit>` with the surface-selected token. Display-only:
11059
+ * no bar, no match state, no write-back. `render`/`html`/`link`/`autoLink`/
11060
+ * sparkline cells are untouched. Additive; default off (fail-closed).
11061
+ */
11062
+ searchHighlight: { type: String, default: "" },
11063
+ /** Enable built-in row-list undo/redo history; default off. */
11064
+ undo: { type: Boolean, default: false },
10646
11065
  renderDetail: {
10647
11066
  type: Function,
10648
11067
  default: void 0
@@ -10694,6 +11113,13 @@ var tableProps = {
10694
11113
  type: Object,
10695
11114
  default: void 0
10696
11115
  },
11116
+ /** Controlled top-level presentation order; omitted keys retain source order. */
11117
+ columnOrder: {
11118
+ type: Array,
11119
+ default: void 0
11120
+ },
11121
+ /** Animate controlled column show/hide transitions; disabled by default. */
11122
+ columnFade: { type: Boolean, default: false },
10697
11123
  filters: {
10698
11124
  type: Object,
10699
11125
  default: void 0
@@ -10702,6 +11128,8 @@ var tableProps = {
10702
11128
  type: Object,
10703
11129
  default: void 0
10704
11130
  },
11131
+ /** Show recently confirmed non-empty filter sets in the filter panel; default off. */
11132
+ recentFilters: { type: Boolean, default: false },
10705
11133
  onFilterValuesChange: {
10706
11134
  type: Function,
10707
11135
  default: void 0
@@ -10743,12 +11171,12 @@ var RESIZE_STEP = 16;
10743
11171
  function isEditableColumn(column) {
10744
11172
  return !!column.editable && !column.formula;
10745
11173
  }
10746
- function getCellValue(row, column) {
10747
- if (column.formula) return core.memoizedFormulaValue(column.formula, row);
11174
+ function getCellValue(row, column, formulaTables) {
11175
+ if (column.formula) return core.memoizedFormulaValue(column.formula, row, formulaTables);
10748
11176
  const key = column.dataIndex ?? column.key;
10749
11177
  return row[key];
10750
11178
  }
10751
- function withComputedFormulaCells(rows, columns) {
11179
+ function withComputedFormulaCells(rows, columns, formulaTables) {
10752
11180
  const formulaCols = columns.filter((c) => c.formula);
10753
11181
  if (formulaCols.length === 0) return rows;
10754
11182
  return rows.map((row) => {
@@ -10756,7 +11184,11 @@ function withComputedFormulaCells(rows, columns) {
10756
11184
  for (const col of formulaCols) {
10757
11185
  const key = col.dataIndex ?? col.key;
10758
11186
  const next = shadow ?? { ...row };
10759
- next[key] = core.memoizedFormulaValue(col.formula, row);
11187
+ next[key] = core.memoizedFormulaValue(
11188
+ col.formula,
11189
+ row,
11190
+ formulaTables
11191
+ );
10760
11192
  shadow = next;
10761
11193
  }
10762
11194
  return shadow;
@@ -10787,7 +11219,7 @@ function resolveInitialWidth(col) {
10787
11219
  }
10788
11220
  return DEFAULT_COL_WIDTH;
10789
11221
  }
10790
- function computeVisibleColSet(enabled, columns, scrollLeft, viewportWidth, widths) {
11222
+ function computeVisibleColSet(enabled, columns, scrollLeft, viewportWidth, widths, pinOf) {
10791
11223
  if (!enabled) return null;
10792
11224
  const range = core.computeVirtualRange({
10793
11225
  itemCount: columns.length,
@@ -10799,7 +11231,7 @@ function computeVisibleColSet(enabled, columns, scrollLeft, viewportWidth, width
10799
11231
  const visible = /* @__PURE__ */ new Set();
10800
11232
  for (let index = range.startIndex; index <= range.endIndex; index += 1) visible.add(index);
10801
11233
  columns.forEach((column, index) => {
10802
- if (column.pinned) visible.add(index);
11234
+ if (pinOf(column) !== null) visible.add(index);
10803
11235
  });
10804
11236
  return visible;
10805
11237
  }
@@ -10808,18 +11240,21 @@ function cellId(rowIdent, columnKey) {
10808
11240
  }
10809
11241
 
10810
11242
  // src/primitives/table/useTableSort.ts
10811
- function buildSorter(col) {
11243
+ function buildSorter(col, formulaTables) {
10812
11244
  if (col.sorter) return col.sorter;
10813
- return (a, b) => core.compareValues(getCellValue(a, col), getCellValue(b, col));
11245
+ return (a, b) => core.compareValues(getCellValue(a, col, formulaTables), getCellValue(b, col, formulaTables));
10814
11246
  }
10815
- function buildMultiSortComparator(leafColumns, state) {
11247
+ function buildMultiSortComparator(leafColumns, state, formulaTables) {
10816
11248
  if (state.length === 0) return null;
10817
11249
  const colMap = new Map(leafColumns.map((c) => [c.key, c]));
10818
11250
  const chain = [];
10819
11251
  for (const s of state) {
10820
11252
  const col = colMap.get(s.key);
10821
11253
  if (!col) continue;
10822
- chain.push({ dir: s.direction === "asc" ? 1 : -1, sorter: buildSorter(col) });
11254
+ chain.push({
11255
+ dir: s.direction === "asc" ? 1 : -1,
11256
+ sorter: buildSorter(col, formulaTables)
11257
+ });
10823
11258
  }
10824
11259
  if (chain.length === 0) return null;
10825
11260
  return (a, b) => {
@@ -10830,93 +11265,6 @@ function buildMultiSortComparator(leafColumns, state) {
10830
11265
  return 0;
10831
11266
  };
10832
11267
  }
10833
- function useTableSort(data, options) {
10834
- const sortProp = vue.computed(() => options.sort === void 0 ? void 0 : vue.toValue(options.sort));
10835
- const internalSortValue = vue.ref(options.defaultSort ?? null);
10836
- const sortState = vue.computed({
10837
- get: () => sortProp.value === void 0 ? internalSortValue.value : sortProp.value ?? null,
10838
- set: (val) => {
10839
- if (sortProp.value === void 0) internalSortValue.value = val;
10840
- options.onSortChange?.(val);
10841
- }
10842
- });
10843
- const multiEnabled = vue.computed(() => vue.toValue(options.multiSort) === true);
10844
- const multiSortProp = vue.computed(
10845
- () => options.multiSortState === void 0 ? void 0 : vue.toValue(options.multiSortState)
10846
- );
10847
- const internalMultiSort = vue.ref(options.defaultMultiSort ?? []);
10848
- const multiSortState = vue.computed({
10849
- get: () => multiSortProp.value === void 0 ? internalMultiSort.value : multiSortProp.value,
10850
- set: (val) => {
10851
- if (multiSortProp.value === void 0) internalMultiSort.value = val;
10852
- options.onMultiSortChange?.(val);
10853
- }
10854
- });
10855
- const sortComparator = vue.computed(() => {
10856
- const s = sortState.value;
10857
- if (!s) return null;
10858
- const col = vue.toValue(options.leafColumns).find((c) => c.key === s.key);
10859
- if (!col) return null;
10860
- const dir = s.direction === "asc" ? 1 : -1;
10861
- return (a, b) => buildSorter(col)(a, b) * dir;
10862
- });
10863
- const multiSortComparator = vue.computed(
10864
- () => buildMultiSortComparator(vue.toValue(options.leafColumns), multiSortState.value)
10865
- );
10866
- const sortedData = vue.computed(() => {
10867
- const comparator = multiEnabled.value ? multiSortComparator.value : sortComparator.value;
10868
- if (!comparator) return data.value;
10869
- return [...data.value].sort(comparator);
10870
- });
10871
- function setSort(next) {
10872
- if (sortProp.value === void 0) internalSortValue.value = next;
10873
- options.onSortChange?.(next);
10874
- }
10875
- function cycleSort(col) {
10876
- if (!col.sortable) return;
10877
- const s = sortState.value;
10878
- if (!s || s.key !== col.key) {
10879
- setSort({ key: col.key, direction: "asc" });
10880
- return;
10881
- }
10882
- if (s.direction === "asc") {
10883
- setSort({ key: col.key, direction: "desc" });
10884
- return;
10885
- }
10886
- setSort(null);
10887
- }
10888
- function setMultiSort(next) {
10889
- if (multiSortProp.value === void 0) internalMultiSort.value = next;
10890
- options.onMultiSortChange?.(next);
10891
- }
10892
- function cycleMultiSort(col) {
10893
- if (!col.sortable) return;
10894
- const idx = multiSortState.value.findIndex((s) => s.key === col.key);
10895
- if (idx < 0) {
10896
- setMultiSort([...multiSortState.value, { key: col.key, direction: "asc" }]);
10897
- return;
10898
- }
10899
- const next = [...multiSortState.value];
10900
- if (next[idx].direction === "asc") {
10901
- next[idx] = { key: col.key, direction: "desc" };
10902
- setMultiSort(next);
10903
- return;
10904
- }
10905
- next.splice(idx, 1);
10906
- setMultiSort(next);
10907
- }
10908
- return {
10909
- sortState,
10910
- cycleSort,
10911
- setSort,
10912
- sortComparator,
10913
- sortedData,
10914
- multiSortState,
10915
- cycleMultiSort,
10916
- setMultiSort,
10917
- multiSortComparator
10918
- };
10919
- }
10920
11268
  function mergeFilterValues(filters, filterValues) {
10921
11269
  const next = { ...filters };
10922
11270
  for (const [key, values] of Object.entries(filterValues)) {
@@ -11033,6 +11381,37 @@ async function downloadCsv(filename, csv) {
11033
11381
  mimeType: "text/csv;charset=utf-8;"
11034
11382
  });
11035
11383
  }
11384
+ var undoButtonStyle = {
11385
+ border: "none",
11386
+ background: "transparent",
11387
+ cursor: "pointer",
11388
+ color: "var(--iris-muted)",
11389
+ fontSize: "var(--iris-font-size-md, 14px)"
11390
+ };
11391
+ var undoButton = (dataAttr, labelKey, active, onActivate, t) => vue.h(
11392
+ "button",
11393
+ {
11394
+ type: "button",
11395
+ [dataAttr]: "",
11396
+ onClick: onActivate,
11397
+ disabled: !active,
11398
+ "aria-label": t(labelKey),
11399
+ title: t(labelKey),
11400
+ style: { ...undoButtonStyle, cursor: active ? "pointer" : "default" }
11401
+ },
11402
+ dataAttr === "data-iris-table-undo" ? "\u21B6" : "\u21B7"
11403
+ );
11404
+ function renderUndoToolbar(ctx) {
11405
+ if (!ctx.enabled) return [];
11406
+ const canUndo = ctx.controller.canUndo.value;
11407
+ const canRedo = ctx.controller.canRedo.value;
11408
+ return [
11409
+ undoButton("data-iris-table-undo", "table.undo", canUndo, ctx.controller.undo, ctx.t),
11410
+ undoButton("data-iris-table-redo", "table.redo", canRedo, ctx.controller.redo, ctx.t)
11411
+ ];
11412
+ }
11413
+
11414
+ // src/primitives/table/table-sections.ts
11036
11415
  function renderFormSection(ctx) {
11037
11416
  const fc = ctx.formConfig;
11038
11417
  if (!fc) return null;
@@ -11123,13 +11502,14 @@ var toolbarBtnStyle = {
11123
11502
  };
11124
11503
  function renderToolbarSection(ctx) {
11125
11504
  const tb = ctx.toolbar;
11126
- if (!tb && !ctx.densityToggle && !ctx.auditLog) return null;
11505
+ if (!tb && !ctx.densityToggle && !ctx.auditLog && !ctx.undo.enabled) return null;
11127
11506
  const toolChildren = [];
11128
11507
  if (tb?.title) {
11129
11508
  toolChildren.push(
11130
11509
  vue.h("span", { style: { fontWeight: 600, color: "var(--iris-foreground)" } }, tb.title)
11131
11510
  );
11132
11511
  }
11512
+ toolChildren.push(...renderUndoToolbar(ctx.undo));
11133
11513
  toolChildren.push(vue.h("div", { style: { flex: "1" } }));
11134
11514
  if (tb?.onRefresh) {
11135
11515
  toolChildren.push(
@@ -11405,6 +11785,15 @@ function renderContextMenuSection(ctx) {
11405
11785
  );
11406
11786
  return vue.h(vue.Teleport, { to: "body" }, [node]);
11407
11787
  }
11788
+ function recentFilterLabel(entry, columns) {
11789
+ const col = columns.find((column) => column.key === entry.key);
11790
+ if (!col) return entry.values.join(", ");
11791
+ const labels = entry.values.map((value) => {
11792
+ const option = col.filterOptions?.find((item) => item.value === value);
11793
+ return option ? option.label : value;
11794
+ });
11795
+ return `${col.title ?? entry.key}: ${labels.join(", ")}`;
11796
+ }
11408
11797
  function renderFilterPanelSection(ctx) {
11409
11798
  const st = ctx.state.value;
11410
11799
  if (!st || !st.open) return null;
@@ -11437,6 +11826,49 @@ function renderFilterPanelSection(ctx) {
11437
11826
  }
11438
11827
  },
11439
11828
  [
11829
+ ...ctx.recent.length > 0 ? [
11830
+ vue.h(
11831
+ "div",
11832
+ {
11833
+ "data-iris-filter-recent-title": "",
11834
+ style: {
11835
+ color: "var(--iris-muted)",
11836
+ fontSize: "var(--iris-font-size-xs, 11px)",
11837
+ marginTop: "var(--iris-space-xxs, 4px)"
11838
+ }
11839
+ },
11840
+ ctx.t("table.recentFilters")
11841
+ ),
11842
+ ...ctx.recent.map(
11843
+ (entry, index) => vue.h(
11844
+ "button",
11845
+ {
11846
+ key: `${entry.key}:${index}`,
11847
+ type: "button",
11848
+ "data-iris-filter-recent": index,
11849
+ onClick: () => ctx.onApplyRecent(entry),
11850
+ style: {
11851
+ border: "none",
11852
+ background: "transparent",
11853
+ color: "var(--iris-foreground)",
11854
+ cursor: "pointer",
11855
+ font: "inherit",
11856
+ fontSize: "var(--iris-font-size-sm, 13px)",
11857
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
11858
+ borderRadius: "var(--iris-radius-sm, 4px)",
11859
+ textAlign: "start"
11860
+ }
11861
+ },
11862
+ recentFilterLabel(entry, ctx.columns)
11863
+ )
11864
+ ),
11865
+ vue.h("div", {
11866
+ style: {
11867
+ borderTop: "1px solid var(--iris-border)",
11868
+ margin: "var(--iris-space-xxs, 4px) 0"
11869
+ }
11870
+ })
11871
+ ] : [],
11440
11872
  ...options.map(
11441
11873
  (opt) => vue.h(
11442
11874
  "div",
@@ -11920,6 +12352,7 @@ function renderTableSummaryRow(ctx) {
11920
12352
  if (ctx.visibleColSet && !ctx.visibleColSet.has(columnIndex)) continue;
11921
12353
  const align = column.align ?? "left";
11922
12354
  const operation = column.summary;
12355
+ const fadeStyle = ctx.columnFadeStyle(column);
11923
12356
  const value = operation ? core.aggregate(ctx.bodyData, (row) => ctx.getCellValue(row, column), operation) : null;
11924
12357
  const content = operation != null && value != null ? column.renderSummary ? column.renderSummary(value, ctx.bodyData) : String(value) : "";
11925
12358
  summaryCells.push(
@@ -11930,7 +12363,10 @@ function renderTableSummaryRow(ctx) {
11930
12363
  role: "cell",
11931
12364
  "data-iris-table-cell": column.key,
11932
12365
  "data-iris-table-summary-cell": operation ? "" : void 0,
11933
- "data-iris-table-pinned": column.pinned,
12366
+ "data-iris-table-pinned": ctx.pinOf(column),
12367
+ "data-iris-column-fade": ctx.columnFadeAttr(column),
12368
+ "aria-hidden": fadeStyle ? "true" : void 0,
12369
+ inert: fadeStyle ? "" : void 0,
11934
12370
  style: {
11935
12371
  display: "flex",
11936
12372
  alignItems: "center",
@@ -11941,6 +12377,7 @@ function renderTableSummaryRow(ctx) {
11941
12377
  overflow: "hidden",
11942
12378
  textOverflow: "ellipsis",
11943
12379
  ...ctx.visibleColSet ? { gridColumnStart: String(ctx.colTrack(columnIndex)) } : {},
12380
+ ...fadeStyle ?? {},
11944
12381
  ...ctx.pinnedStyle(column.key)
11945
12382
  }
11946
12383
  },
@@ -11964,6 +12401,187 @@ function renderTableSummaryRow(ctx) {
11964
12401
  summaryCells
11965
12402
  );
11966
12403
  }
12404
+ var FADE_DURATION_MS = 200;
12405
+ function createTableColumnFade(options) {
12406
+ const fadeOverlay = vue.shallowRef({});
12407
+ const previousVisibility = vue.ref({ ...options.visibility() });
12408
+ let fadeFlipRaf = null;
12409
+ let fadeCommitTimer = null;
12410
+ let fadeFocusCandidate = null;
12411
+ let disposed = false;
12412
+ const topLevelColumn = (key) => options.columns.value.find((column) => column.key === key);
12413
+ const fadeFlip = (current) => {
12414
+ let changed = false;
12415
+ const next = {};
12416
+ for (const [key, entry] of Object.entries(current)) {
12417
+ if (entry.phase === "pending") {
12418
+ next[key] = { dir: entry.dir, phase: "run" };
12419
+ changed = true;
12420
+ } else {
12421
+ next[key] = entry;
12422
+ }
12423
+ }
12424
+ return changed ? next : void 0;
12425
+ };
12426
+ const fadeCommit = (current) => {
12427
+ let changed = false;
12428
+ const next = {};
12429
+ const visibility = options.visibility();
12430
+ for (const [key, entry] of Object.entries(current)) {
12431
+ const visible = visibility[key] !== false;
12432
+ const done = entry.dir === "out" ? !visible : visible;
12433
+ if (done) changed = true;
12434
+ else next[key] = entry;
12435
+ }
12436
+ return changed ? next : void 0;
12437
+ };
12438
+ const cancelFadeSchedule = () => {
12439
+ fadeFocusCandidate = null;
12440
+ if (fadeFlipRaf !== null) {
12441
+ if (typeof globalThis.cancelAnimationFrame === "function") {
12442
+ globalThis.cancelAnimationFrame(fadeFlipRaf);
12443
+ }
12444
+ fadeFlipRaf = null;
12445
+ }
12446
+ if (fadeCommitTimer !== null) {
12447
+ clearTimeout(fadeCommitTimer);
12448
+ fadeCommitTimer = null;
12449
+ }
12450
+ };
12451
+ const recoverFocus = (candidate) => {
12452
+ if (disposed || typeof document === "undefined" || typeof HTMLElement === "undefined" || !(candidate instanceof HTMLElement))
12453
+ return;
12454
+ const hidden = candidate.closest("[data-iris-column-fade][inert]");
12455
+ if (!hidden) return;
12456
+ const root = hidden.closest("[data-iris-table]");
12457
+ const rowIndex = hidden.dataset.gridRow;
12458
+ if (root && rowIndex !== void 0) {
12459
+ const currentColumn = Number(hidden.dataset.gridCol);
12460
+ const alternatives = Array.from(
12461
+ root.querySelectorAll("[data-grid-row][data-grid-col]")
12462
+ ).filter((cell) => cell.dataset.gridRow === rowIndex && !cell.hasAttribute("inert")).sort(
12463
+ (a, b) => Math.abs(Number(a.dataset.gridCol) - currentColumn) - Math.abs(Number(b.dataset.gridCol) - currentColumn)
12464
+ );
12465
+ if (alternatives[0]) {
12466
+ alternatives[0].focus();
12467
+ return;
12468
+ }
12469
+ }
12470
+ if (document.activeElement === candidate || hidden.contains(document.activeElement)) {
12471
+ candidate.blur();
12472
+ }
12473
+ };
12474
+ vue.watch(
12475
+ [options.visibility, options.enabled, options.reducedMotion],
12476
+ ([visibility, fadeEnabled, motion]) => {
12477
+ const next = visibility;
12478
+ const previous = previousVisibility.value;
12479
+ previousVisibility.value = { ...next };
12480
+ if (!fadeEnabled || motion) {
12481
+ cancelFadeSchedule();
12482
+ fadeOverlay.value = {};
12483
+ return;
12484
+ }
12485
+ const overlay = { ...fadeOverlay.value };
12486
+ const activeElement = typeof document !== "undefined" ? document.activeElement : null;
12487
+ const changedKeys = /* @__PURE__ */ new Set();
12488
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(next)])) {
12489
+ if (!topLevelColumn(key)) continue;
12490
+ const wasVisible = previous[key] !== false;
12491
+ const isVisible = next[key] !== false;
12492
+ if (wasVisible === isVisible) continue;
12493
+ overlay[key] = { dir: isVisible ? "in" : "out", phase: "pending" };
12494
+ changedKeys.add(key);
12495
+ }
12496
+ if (changedKeys.size === 0) return;
12497
+ fadeOverlay.value = overlay;
12498
+ fadeFocusCandidate = activeElement;
12499
+ if (typeof window === "undefined" || typeof window.requestAnimationFrame !== "function") {
12500
+ cancelFadeSchedule();
12501
+ fadeOverlay.value = {};
12502
+ return;
12503
+ }
12504
+ if (fadeFlipRaf === null) {
12505
+ fadeFlipRaf = window.requestAnimationFrame(() => {
12506
+ if (disposed) return;
12507
+ fadeFlipRaf = null;
12508
+ fadeFlipRaf = window.requestAnimationFrame(() => {
12509
+ if (disposed) return;
12510
+ fadeFlipRaf = null;
12511
+ const candidate = fadeFocusCandidate;
12512
+ fadeOverlay.value = fadeFlip(fadeOverlay.value) ?? fadeOverlay.value;
12513
+ void vue.nextTick(() => recoverFocus(candidate));
12514
+ });
12515
+ });
12516
+ }
12517
+ if (fadeCommitTimer !== null) clearTimeout(fadeCommitTimer);
12518
+ fadeCommitTimer = setTimeout(() => {
12519
+ if (disposed) return;
12520
+ fadeCommitTimer = null;
12521
+ fadeOverlay.value = fadeCommit(fadeOverlay.value) ?? fadeOverlay.value;
12522
+ }, FADE_DURATION_MS);
12523
+ }
12524
+ );
12525
+ vue.onBeforeUnmount(() => {
12526
+ disposed = true;
12527
+ cancelFadeSchedule();
12528
+ });
12529
+ const effectiveVisibility = vue.computed(() => {
12530
+ if (Object.keys(fadeOverlay.value).length === 0) return options.visibility();
12531
+ const merged = { ...options.visibility() };
12532
+ for (const key of Object.keys(fadeOverlay.value)) merged[key] = true;
12533
+ return merged;
12534
+ });
12535
+ const displayColumns = vue.computed(() => {
12536
+ const visibility = effectiveVisibility.value;
12537
+ if (Object.keys(visibility).length === 0) return options.columns.value;
12538
+ return options.columns.value.filter((column) => visibility[column.key] !== false);
12539
+ });
12540
+ const fadeByLeaf = vue.computed(() => {
12541
+ if (Object.keys(fadeOverlay.value).length === 0) return {};
12542
+ const out = {};
12543
+ for (const [key, entry] of Object.entries(fadeOverlay.value)) {
12544
+ const top = topLevelColumn(key);
12545
+ if (!top) continue;
12546
+ const leaves = top.children && top.children.length > 0 ? core.flattenLeafColumns([top]) : [top];
12547
+ for (const leaf of leaves) out[leaf.key] = entry;
12548
+ }
12549
+ return out;
12550
+ });
12551
+ const columnFadeAttr = (column) => (fadeOverlay.value[column.key] ?? fadeByLeaf.value[column.key])?.dir;
12552
+ const columnFadeStyle = (column) => {
12553
+ const entry = fadeOverlay.value[column.key] ?? fadeByLeaf.value[column.key];
12554
+ if (!entry) return null;
12555
+ const hidden = entry.dir === "out" ? entry.phase === "run" : entry.phase === "pending";
12556
+ return hidden ? { opacity: "0" } : null;
12557
+ };
12558
+ const columnFadeAttrs = (column) => {
12559
+ const hidden = columnFadeStyle(column) !== null;
12560
+ return {
12561
+ "data-iris-column-fade": columnFadeAttr(column),
12562
+ "aria-hidden": hidden ? "true" : void 0,
12563
+ inert: hidden ? "" : void 0
12564
+ };
12565
+ };
12566
+ const columnFadeActive = vue.computed(
12567
+ () => options.enabled() === true && Object.keys(fadeByLeaf.value).length > 0
12568
+ );
12569
+ const isCollapsed = (key) => {
12570
+ const entry = fadeByLeaf.value[key];
12571
+ return Boolean(
12572
+ entry && (entry.dir === "out" && entry.phase === "run" || entry.dir === "in" && entry.phase === "pending")
12573
+ );
12574
+ };
12575
+ return {
12576
+ displayColumns,
12577
+ fadeByLeaf,
12578
+ columnFadeAttr,
12579
+ columnFadeStyle,
12580
+ columnFadeAttrs,
12581
+ columnFadeActive,
12582
+ isCollapsed
12583
+ };
12584
+ }
11967
12585
  function renderTableStateRow(ctx) {
11968
12586
  if (ctx.error) {
11969
12587
  return vue.h("div", { role: "row", "data-iris-table-row": "error", style: ctx.stateRowStyle }, [
@@ -12073,24 +12691,25 @@ function createTableKeyboard(options) {
12073
12691
  const range = activeCellRange();
12074
12692
  const clip = options.clipConfig();
12075
12693
  if (!range || clip?.copy === false) return;
12076
- const rows = withComputedFormulaCells(options.rows(), options.columns());
12077
- void core.writeClipboardText(
12078
- core.serializeTableRange(
12079
- rows,
12080
- options.columns(),
12081
- range,
12082
- clip?.copyFormat,
12083
- clip?.copyWithFormat === true
12084
- )
12085
- );
12694
+ const text = options.serializeRange(clip?.copyFormat, clip?.copyWithFormat === true);
12695
+ if (text !== null) void core.writeClipboardText(text);
12086
12696
  };
12087
12697
  const handleClipboardKey = (event) => {
12088
12698
  const clip = options.clipConfig();
12089
12699
  if (!options.cellRange() || !clip || event.defaultPrevented) return;
12090
- if (event.key.toLowerCase() !== "c" || !event.ctrlKey && !event.metaKey) return;
12091
- if (clip.copy === false || !activeCellRange()) return;
12092
- event.preventDefault();
12093
- copyActiveRange();
12700
+ if (!event.ctrlKey && !event.metaKey) return;
12701
+ const key = event.key.toLowerCase();
12702
+ const range = activeCellRange();
12703
+ if (!range) return;
12704
+ if (key === "c") {
12705
+ if (clip.copy === false) return;
12706
+ event.preventDefault();
12707
+ copyActiveRange();
12708
+ } else if (key === "v") {
12709
+ if (clip.paste === false) return;
12710
+ event.preventDefault();
12711
+ options.pasteRange(range);
12712
+ }
12094
12713
  };
12095
12714
  return {
12096
12715
  handleRootKeyDown: (event) => {
@@ -12103,11 +12722,11 @@ function createTableKeyboard(options) {
12103
12722
  copyActiveRange
12104
12723
  };
12105
12724
  }
12106
- function computeResponsiveTableColumns(columns, containerWidth, leadingWidth, widthOf) {
12725
+ function computeResponsiveTableColumns(columns, containerWidth, leadingWidth, widthOf, pinOf) {
12107
12726
  if (containerWidth <= 0 || containerWidth >= core.RESPONSIVE_NARROW_WIDTH) {
12108
12727
  return { columns, overflow: false };
12109
12728
  }
12110
- const isPinned = (column) => column.children && column.children.length > 0 ? column.children.some(isPinned) : column.pinned !== void 0;
12729
+ const isPinned = (column) => column.children && column.children.length > 0 ? column.children.some(isPinned) : pinOf(column) !== null;
12111
12730
  const fitted = core.computeResponsiveColumns(columns, Math.max(1, containerWidth - leadingWidth), {
12112
12731
  widthOf: (column) => widthOf(column),
12113
12732
  isPinned: (column) => isPinned(column),
@@ -12122,6 +12741,7 @@ function computeResponsiveTableColumns(columns, containerWidth, leadingWidth, wi
12122
12741
 
12123
12742
  // src/primitives/table/table-styles.ts
12124
12743
  var TABLE_STYLE_ID = "iris-table-row-styles";
12744
+ var TABLE_FADE_STYLE_ID = "iris-table-column-fade-styles-vue";
12125
12745
  var TABLE_STYLES = `
12126
12746
  [data-iris-table] [role="row"]:hover {
12127
12747
  --iris-cell-bg: var(--iris-surface-hover);
@@ -12143,15 +12763,52 @@ var TABLE_STYLES = `
12143
12763
  background: color-mix(in srgb, var(--iris-primary) 18%, var(--iris-background));
12144
12764
  }
12145
12765
  `;
12146
- function ensureTableStyles() {
12147
- if (typeof document === "undefined" || document.getElementById(TABLE_STYLE_ID)) return;
12148
- const style = document.createElement("style");
12149
- style.id = TABLE_STYLE_ID;
12150
- style.textContent = TABLE_STYLES;
12151
- document.head.appendChild(style);
12766
+ var TABLE_FADE_STYLES = `
12767
+ [data-iris-column-fade-active] [data-iris-column-fade] {
12768
+ transition: opacity var(--iris-duration-md, 200ms) ease;
12769
+ }
12770
+ [data-iris-column-fade-active] [role="row"],
12771
+ [data-iris-column-fade-active][role="row"] {
12772
+ transition: grid-template-columns var(--iris-duration-md, 200ms) ease;
12773
+ }
12774
+ @media (prefers-reduced-motion: reduce) {
12775
+ [data-iris-column-fade-active] [data-iris-column-fade],
12776
+ [data-iris-column-fade-active][data-iris-column-fade],
12777
+ [data-iris-column-fade-active] [role="row"],
12778
+ [data-iris-column-fade-active][role="row"] {
12779
+ transition: none !important;
12780
+ }
12781
+ }
12782
+ `;
12783
+ function ensureTableStyles(includeFade = false) {
12784
+ if (typeof document === "undefined") return;
12785
+ if (!document.getElementById(TABLE_STYLE_ID)) {
12786
+ const style = document.createElement("style");
12787
+ style.id = TABLE_STYLE_ID;
12788
+ style.textContent = TABLE_STYLES;
12789
+ document.head.appendChild(style);
12790
+ }
12791
+ if (!includeFade) return;
12792
+ if (!document.getElementById(TABLE_FADE_STYLE_ID)) {
12793
+ const style = document.createElement("style");
12794
+ style.id = TABLE_FADE_STYLE_ID;
12795
+ style.textContent = TABLE_FADE_STYLES;
12796
+ document.head.appendChild(style);
12797
+ }
12152
12798
  }
12153
12799
 
12154
12800
  // src/primitives/table/table-columns.ts
12801
+ function applyTableColumnOrder(columns, order) {
12802
+ if (!order || order.length === 0) return columns;
12803
+ const orderIndex = /* @__PURE__ */ new Map();
12804
+ order.forEach((key, index) => {
12805
+ if (!orderIndex.has(key)) orderIndex.set(key, index);
12806
+ });
12807
+ const ordered = columns.filter((column) => orderIndex.has(column.key));
12808
+ const rest = columns.filter((column) => !orderIndex.has(column.key));
12809
+ ordered.sort((left, right) => orderIndex.get(left.key) - orderIndex.get(right.key));
12810
+ return [...ordered, ...rest];
12811
+ }
12155
12812
  function applyDetectedTableTypes(columns, detectedTypes) {
12156
12813
  const apply = (column) => {
12157
12814
  const kind = detectedTypes[column.key];
@@ -12249,16 +12906,24 @@ function createTableRowTarget(root) {
12249
12906
  };
12250
12907
  return { find, scrollTo, goTo, dispose };
12251
12908
  }
12252
- function createTablePinnedDrag(options) {
12909
+ function leftPinnedCount(columns, pinOf, cap) {
12910
+ let count = 0;
12911
+ for (let index = 0; index < cap; index += 1) {
12912
+ if (pinOf(columns[index]) === "left") count = index + 1;
12913
+ else break;
12914
+ }
12915
+ return count;
12916
+ }
12917
+ function createTablePinnedDrag(options) {
12253
12918
  const firstRightPinnedIndex = vue.computed(() => {
12254
- const index = options.columns().findIndex((column) => column.pinned === "right");
12919
+ const index = options.columns().findIndex((column) => options.pinOf(column) === "right");
12255
12920
  return index < 0 ? options.columns().length : index;
12256
12921
  });
12257
12922
  const pinnedBoundaryColumn = vue.computed(() => {
12258
12923
  if (!options.enabled()) return null;
12259
12924
  for (let index = firstRightPinnedIndex.value - 1; index >= 0; index -= 1) {
12260
12925
  const column = options.columns()[index];
12261
- if (column?.pinned === "left") return column;
12926
+ if (column && options.pinOf(column) === "left") return column;
12262
12927
  }
12263
12928
  return null;
12264
12929
  });
@@ -12277,7 +12942,7 @@ function createTablePinnedDrag(options) {
12277
12942
  const resolvePinnedCount = (dx) => {
12278
12943
  const columns = options.columns();
12279
12944
  const cap = firstRightPinnedIndex.value;
12280
- const current = core.leftPinnedCount(columns, cap);
12945
+ const current = leftPinnedCount(columns, options.pinOf, cap);
12281
12946
  let currentWidth = 0;
12282
12947
  for (let index = 0; index < current; index += 1) {
12283
12948
  const column = columns[index];
@@ -12295,14 +12960,14 @@ function createTablePinnedDrag(options) {
12295
12960
  const columns = options.columns();
12296
12961
  const cap = firstRightPinnedIndex.value;
12297
12962
  const clamped = Math.max(0, Math.min(cap, count));
12298
- const current = core.leftPinnedCount(columns, cap);
12963
+ const current = leftPinnedCount(columns, options.pinOf, cap);
12299
12964
  if (clamped === current) return;
12300
12965
  for (let index = 0; index < cap; index += 1) {
12301
12966
  const column = columns[index];
12302
12967
  if (!column) continue;
12303
12968
  const target = index < clamped ? "left" : null;
12304
- if (column.pinned === target) continue;
12305
- options.onColumnPinnedChange?.(column.key, target);
12969
+ if (options.pinOf(column) === target) continue;
12970
+ options.setPinned(column.key, target);
12306
12971
  }
12307
12972
  options.onPinnedCountChange?.(clamped);
12308
12973
  };
@@ -12382,6 +13047,218 @@ function createTablePinnedDrag(options) {
12382
13047
  );
12383
13048
  };
12384
13049
  }
13050
+ function isInsideTableRoot(root, target) {
13051
+ return target !== null && typeof target === "object" && "nodeType" in target && root() !== null && root().contains(target);
13052
+ }
13053
+ function isTextControl(target) {
13054
+ if (!target || typeof target !== "object") return false;
13055
+ const element = target;
13056
+ return element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT" || element.dataset?.irisTableEditor !== void 0;
13057
+ }
13058
+ function registerScopedKeydownListener(gate, handler) {
13059
+ let listening = false;
13060
+ let stopGateWatch = null;
13061
+ const syncListener = () => {
13062
+ if (typeof window === "undefined") return;
13063
+ if (gate() && !listening) {
13064
+ window.addEventListener("keydown", handler);
13065
+ listening = true;
13066
+ } else if (!gate() && listening) {
13067
+ window.removeEventListener("keydown", handler);
13068
+ listening = false;
13069
+ }
13070
+ };
13071
+ vue.onMounted(() => {
13072
+ syncListener();
13073
+ stopGateWatch = vue.watch(gate, syncListener);
13074
+ });
13075
+ vue.onBeforeUnmount(() => {
13076
+ stopGateWatch?.();
13077
+ stopGateWatch = null;
13078
+ if (listening && typeof window !== "undefined") {
13079
+ window.removeEventListener("keydown", handler);
13080
+ listening = false;
13081
+ }
13082
+ });
13083
+ }
13084
+
13085
+ // src/primitives/table/table-undo.ts
13086
+ function createCommittedList(source) {
13087
+ let current = source();
13088
+ return {
13089
+ list: () => {
13090
+ const latest = source();
13091
+ if (latest !== current) current = latest;
13092
+ return current;
13093
+ },
13094
+ sync: (next) => {
13095
+ current = next;
13096
+ }
13097
+ };
13098
+ }
13099
+ function replaceTableCell(rows, key, columnKey, value, rowKeyOf) {
13100
+ const index = rows.findIndex((candidate, i) => rowKeyOf(candidate, i) === key);
13101
+ if (index < 0) return rows;
13102
+ const next = rows.slice();
13103
+ next[index] = { ...next[index], [columnKey]: value };
13104
+ return next;
13105
+ }
13106
+ function sameRowList(a, b) {
13107
+ if (a === b) return true;
13108
+ if (a.length !== b.length) return false;
13109
+ for (let i = 0; i < a.length; i += 1) {
13110
+ if (a[i] !== b[i]) return false;
13111
+ }
13112
+ return true;
13113
+ }
13114
+ function rebaselineOnExternalChange(ctx, stack, bump) {
13115
+ let lastExternalRows = ctx.sourceRows();
13116
+ vue.watch(ctx.sourceRows, (next) => {
13117
+ if (next === lastExternalRows) return;
13118
+ lastExternalRows = next;
13119
+ if (ctx.enabled() && !stack.canUndo() && !stack.canRedo()) {
13120
+ stack.clear();
13121
+ stack.push([...next ?? []]);
13122
+ bump();
13123
+ }
13124
+ });
13125
+ }
13126
+ function replayStep(ctx, commit, setRestoring) {
13127
+ return (rows, type) => {
13128
+ if (rows === void 0) return;
13129
+ const before = ctx.selection.current();
13130
+ if (ctx.selection.enabled() && before.length > 0) {
13131
+ const keys = /* @__PURE__ */ new Set();
13132
+ rows.forEach((row, index) => keys.add(ctx.selection.keyOf(row, index)));
13133
+ const vanished = before.filter((key) => !keys.has(key));
13134
+ if (vanished.length > 0) {
13135
+ ctx.selection.rebase();
13136
+ ctx.selection.set(before.filter((key) => !vanished.includes(key)));
13137
+ }
13138
+ }
13139
+ setRestoring(true);
13140
+ try {
13141
+ commit(rows, type);
13142
+ } finally {
13143
+ setRestoring(false);
13144
+ }
13145
+ };
13146
+ }
13147
+ function undoKeydownBindings(ctx, onUndo, onRedo) {
13148
+ const bindings = core.normalizeKeymap();
13149
+ return (event) => {
13150
+ if (!ctx.enabled() || event.defaultPrevented) return;
13151
+ if (!isInsideTableRoot(ctx.root, event.target)) return;
13152
+ if (isTextControl(event.target) || ctx.isEditing()) return;
13153
+ if (core.matchTableKey(event, bindings.undo)) {
13154
+ event.preventDefault();
13155
+ onUndo();
13156
+ } else if (core.matchTableKey(event, bindings.redo)) {
13157
+ event.preventDefault();
13158
+ onRedo();
13159
+ }
13160
+ };
13161
+ }
13162
+ function createTableUndoRuntime(ctx) {
13163
+ const stack = core.createUndoStack({
13164
+ maxHistory: 100,
13165
+ initial: [...ctx.initialRows() ?? []],
13166
+ equals: sameRowList
13167
+ });
13168
+ const tick = vue.ref(0);
13169
+ let restoring = false;
13170
+ const bump = () => {
13171
+ tick.value += 1;
13172
+ };
13173
+ const record = (rows) => {
13174
+ if (!ctx.enabled() || restoring) return;
13175
+ stack.push([...rows ?? []]);
13176
+ bump();
13177
+ };
13178
+ const commit = (rows, type = "edit") => {
13179
+ record(rows);
13180
+ ctx.recordAudit(rows, type);
13181
+ ctx.setRows(rows);
13182
+ ctx.onDataChange(rows);
13183
+ };
13184
+ return {
13185
+ stack,
13186
+ bump,
13187
+ readTick: () => void tick.value,
13188
+ record,
13189
+ commit,
13190
+ setRestoring: (value) => restoring = value
13191
+ };
13192
+ }
13193
+ function createUndoRedoActions(stack, bump, replay) {
13194
+ const undo = () => {
13195
+ const rows = stack.undo();
13196
+ if (rows !== void 0) {
13197
+ bump();
13198
+ replay(rows, "undo");
13199
+ }
13200
+ };
13201
+ const redo = () => {
13202
+ const rows = stack.redo();
13203
+ if (rows !== void 0) {
13204
+ bump();
13205
+ replay(rows, "redo");
13206
+ }
13207
+ };
13208
+ return { undo, redo };
13209
+ }
13210
+ function createTableUndoController(enabled, initialRows, sourceRows, setRows, recordAudit, onDataChange, root, isEditing, selection) {
13211
+ const ctx = {
13212
+ enabled,
13213
+ initialRows,
13214
+ sourceRows,
13215
+ setRows,
13216
+ recordAudit,
13217
+ onDataChange,
13218
+ root,
13219
+ isEditing,
13220
+ selection
13221
+ };
13222
+ const runtime = createTableUndoRuntime(ctx);
13223
+ const { stack, bump, record, commit } = runtime;
13224
+ const canUndo = vue.computed(() => {
13225
+ runtime.readTick();
13226
+ return stack.canUndo();
13227
+ });
13228
+ const canRedo = vue.computed(() => {
13229
+ runtime.readTick();
13230
+ return stack.canRedo();
13231
+ });
13232
+ rebaselineOnExternalChange(ctx, stack, bump);
13233
+ const replay = replayStep(ctx, commit, runtime.setRestoring);
13234
+ const { undo, redo } = createUndoRedoActions(stack, bump, replay);
13235
+ const handleKeydown = undoKeydownBindings(ctx, undo, redo);
13236
+ registerScopedKeydownListener(() => ctx.enabled(), handleKeydown);
13237
+ return { stack, canUndo, canRedo, record, commit, undo, redo, handleKeydown };
13238
+ }
13239
+ var SEARCH_HIT_STYLE = {
13240
+ background: "var(--iris-surface-selected, rgba(99,102,241,0.12))",
13241
+ color: "inherit",
13242
+ borderRadius: "var(--iris-radius-sm, 4px)",
13243
+ padding: "0 var(--iris-space-xxs, 4px)"
13244
+ };
13245
+ function applySearchHighlight(node, query) {
13246
+ if (!query || typeof node !== "string") return node;
13247
+ const text = String(node);
13248
+ const segments = core.splitSearchHits(text, query);
13249
+ if (!segments) return node;
13250
+ return segments.map(
13251
+ (segment, index) => index % 2 === 1 ? vue.h(
13252
+ "mark",
13253
+ {
13254
+ key: index,
13255
+ "data-iris-search-hit": "",
13256
+ style: SEARCH_HIT_STYLE
13257
+ },
13258
+ segment
13259
+ ) : segment
13260
+ );
13261
+ }
12385
13262
  function useTableImport(isPreviewEnabled, getOnImport) {
12386
13263
  const importFileInput = vue.ref(null);
12387
13264
  const importPreviewRows = vue.ref(null);
@@ -12511,7 +13388,7 @@ function renderCellEditContent(ctx, row, col, index, editCellId) {
12511
13388
  "aria-invalid": error ? "true" : void 0,
12512
13389
  "aria-describedby": error ? `${editCellId}-error` : void 0,
12513
13390
  onInput: (e) => {
12514
- ctx.editingDraft.value = e.target.value;
13391
+ ctx.setEditingDraft(e.target.value);
12515
13392
  },
12516
13393
  onKeydown: (e) => {
12517
13394
  if (e.key === "Enter") {
@@ -12619,6 +13496,7 @@ function renderGroupedHeader(ctx, matrix) {
12619
13496
  const isLeaf = !col.children || col.children.length === 0;
12620
13497
  const sortable = isLeaf && col.sortable;
12621
13498
  const align = col.align ?? "left";
13499
+ const fadeStyle = ctx.columnFadeStyle(col);
12622
13500
  const headerSlot = ctx.slots[`header.${col.key}`];
12623
13501
  const title = headerSlot?.({ column: col }) ?? col.title;
12624
13502
  cells.push(
@@ -12629,8 +13507,13 @@ function renderGroupedHeader(ctx, matrix) {
12629
13507
  role: "columnheader",
12630
13508
  "data-iris-table-header": col.key,
12631
13509
  "data-iris-table-header-group": isLeaf ? void 0 : "",
13510
+ "data-iris-table-pinned": isLeaf && (ctx.pinnedColumnsControlled || ctx.columnPinMenu) ? ctx.pinOf(col) : void 0,
13511
+ "data-iris-column-fade": ctx.columnFadeAttr(col),
13512
+ "aria-hidden": fadeStyle ? "true" : void 0,
13513
+ inert: fadeStyle ? "" : void 0,
12632
13514
  "aria-colspan": cell.colSpan,
12633
13515
  onClick: sortable ? () => ctx.onHeaderClick(col) : void 0,
13516
+ ...isLeaf && ctx.onHeaderContextMenu ? { onContextmenu: (event) => ctx.onHeaderContextMenu(event, col) } : {},
12634
13517
  "aria-sort": sortable ? ctx.ariaSortFor(col) : void 0,
12635
13518
  style: {
12636
13519
  gridColumn: `${lead + cell.colStart} / span ${cell.colSpan}`,
@@ -12648,7 +13531,9 @@ function renderGroupedHeader(ctx, matrix) {
12648
13531
  color: "var(--iris-foreground)",
12649
13532
  whiteSpace: "nowrap",
12650
13533
  overflow: "hidden",
12651
- textOverflow: "ellipsis"
13534
+ textOverflow: "ellipsis",
13535
+ ...fadeStyle ?? {},
13536
+ ...isLeaf && (ctx.pinnedColumnsControlled || ctx.columnPinMenu) && ctx.pinOf(col) !== null ? { ...ctx.pinnedStyle(col.key), background: "var(--iris-surface)" } : {}
12652
13537
  }
12653
13538
  },
12654
13539
  [
@@ -12896,6 +13781,47 @@ function renderTableTabs(tabs, activeTab, onApply) {
12896
13781
  }
12897
13782
 
12898
13783
  // src/primitives/table/Table.ts
13784
+ var SCROLL_TOP_VISIBLE_PX = 200;
13785
+ var PIN_LEFT_MENU_KEY = "__iris-pin-left";
13786
+ var UNPIN_MENU_KEY = "__iris-unpin";
13787
+ var BACK_TOP_ANCHOR_STYLE = {
13788
+ position: "sticky",
13789
+ insetBlockEnd: "0px",
13790
+ height: "0px",
13791
+ pointerEvents: "none",
13792
+ zIndex: "3"
13793
+ };
13794
+ var BACK_TOP_BUTTON_STYLE = {
13795
+ position: "absolute",
13796
+ insetBlockEnd: "24px",
13797
+ insetInlineEnd: "24px",
13798
+ width: "40px",
13799
+ height: "40px",
13800
+ borderRadius: "50%",
13801
+ border: "1px solid var(--iris-border)",
13802
+ background: "var(--iris-surface, var(--iris-background))",
13803
+ color: "var(--iris-foreground)",
13804
+ cursor: "pointer",
13805
+ boxShadow: "var(--iris-shadow-md)",
13806
+ display: "inline-flex",
13807
+ alignItems: "center",
13808
+ justifyContent: "center",
13809
+ fontSize: "var(--iris-font-size-xl, 18px)",
13810
+ pointerEvents: "auto"
13811
+ };
13812
+ async function readClipboardText() {
13813
+ if (typeof navigator === "undefined") return null;
13814
+ const nav = navigator;
13815
+ if (!nav.clipboard?.readText) return null;
13816
+ try {
13817
+ return await nav.clipboard.readText();
13818
+ } catch {
13819
+ return null;
13820
+ }
13821
+ }
13822
+ function prefersReducedMotion() {
13823
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
13824
+ }
12899
13825
  var IrisTable = vue.defineComponent({
12900
13826
  name: "IrisTable",
12901
13827
  inheritAttrs: false,
@@ -12908,13 +13834,22 @@ var IrisTable = vue.defineComponent({
12908
13834
  "update:columnWidths": (_value) => true,
12909
13835
  /** Controlled columnVisibility channel (parent owns the map). */
12910
13836
  "update:columnVisibility": (_value) => true,
13837
+ /** Controlled top-level column-order proposal channel. */
13838
+ "update:columnOrder": (_value) => true,
12911
13839
  rowClick: (_row, _index) => true,
12912
13840
  rowDblclick: (_row, _index) => true,
12913
13841
  cellEdit: (_payload) => true,
12914
13842
  expandedRowsChange: (_keys) => true
12915
13843
  },
12916
13844
  setup(props, { slots, attrs, emit, expose }) {
12917
- ensureTableStyles();
13845
+ ensureTableStyles(props.columnFade);
13846
+ vue.watch(
13847
+ () => props.columnFade,
13848
+ (enabled) => {
13849
+ if (enabled) ensureTableStyles(true);
13850
+ }
13851
+ );
13852
+ const getCellValue2 = (row, column) => getCellValue(row, column, props.formulaTables);
12918
13853
  const { t } = useI18n();
12919
13854
  const densityState = vue.ref("comfortable");
12920
13855
  const effectiveDensity = vue.computed(() => {
@@ -12928,17 +13863,102 @@ var IrisTable = vue.defineComponent({
12928
13863
  () => props.importPreview,
12929
13864
  () => props.toolbar?.onImport
12930
13865
  );
12931
- const responsiveWidth = vue.ref(0);
12932
- const internalWidths = vue.ref({});
12933
- const effectiveWidths = vue.computed(() => {
12934
- if (props.columnWidths) return props.columnWidths;
12935
- return internalWidths.value;
13866
+ const gridCore = useGridCore();
13867
+ const columnsFeature = useGridColumns(gridCore, {
13868
+ pinned: props.pinnedColumns,
13869
+ onVisibilityChange: (next) => emit("update:columnVisibility", next),
13870
+ onOrderChange: (next) => emit("update:columnOrder", next),
13871
+ onWidthsChange: (next) => emit("update:columnWidths", next),
13872
+ onPinnedChange: (key, side) => props.onColumnPinnedChange?.(key, side)
12936
13873
  });
12937
- const sourceDisplayColumns = vue.computed(() => {
12938
- const vis = props.columnVisibility;
12939
- if (!vis) return props.columns;
12940
- return props.columns.filter((c) => vis[c.key] !== false);
13874
+ const pinnedPropControlled = vue.ref(props.pinnedColumns !== void 0);
13875
+ const pinOf = (column) => {
13876
+ const pinned = props.pinnedColumns;
13877
+ if (pinned !== void 0) {
13878
+ if (Object.prototype.hasOwnProperty.call(pinned, column.key)) {
13879
+ return pinned[column.key] ?? null;
13880
+ }
13881
+ return column.pinned ?? null;
13882
+ }
13883
+ const internal = columnsFeature.state.value.pinned;
13884
+ if (Object.prototype.hasOwnProperty.call(internal, column.key)) {
13885
+ return internal[column.key] ?? null;
13886
+ }
13887
+ return column.pinned ?? null;
13888
+ };
13889
+ vue.watch(
13890
+ () => props.pinnedColumns,
13891
+ (next) => {
13892
+ if (next !== void 0) {
13893
+ pinnedPropControlled.value = true;
13894
+ columnsFeature.model.syncPinned(next);
13895
+ } else {
13896
+ if (pinnedPropControlled.value) columnsFeature.model.syncPinned({});
13897
+ pinnedPropControlled.value = false;
13898
+ }
13899
+ },
13900
+ { immediate: true, flush: "sync" }
13901
+ );
13902
+ const orderPropControlled = vue.ref(props.columnOrder !== void 0);
13903
+ vue.watch(
13904
+ () => props.columnOrder,
13905
+ (next) => {
13906
+ if (next !== void 0) {
13907
+ orderPropControlled.value = true;
13908
+ columnsFeature.model.syncOrder(next);
13909
+ } else {
13910
+ if (orderPropControlled.value) columnsFeature.model.syncOrder([]);
13911
+ orderPropControlled.value = false;
13912
+ }
13913
+ },
13914
+ { immediate: true, flush: "sync" }
13915
+ );
13916
+ const effectiveColumnOrder = vue.computed(
13917
+ () => orderPropControlled.value ? props.columnOrder ?? [] : columnsFeature.state.value.order
13918
+ );
13919
+ const uncontrolledWidths = vue.ref({});
13920
+ const widthPropControlled = vue.ref(props.columnWidths !== void 0);
13921
+ vue.watch(
13922
+ () => props.columnVisibility,
13923
+ (next) => columnsFeature.model.syncVisibility(next ?? {}),
13924
+ { immediate: true }
13925
+ );
13926
+ vue.watch(
13927
+ () => props.columnWidths,
13928
+ (next) => {
13929
+ if (next !== void 0) {
13930
+ columnsFeature.model.syncWidths(next);
13931
+ } else if (widthPropControlled.value) {
13932
+ columnsFeature.model.syncWidths(uncontrolledWidths.value);
13933
+ }
13934
+ widthPropControlled.value = next !== void 0;
13935
+ },
13936
+ { immediate: true }
13937
+ );
13938
+ const responsiveWidth = vue.ref(0);
13939
+ const effectiveWidths = vue.computed(
13940
+ () => props.columnWidths !== void 0 ? props.columnWidths : columnsFeature.state.value.widths
13941
+ );
13942
+ const effectiveVisibility = vue.computed(
13943
+ () => columnsFeature.state.value.visibility
13944
+ );
13945
+ const columnFadeEnabled = vue.computed(() => props.columnFade);
13946
+ const reducedMotion = usePrefersReducedMotion(columnFadeEnabled);
13947
+ const columnFade = createTableColumnFade({
13948
+ visibility: () => effectiveVisibility.value,
13949
+ enabled: () => columnFadeEnabled.value,
13950
+ reducedMotion,
13951
+ columns: vue.computed(() => props.columns)
12941
13952
  });
13953
+ const {
13954
+ displayColumns: sourceDisplayColumns,
13955
+ columnFadeAttr,
13956
+ columnFadeStyle,
13957
+ columnFadeAttrs,
13958
+ columnFadeActive,
13959
+ fadeByLeaf,
13960
+ isCollapsed: isColumnFadeCollapsed
13961
+ } = columnFade;
12942
13962
  const detectedTypes = vue.ref({});
12943
13963
  const detectTypesDone = vue.ref(false);
12944
13964
  const detectedDisplayColumns = vue.computed(() => {
@@ -12954,13 +13974,17 @@ var IrisTable = vue.defineComponent({
12954
13974
  const width = effectiveWidths.value[column.key] ?? resolveInitialWidth(column);
12955
13975
  return Number.isFinite(width) && width >= 0 ? width : resolveInitialWidth(column);
12956
13976
  };
13977
+ const orderedDisplayColumns = vue.computed(
13978
+ () => applyTableColumnOrder(detectedDisplayColumns.value, effectiveColumnOrder.value)
13979
+ );
12957
13980
  const responsiveResult = vue.computed(
12958
13981
  () => props.responsive ? computeResponsiveTableColumns(
12959
- detectedDisplayColumns.value,
13982
+ orderedDisplayColumns.value,
12960
13983
  responsiveWidth.value,
12961
13984
  responsiveLeadingWidth.value,
12962
- responsiveWidthOf
12963
- ) : { columns: detectedDisplayColumns.value, overflow: false }
13985
+ responsiveWidthOf,
13986
+ pinOf
13987
+ ) : { columns: orderedDisplayColumns.value, overflow: false }
12964
13988
  );
12965
13989
  const responsiveDisplayColumns = vue.computed(
12966
13990
  () => responsiveResult.value.columns
@@ -13013,6 +14037,7 @@ var IrisTable = vue.defineComponent({
13013
14037
  const tableData = vue.computed(
13014
14038
  () => proxyCtrl.proxy.value ? proxyCtrl.liveData.value : localRowsOverride.value ?? props.data ?? []
13015
14039
  );
14040
+ const committedList = createCommittedList(() => tableData.value);
13016
14041
  vue.watch(
13017
14042
  [() => props.autoDetectTypes, tableData, () => props.columns],
13018
14043
  () => {
@@ -13022,38 +14047,76 @@ var IrisTable = vue.defineComponent({
13022
14047
  for (const column of core.flattenLeafColumns(props.columns)) {
13023
14048
  if (column.formula) continue;
13024
14049
  next[column.key] = core.detectColumnType(
13025
- tableData.value.map((row) => getCellValue(row, column))
14050
+ tableData.value.map((row) => getCellValue2(row, column))
13026
14051
  );
13027
14052
  }
13028
14053
  detectedTypes.value = next;
13029
14054
  },
13030
14055
  { immediate: true }
13031
14056
  );
13032
- const {
13033
- sortState: internalSort,
13034
- cycleSort,
13035
- setSort,
13036
- sortComparator,
13037
- sortedData: sortedRows,
13038
- multiSortState,
13039
- cycleMultiSort
13040
- } = useTableSort(tableData, {
13041
- leafColumns,
13042
- sort: vue.computed(() => props.sort),
14057
+ let recordUndoRows = null;
14058
+ let suppressUndoRecord = false;
14059
+ const readRowChildren = (row) => {
14060
+ if (props.lazyLoad !== void 0) {
14061
+ const children = row.children;
14062
+ if (Array.isArray(children)) return children;
14063
+ }
14064
+ return props.getSubRows?.(row);
14065
+ };
14066
+ const writeLazyChildren = props.lazyLoad === void 0 ? void 0 : (row, children) => ({
14067
+ ...row,
14068
+ children
14069
+ });
14070
+ const sorting = useGridSorting(gridCore, {
14071
+ mode: props.multiSort ? "multiple" : "single",
13043
14072
  defaultSort: props.defaultSort,
14073
+ defaultMultiSort: props.defaultMultiSort,
13044
14074
  onSortChange: (next) => {
13045
14075
  emit("update:sort", next);
13046
14076
  if (remoteSort.value) proxyCtrl.setParams({ sort: next });
13047
14077
  },
13048
- multiSort: () => props.multiSort,
13049
- multiSortState: () => props.multiSortState,
13050
- defaultMultiSort: props.defaultMultiSort,
13051
14078
  onMultiSortChange: (next) => {
13052
14079
  emit("update:multiSortState", next);
13053
14080
  emit("multiSortChange", next);
13054
14081
  if (remoteSort.value) proxyCtrl.setParams({ sorts: next });
13055
14082
  }
13056
14083
  });
14084
+ vue.watch(
14085
+ () => props.sort,
14086
+ (next) => next !== void 0 && sorting.model.syncSort(next ?? null),
14087
+ { immediate: true }
14088
+ );
14089
+ vue.watch(
14090
+ () => props.multiSortState,
14091
+ (next) => next !== void 0 && sorting.model.syncMultiSort(next ?? []),
14092
+ { immediate: true }
14093
+ );
14094
+ const internalSort = vue.computed(
14095
+ () => props.sort !== void 0 ? props.sort ?? null : sorting.sort.value
14096
+ );
14097
+ const multiSortState = vue.computed(
14098
+ () => props.multiSortState !== void 0 ? props.multiSortState : sorting.multiSort.value
14099
+ );
14100
+ const setSort = (next) => sorting.model.setSort(next);
14101
+ const cycleSort = (column) => {
14102
+ if (column.sortable) sorting.model.cycleSort(column.key);
14103
+ };
14104
+ const cycleMultiSort = (column) => {
14105
+ if (column.sortable) sorting.model.cycleMultiSort(column.key);
14106
+ };
14107
+ const sortComparator = vue.computed(
14108
+ () => buildMultiSortComparator(
14109
+ leafColumns.value,
14110
+ internalSort.value ? [internalSort.value] : [],
14111
+ props.formulaTables
14112
+ )
14113
+ );
14114
+ const multiSortComparator = vue.computed(() => buildMultiSortComparator(leafColumns.value, multiSortState.value, props.formulaTables));
14115
+ const sortedRows = vue.computed(() => {
14116
+ if (remoteSort.value) return tableData.value;
14117
+ const compare = props.multiSort ? multiSortComparator.value : sortComparator.value;
14118
+ return compare ? [...tableData.value].sort(compare) : tableData.value;
14119
+ });
13057
14120
  const tableViews = createTableViewsController({
13058
14121
  config: () => props.views,
13059
14122
  sort: internalSort,
@@ -13061,6 +14124,34 @@ var IrisTable = vue.defineComponent({
13061
14124
  onActiveViewChange: (key) => props.onActiveViewChange?.(key)
13062
14125
  });
13063
14126
  const sortedData = vue.computed(() => remoteSort.value ? tableData.value : sortedRows.value);
14127
+ const filtering = useGridFiltering(gridCore, {
14128
+ defaultFilters: props.filters,
14129
+ defaultFilterValues: props.filterValues,
14130
+ onFilterValuesChange: (next) => props.onFilterValuesChange?.(next)
14131
+ });
14132
+ vue.watch(
14133
+ () => props.filters,
14134
+ (next) => next !== void 0 && filtering.model.syncFilters(next),
14135
+ { immediate: true }
14136
+ );
14137
+ vue.watch(
14138
+ () => props.filterValues,
14139
+ (next) => next !== void 0 && filtering.model.syncFilterValues(next),
14140
+ { immediate: true }
14141
+ );
14142
+ const effectiveFilters = vue.computed(
14143
+ () => props.filters !== void 0 ? props.filters : filtering.filters.value
14144
+ );
14145
+ const effectiveFilterValues = vue.computed(
14146
+ () => props.filterValues !== void 0 ? props.filterValues : filtering.filterValues.value
14147
+ );
14148
+ const recent = core.createRecentFilters();
14149
+ const recentEntries = vue.shallowRef(recent.list());
14150
+ vue.onScopeDispose(
14151
+ recent.subscribe(() => {
14152
+ recentEntries.value = recent.list();
14153
+ })
14154
+ );
13064
14155
  vue.watch([internalSort, () => props.multiSort, remoteSort, () => proxyCtrl.proxy.value], () => {
13065
14156
  if (!proxyCtrl.proxy.value || !remoteSort.value || props.multiSort) return;
13066
14157
  proxyCtrl.setParams({ sort: internalSort.value });
@@ -13086,7 +14177,7 @@ var IrisTable = vue.defineComponent({
13086
14177
  if (formDraft.value[key] === value) return;
13087
14178
  formDraft.value = { ...formDraft.value, [key]: value };
13088
14179
  };
13089
- const mergedProxyFilters = (form) => mergeFilterValues(core.mergeFormFilters(props.filters ?? {}, form), props.filterValues ?? {});
14180
+ const mergedProxyFilters = (form) => mergeFilterValues(core.mergeFormFilters(effectiveFilters.value, form), effectiveFilterValues.value);
13090
14181
  const handleFormSubmit = () => {
13091
14182
  const values = core.buildFormValues(props.formConfig?.fields, formDraft.value);
13092
14183
  props.formConfig?.onSearch?.(values);
@@ -13108,7 +14199,7 @@ var IrisTable = vue.defineComponent({
13108
14199
  }
13109
14200
  };
13110
14201
  vue.watch(
13111
- [formApplied, () => props.filters, () => props.filterValues],
14202
+ [formApplied, effectiveFilters, effectiveFilterValues],
13112
14203
  () => {
13113
14204
  if (proxyCtrl.proxy.value && remoteFilter.value) {
13114
14205
  proxyCtrl.setParams({
@@ -13118,22 +14209,39 @@ var IrisTable = vue.defineComponent({
13118
14209
  },
13119
14210
  { immediate: true }
13120
14211
  );
14212
+ const gridRows = useGridRows(gridCore, tableData.value, {
14213
+ getRowKey: (row, index) => rowId(row, index),
14214
+ getChildren: props.getSubRows !== void 0 || props.lazyLoad !== void 0 ? readRowChildren : void 0,
14215
+ setChildren: writeLazyChildren,
14216
+ onRowsChange: (transaction) => {
14217
+ const next = [...transaction.rows];
14218
+ committedList.sync(next);
14219
+ if (proxyCtrl.proxy.value) proxyCtrl.liveData.value = next;
14220
+ else localRowsOverride.value = next;
14221
+ if (!suppressUndoRecord) recordUndoRows?.(next);
14222
+ }
14223
+ });
14224
+ vue.watch(tableData, (rows) => {
14225
+ committedList.sync(rows);
14226
+ gridRows.model.sync(rows);
14227
+ });
13121
14228
  const selControlled = vue.computed(() => props.selection !== void 0);
13122
- const selectionModel = core.createSelectionModel({
13123
- defaultSelected: props.selection ?? props.defaultSelection ?? [],
14229
+ const { model: selectionModel, selection: selectedKeys } = useGridSelection(gridCore, {
14230
+ mode: "multiple",
14231
+ value: props.selection,
14232
+ defaultValue: props.defaultSelection,
13124
14233
  onChange: (keys) => emit("update:selection", keys)
13125
14234
  });
13126
- const selectedKeys = vue.shallowRef(selectionModel.get());
13127
- vue.onBeforeUnmount(
13128
- selectionModel.store.subscribe((keys) => {
13129
- selectedKeys.value = keys;
13130
- })
13131
- );
13132
14235
  vue.watch(
13133
14236
  () => props.selection,
13134
- (sel) => {
13135
- if (sel !== void 0) selectionModel.sync(sel);
13136
- }
14237
+ (selection) => {
14238
+ if (selection === void 0) return;
14239
+ const current = selectionModel.get();
14240
+ if (current.length !== selection.length || current.some((key, index) => !Object.is(key, selection[index]))) {
14241
+ selectionModel.sync(selection);
14242
+ }
14243
+ },
14244
+ { immediate: true }
13137
14245
  );
13138
14246
  const displaySelection = vue.computed(
13139
14247
  () => selControlled.value ? props.selection : selectedKeys.value
@@ -13142,17 +14250,11 @@ var IrisTable = vue.defineComponent({
13142
14250
  if (selControlled.value) selectionModel.sync(props.selection);
13143
14251
  };
13144
14252
  const hasDetail = vue.computed(() => props.renderDetail !== void 0);
13145
- const expansion = core.createExpansion({
14253
+ const { model: expansion, expandedKeys } = useGridExpansion(gridCore, {
13146
14254
  mode: "multiple",
13147
- defaultExpanded: (props.defaultExpandedRowKeys ?? []).map(String),
14255
+ defaultValue: (props.defaultExpandedRowKeys ?? []).map(String),
13148
14256
  onChange: (keys) => emit("expandedRowsChange", keys)
13149
14257
  });
13150
- const expandedKeys = vue.shallowRef(expansion.get());
13151
- vue.onBeforeUnmount(
13152
- expansion.store.subscribe((keys) => {
13153
- expandedKeys.value = keys;
13154
- })
13155
- );
13156
14258
  const isRowExpandable = (row, idx) => hasDetail.value && (props.rowExpandable ? props.rowExpandable(row, idx) : true);
13157
14259
  const rowId = (row, index) => {
13158
14260
  const v = row[props.rowKey];
@@ -13171,21 +14273,17 @@ var IrisTable = vue.defineComponent({
13171
14273
  auditRowsRef.value = next;
13172
14274
  };
13173
14275
  const treeMode = vue.computed(() => props.getSubRows !== void 0 || props.lazyLoad !== void 0);
13174
- const lazyChildren = vue.ref(/* @__PURE__ */ new Map());
13175
14276
  const lazyLoading = vue.ref(/* @__PURE__ */ new Set());
13176
14277
  let lazyEpoch = 0;
13177
14278
  vue.watch(
13178
14279
  () => proxyCtrl.proxy.value ? proxyCtrl.state.value.data : props.data,
13179
14280
  () => {
13180
- lazyChildren.value = /* @__PURE__ */ new Map();
13181
14281
  lazyLoading.value = /* @__PURE__ */ new Set();
13182
14282
  lazyEpoch += 1;
13183
14283
  }
13184
14284
  );
13185
- const lazyChildrenOf = (row) => {
13186
- const key = String(row[props.rowKey]);
13187
- return lazyChildren.value.get(key) ?? props.getSubRows?.(row);
13188
- };
14285
+ const lazyChildrenOf = readRowChildren;
14286
+ const hasLazyChildren = (row) => props.lazyLoad !== void 0 && Array.isArray(row.children);
13189
14287
  const flatTree = vue.computed(
13190
14288
  () => treeMode.value ? core.flattenTree(sortedData.value, {
13191
14289
  getKey: (r) => String(r[props.rowKey]),
@@ -13198,9 +14296,9 @@ var IrisTable = vue.defineComponent({
13198
14296
  );
13199
14297
  const filteredData = vue.computed(() => {
13200
14298
  if (remoteFilter.value) return sortedData.value;
13201
- const merged = proxyCtrl.proxy.value ? props.filters ?? {} : core.mergeFormFilters(props.filters ?? {}, formApplied.value);
14299
+ const merged = proxyCtrl.proxy.value ? effectiveFilters.value : core.mergeFormFilters(effectiveFilters.value, formApplied.value);
13202
14300
  const active = Object.entries(merged).filter(([, v]) => v != null && v !== "");
13203
- const checkedEntries = Object.entries(props.filterValues ?? {}).filter(
14301
+ const checkedEntries = Object.entries(effectiveFilterValues.value).filter(
13204
14302
  ([, values]) => values.length > 0
13205
14303
  );
13206
14304
  if (active.length === 0 && checkedEntries.length === 0) return sortedData.value;
@@ -13208,12 +14306,12 @@ var IrisTable = vue.defineComponent({
13208
14306
  const textOk = active.every(([key, value]) => {
13209
14307
  const col = displayColumns.value.find((c) => c.key === key);
13210
14308
  if (!col) return true;
13211
- return String(getCellValue(row, col) ?? "").toLowerCase().includes(value.toLowerCase());
14309
+ return String(getCellValue2(row, col) ?? "").toLowerCase().includes(value.toLowerCase());
13212
14310
  });
13213
14311
  const setsOk = checkedEntries.every(([key, values]) => {
13214
14312
  const col = displayColumns.value.find((c) => c.key === key);
13215
14313
  if (!col) return true;
13216
- return values.includes(String(getCellValue(row, col) ?? ""));
14314
+ return values.includes(String(getCellValue2(row, col) ?? ""));
13217
14315
  });
13218
14316
  return textOk && setsOk;
13219
14317
  });
@@ -13221,28 +14319,48 @@ var IrisTable = vue.defineComponent({
13221
14319
  const bodyData = vue.computed(
13222
14320
  () => flatTree.value ? flatTree.value.map((t2) => t2.row) : filteredData.value
13223
14321
  );
14322
+ const reconcileClipboardRows = (sourceRows, previousRows, rows) => {
14323
+ const visibleKeys = /* @__PURE__ */ new Map();
14324
+ bodyData.value.forEach((row, index) => {
14325
+ visibleKeys.set(row, rowId(row, index));
14326
+ });
14327
+ const keyOf = (row, index, source) => {
14328
+ const visibleKey = visibleKeys.get(row);
14329
+ if (visibleKey !== void 0) return visibleKey;
14330
+ const sourceIndex = source?.indexOf(row) ?? -1;
14331
+ return rowId(row, sourceIndex >= 0 ? sourceIndex : index);
14332
+ };
14333
+ const patches = /* @__PURE__ */ new Map();
14334
+ rows.forEach((row, index) => {
14335
+ if (Object.is(row, previousRows[index])) return;
14336
+ const previous = previousRows[index];
14337
+ if (!previous) return;
14338
+ const sourceIndex = sourceRows.indexOf(previous);
14339
+ patches.set(keyOf(previous, sourceIndex >= 0 ? sourceIndex : index, sourceRows), row);
14340
+ });
14341
+ if (props.getSubRows !== void 0 || props.lazyLoad !== void 0) {
14342
+ return core.reconcileTreeRows(sourceRows, patches, {
14343
+ getRowKey: (row, index) => keyOf(row, index),
14344
+ getChildren: readRowChildren,
14345
+ setChildren: writeLazyChildren
14346
+ });
14347
+ }
14348
+ return sourceRows.map((row, index) => patches.get(keyOf(row, index, sourceRows)) ?? row);
14349
+ };
13224
14350
  const cascadingTreeSelection = vue.computed(
13225
14351
  () => props.treeSelectionCascade && props.selectable === "multi" && treeMode.value
13226
14352
  );
13227
14353
  const treeSelectionNodes = vue.computed(() => {
13228
- const getChildren = props.getSubRows;
14354
+ const getChildren = props.getSubRows !== void 0 || props.lazyLoad !== void 0 ? readRowChildren : void 0;
13229
14355
  if (!cascadingTreeSelection.value || !getChildren) return [];
13230
- const nodes = [];
13231
- const seen = /* @__PURE__ */ new Set();
13232
14356
  let rowIndex = 0;
13233
- const walk = (rows, parentKey) => {
13234
- for (const row of rows) {
13235
- const key = rowId(row, rowIndex);
13236
- rowIndex += 1;
13237
- if (seen.has(key)) continue;
13238
- seen.add(key);
13239
- nodes.push({ key, parentKey });
13240
- const children = getChildren(row);
13241
- if (children && children.length > 0) walk(children, key);
13242
- }
13243
- };
13244
- walk(sortedData.value);
13245
- return nodes;
14357
+ return core.flattenTreeSelectionNodes(sortedData.value, {
14358
+ // Keep the legacy fallback key's global pre-order ordinal. The core
14359
+ // helper owns recursion and duplicate/cycle protection; this closure
14360
+ // only supplies the table's existing row-id policy.
14361
+ getKey: (row) => rowId(row, rowIndex++),
14362
+ getChildren
14363
+ });
13246
14364
  });
13247
14365
  const compactTreeSelectionSeed = (keys) => {
13248
14366
  const selected = new Set(keys);
@@ -13313,51 +14431,8 @@ var IrisTable = vue.defineComponent({
13313
14431
  }
13314
14432
  selectionModel.set([...allRowIds.value]);
13315
14433
  };
13316
- const editingCellId = vue.ref(null);
13317
- const editingColumnKey = vue.ref(null);
13318
- const editingDraft = vue.ref("");
13319
- const editError = vue.ref(null);
13320
14434
  const editorInputRef = vue.ref(null);
13321
- const beginEdit = (row, column, rowIdent) => {
13322
- if (!isEditableColumn(column)) return;
13323
- editingCellId.value = cellId(rowIdent, column.key);
13324
- editingColumnKey.value = column.key;
13325
- const current = getCellValue(row, column);
13326
- editingDraft.value = current == null ? "" : String(current);
13327
- editError.value = null;
13328
- void vue.nextTick(() => editorInputRef.value?.focus());
13329
- };
13330
- const commitEdit = (row, column, rowIndex) => {
13331
- if (editingCellId.value === null) return;
13332
- const oldValue = getCellValue(row, column);
13333
- const draft = editingDraft.value;
13334
- const newValue = column.editor === "number" ? draft === "" || Number.isNaN(Number(draft)) ? oldValue : Number(draft) : draft;
13335
- if (column.editRules && column.editRules.length > 0) {
13336
- const context = { rows: tableData.value, columnKey: column.key };
13337
- core.validateEditRulesAsync(column.editRules, draft, row, false, context).then((r) => {
13338
- if (!r.valid) {
13339
- editError.value = r.messages[0] ?? null;
13340
- return;
13341
- }
13342
- finishCommit(row, column, rowIndex, oldValue, newValue);
13343
- });
13344
- return;
13345
- }
13346
- if (column.validate) {
13347
- const error = column.validate(newValue, row);
13348
- if (error) {
13349
- editError.value = error;
13350
- return;
13351
- }
13352
- }
13353
- finishCommit(row, column, rowIndex, oldValue, newValue);
13354
- editError.value = null;
13355
- editingCellId.value = null;
13356
- if (newValue !== oldValue) {
13357
- emit("cellEdit", { row, column, oldValue, newValue, rowIndex });
13358
- }
13359
- };
13360
- const writeCellValue = (row, column, rowIndex, oldValue, newValue) => {
14435
+ const recordCellCommit = (row, column, rowIndex, oldValue, newValue) => {
13361
14436
  if (newValue === oldValue) return;
13362
14437
  if (props.auditLog) {
13363
14438
  audit.push({
@@ -13369,43 +14444,95 @@ var IrisTable = vue.defineComponent({
13369
14444
  });
13370
14445
  }
13371
14446
  emit("cellEdit", { row, column, oldValue, newValue, rowIndex });
13372
- if (proxyCtrl.proxy.value) {
13373
- const id = rowId(row, rowIndex);
13374
- const idx = proxyCtrl.liveData.value.findIndex((r, i) => rowId(r, i) === id);
13375
- if (idx >= 0) {
13376
- const key = column.dataIndex ?? column.key;
13377
- const next = { ...proxyCtrl.liveData.value[idx], [key]: newValue };
13378
- proxyCtrl.liveData.value = [
13379
- ...proxyCtrl.liveData.value.slice(0, idx),
13380
- next,
13381
- ...proxyCtrl.liveData.value.slice(idx + 1)
13382
- ];
14447
+ };
14448
+ const cellEditing = useGridEditing(gridCore, {
14449
+ getRowKey: (row, index) => rowId(row, index),
14450
+ getRowIndex: (rowKey) => {
14451
+ const index = bodyData.value.findIndex(
14452
+ (row, rowIndex) => Object.is(rowId(row, rowIndex), rowKey)
14453
+ );
14454
+ return index >= 0 ? index : void 0;
14455
+ },
14456
+ getRules: (columnKey) => leafColumns.value.find((column) => column.key === columnKey)?.editRules,
14457
+ getValue: (row, columnKey) => {
14458
+ const column = leafColumns.value.find((candidate) => candidate.key === columnKey);
14459
+ return column ? getCellValue2(row, column) : row[columnKey];
14460
+ },
14461
+ setValue: (row, columnKey, value) => {
14462
+ const column = leafColumns.value.find((candidate) => candidate.key === columnKey);
14463
+ const key = column?.dataIndex ?? column?.key ?? columnKey;
14464
+ return { ...row, [key]: value };
14465
+ },
14466
+ coerce: (draft, row, columnKey) => {
14467
+ const column = leafColumns.value.find((candidate) => candidate.key === columnKey);
14468
+ if (column?.editor !== "number") return draft;
14469
+ const text = String(draft ?? "");
14470
+ if (text === "" || Number.isNaN(Number(text))) {
14471
+ return column ? getCellValue2(row, column) : draft;
13383
14472
  }
14473
+ return Number(text);
14474
+ },
14475
+ validate: (value, row, columnKey) => {
14476
+ const column = leafColumns.value.find((candidate) => candidate.key === columnKey);
14477
+ return column?.validate?.(value, row) ?? null;
14478
+ },
14479
+ isEditable: (_row, columnKey) => {
14480
+ const column = leafColumns.value.find((candidate) => candidate.key === columnKey);
14481
+ return Boolean(column && isEditableColumn(column));
14482
+ },
14483
+ onCommit: (commit) => {
14484
+ const column = leafColumns.value.find((candidate) => candidate.key === commit.columnKey);
14485
+ if (column)
14486
+ recordCellCommit(commit.row, column, commit.rowIndex, commit.oldValue, commit.value);
14487
+ }
14488
+ });
14489
+ const editingState = cellEditing.state;
14490
+ const editingCellId = vue.computed(() => {
14491
+ const target = editingState.value.editing;
14492
+ return target ? cellId(target.rowKey, target.columnKey) : null;
14493
+ });
14494
+ const editingColumnKey = vue.computed(() => editingState.value.editing?.columnKey ?? null);
14495
+ const editingDraft = vue.computed(() => String(editingState.value.draft ?? ""));
14496
+ const editError = vue.computed(() => editingState.value.error);
14497
+ const setEditingDraft = (draft) => cellEditing.setCellDraft(draft);
14498
+ const beginEdit = (row, column, rowIdent) => {
14499
+ if (!isEditableColumn(column)) return;
14500
+ const current = getCellValue2(row, column);
14501
+ if (cellEditing.startCellEdit(rowIdent, column.key, current == null ? "" : String(current))) {
14502
+ void vue.nextTick(() => editorInputRef.value?.focus());
13384
14503
  }
13385
14504
  };
13386
- const finishCommit = (row, column, rowIndex, oldValue, newValue) => {
13387
- editError.value = null;
13388
- editingCellId.value = null;
13389
- editingColumnKey.value = null;
13390
- writeCellValue(row, column, rowIndex, oldValue, newValue);
14505
+ const commitEdit = (_row, _column, _rowIndex) => {
14506
+ cellEditing.commitCellEdit();
13391
14507
  };
13392
- const cancelEdit = () => {
13393
- editError.value = null;
13394
- editingCellId.value = null;
13395
- editingColumnKey.value = null;
14508
+ const cancelEdit = () => cellEditing.cancelCellEdit();
14509
+ const writeCellValue = (row, column, rowIndex, oldValue, newValue) => {
14510
+ if (newValue === oldValue) return;
14511
+ recordCellCommit(row, column, rowIndex, oldValue, newValue);
14512
+ const current = committedList.list();
14513
+ const key = column.dataIndex ?? column.key;
14514
+ const next = replaceTableCell(current, rowId(row, rowIndex), key, newValue, rowId);
14515
+ if (next !== current) {
14516
+ undoController.record(next);
14517
+ setTableRows(next);
14518
+ }
13396
14519
  };
13397
14520
  const rowMode = vue.computed(() => props.editConfig?.mode === "row");
13398
14521
  const rowSessions = vue.ref(/* @__PURE__ */ new Map());
13399
14522
  const rowEditing = vue.ref(null);
13400
14523
  const rowEditorRefs = /* @__PURE__ */ new Map();
13401
- const currentRowFor = (k) => bodyData.value.find((r, i) => rowId(r, i) === k);
14524
+ const currentRowFor = (k) => (
14525
+ // Static tree children are indexed by the shared Core rows model. Keep
14526
+ // the visible-body fallback for proxy/lazy rows owned by this adapter.
14527
+ committedList.list().find((r, i) => rowId(r, i) === k) ?? gridRows.model.find(k) ?? bodyData.value.find((r, i) => rowId(r, i) === k)
14528
+ );
13402
14529
  const beginRowEdit = (row, rowIndex, focusColKey) => {
13403
14530
  const k = rowId(row, rowIndex);
13404
14531
  const editableCols = leafColumns.value.filter(isEditableColumn);
13405
14532
  if (editableCols.length === 0) return;
13406
14533
  const sessions = /* @__PURE__ */ new Map();
13407
14534
  for (const col of editableCols) {
13408
- const current = getCellValue(row, col);
14535
+ const current = getCellValue2(row, col);
13409
14536
  sessions.set(cellId(k, col.key), {
13410
14537
  draft: current == null ? "" : String(current),
13411
14538
  error: null
@@ -13421,7 +14548,7 @@ var IrisTable = vue.defineComponent({
13421
14548
  if (!session) return;
13422
14549
  const row = currentRowFor(k);
13423
14550
  if (!row) return;
13424
- const oldValue = getCellValue(row, column);
14551
+ const oldValue = getCellValue2(row, column);
13425
14552
  const draft = session.draft;
13426
14553
  const newValue = column.editor === "number" ? draft === "" || Number.isNaN(Number(draft)) ? oldValue : Number(draft) : draft;
13427
14554
  if (column.editRules && column.editRules.length > 0) {
@@ -13473,7 +14600,7 @@ var IrisTable = vue.defineComponent({
13473
14600
  if (rowEditing.value?.k === k) {
13474
14601
  const id = cellId(k, col.key);
13475
14602
  if (isEditableColumn(col) && !rowSessions.value.has(id)) {
13476
- const current = getCellValue(row, col);
14603
+ const current = getCellValue2(row, col);
13477
14604
  rowSessions.value.set(id, {
13478
14605
  draft: current == null ? "" : String(current),
13479
14606
  error: null
@@ -13488,6 +14615,7 @@ var IrisTable = vue.defineComponent({
13488
14615
  rowEditorRefs,
13489
14616
  editorInputRef,
13490
14617
  editingDraft,
14618
+ setEditingDraft,
13491
14619
  editError,
13492
14620
  commitEdit,
13493
14621
  cancelEdit,
@@ -13495,7 +14623,7 @@ var IrisTable = vue.defineComponent({
13495
14623
  cancelRowEdit,
13496
14624
  editPreview: props.editPreview,
13497
14625
  previewValue: (row, col, draft) => {
13498
- const raw = col.editor === "number" ? draft === "" || Number.isNaN(Number(draft)) ? getCellValue(row, col) : Number(draft) : draft;
14626
+ const raw = col.editor === "number" ? draft === "" || Number.isNaN(Number(draft)) ? getCellValue2(row, col) : Number(draft) : draft;
13499
14627
  return col.formatter?.(core.applyTableMask(raw, col), row) ?? "";
13500
14628
  }
13501
14629
  };
@@ -13512,7 +14640,7 @@ var IrisTable = vue.defineComponent({
13512
14640
  paddingLeft: `${treeMeta.depth * 16}px`
13513
14641
  }
13514
14642
  },
13515
- treeMeta.hasChildren || props.lazyLoad !== void 0 && !lazyChildren.value.has(treeMeta.key) ? [
14643
+ treeMeta.hasChildren || props.lazyLoad !== void 0 && !hasLazyChildren(row) ? [
13516
14644
  vue.h(
13517
14645
  "button",
13518
14646
  {
@@ -13538,8 +14666,18 @@ var IrisTable = vue.defineComponent({
13538
14666
  const epoch = lazyEpoch;
13539
14667
  props.lazyLoad(row, (children) => {
13540
14668
  if (epoch !== lazyEpoch) return;
13541
- lazyChildren.value.set(treeMeta.key, children);
13542
- if (children && children.length > 0) {
14669
+ const rawKey = row[props.rowKey];
14670
+ const lazyKey = typeof rawKey === "string" || typeof rawKey === "number" ? rawKey : rowId(row, Math.max(0, treeMeta.posInset - 1));
14671
+ suppressUndoRecord = true;
14672
+ let committed = false;
14673
+ try {
14674
+ committed = gridRows.model.setChildren(lazyKey, children, {
14675
+ reason: "lazy-load"
14676
+ });
14677
+ } finally {
14678
+ suppressUndoRecord = false;
14679
+ }
14680
+ if (committed && children && children.length > 0) {
13543
14681
  expansion.toggle(treeMeta.key);
13544
14682
  }
13545
14683
  clearLoading();
@@ -13572,21 +14710,30 @@ var IrisTable = vue.defineComponent({
13572
14710
  vue.watch(
13573
14711
  () => leafColumns.value,
13574
14712
  (cols) => {
13575
- const seeded = { ...internalWidths.value };
13576
- let changed = false;
14713
+ const seeded = { ...uncontrolledWidths.value };
14714
+ let snapshotChanged = false;
13577
14715
  for (const col of cols) {
13578
14716
  if (seeded[col.key] === void 0) {
13579
14717
  seeded[col.key] = resolveInitialWidth(col);
13580
- changed = true;
14718
+ snapshotChanged = true;
13581
14719
  }
13582
14720
  }
13583
- if (changed) internalWidths.value = seeded;
14721
+ if (snapshotChanged) uncontrolledWidths.value = seeded;
14722
+ const modelWidths = { ...columnsFeature.state.value.widths };
14723
+ let modelChanged = false;
14724
+ for (const col of cols) {
14725
+ if (modelWidths[col.key] === void 0) {
14726
+ modelWidths[col.key] = resolveInitialWidth(col);
14727
+ modelChanged = true;
14728
+ }
14729
+ }
14730
+ if (modelChanged) columnsFeature.model.syncWidths(modelWidths);
13584
14731
  },
13585
14732
  { immediate: true, deep: false }
13586
14733
  );
13587
14734
  const setColumnWidths = (next) => {
13588
- if (props.columnWidths === void 0) internalWidths.value = next;
13589
- emit("update:columnWidths", next);
14735
+ if (props.columnWidths === void 0) uncontrolledWidths.value = next;
14736
+ columnsFeature.setWidths(next);
13590
14737
  };
13591
14738
  const onHeaderClick = (column) => {
13592
14739
  if (props.multiSort) cycleMultiSort(column);
@@ -13622,7 +14769,7 @@ var IrisTable = vue.defineComponent({
13622
14769
  const renderFilterTrigger = (col, leaf) => renderTableFilterTrigger({
13623
14770
  column: col,
13624
14771
  leaf,
13625
- active: (props.filterValues?.[col.key]?.length ?? 0) > 0,
14772
+ active: (effectiveFilterValues.value[col.key]?.length ?? 0) > 0,
13626
14773
  open: filterPanelState.value?.open === true && filterPanelState.value.colKey === col.key,
13627
14774
  label: t("table.filter"),
13628
14775
  onOpen: openFilterPanel
@@ -13635,18 +14782,136 @@ var IrisTable = vue.defineComponent({
13635
14782
  if (hasDetail.value) parts.push(`${EXPAND_COL_WIDTH}px`);
13636
14783
  if (props.selectable !== "none") parts.push(`${SELECTION_COL_WIDTH}px`);
13637
14784
  for (const col of leafColumns.value) {
14785
+ if (isColumnFadeCollapsed(col.key)) {
14786
+ parts.push("0px");
14787
+ continue;
14788
+ }
13638
14789
  parts.push(`${effectiveWidths.value[col.key] ?? resolveInitialWidth(col)}px`);
13639
14790
  }
13640
14791
  return parts.join(" ");
13641
14792
  });
13642
- const cellRangeCtrl = core.createCellRange();
13643
- const cellRangeState = vue.shallowRef(cellRangeCtrl.getState());
13644
- vue.onBeforeUnmount(
13645
- cellRangeCtrl.subscribe((s) => {
13646
- cellRangeState.value = s;
13647
- })
13648
- );
14793
+ const { model: cellRangeCtrl, state: cellRangeState } = useGridRange(gridCore);
14794
+ const { serialize: serializeGridRange, paste: pasteGridRange } = useGridClipboard(gridCore, {
14795
+ getRows: () => bodyData.value,
14796
+ getColumns: () => leafColumns.value,
14797
+ rowKeyField: props.rowKey,
14798
+ resolveValue: (row, column) => getCellValue2(row, column),
14799
+ setValue: (row, column, value) => ({
14800
+ ...row,
14801
+ [column.dataIndex ?? column.key]: value
14802
+ }),
14803
+ isCellEditable: (_row, column) => !column.formula,
14804
+ reconcileRows: reconcileClipboardRows,
14805
+ onPaste: (change) => {
14806
+ const rows = [...change.rows];
14807
+ recordAudit(rows, "paste");
14808
+ props.onDataChange?.(rows);
14809
+ }
14810
+ });
13649
14811
  const rootRef = vue.ref(null);
14812
+ let tableDisposed = false;
14813
+ vue.onScopeDispose(() => {
14814
+ tableDisposed = true;
14815
+ });
14816
+ const setTableRows = (rows) => {
14817
+ committedList.sync(rows);
14818
+ suppressUndoRecord = true;
14819
+ try {
14820
+ gridRows.model.commit(rows);
14821
+ } finally {
14822
+ suppressUndoRecord = false;
14823
+ }
14824
+ };
14825
+ const undoController = createTableUndoController(
14826
+ () => props.undo,
14827
+ () => props.data ?? [],
14828
+ () => proxyCtrl.proxy.value ? proxyCtrl.state.value.data : props.data ?? [],
14829
+ setTableRows,
14830
+ recordAudit,
14831
+ (rows) => props.onDataChange?.(rows),
14832
+ () => rootRef.value,
14833
+ () => editingCellId.value !== null || rowEditing.value !== null,
14834
+ {
14835
+ current: () => displaySelection.value,
14836
+ enabled: () => props.selectable !== "none",
14837
+ keyOf: rowId,
14838
+ rebase: rebaseToProp,
14839
+ set: (keys) => selectionModel.set(keys)
14840
+ }
14841
+ );
14842
+ recordUndoRows = undoController.record;
14843
+ const scrollTopShown = vue.ref(false);
14844
+ let scrollTopListeners = [];
14845
+ const clearScrollTopListeners = () => {
14846
+ for (const el of scrollTopListeners) el.removeEventListener("scroll", onScrollTop);
14847
+ scrollTopListeners = [];
14848
+ };
14849
+ const onScrollTop = () => {
14850
+ const root = rootRef.value;
14851
+ if (!root) return;
14852
+ const viewport = root.querySelector("[data-iris-virtual-scroll]");
14853
+ const scroller = viewport ?? root;
14854
+ scrollTopShown.value = scroller.scrollTop >= SCROLL_TOP_VISIBLE_PX;
14855
+ };
14856
+ const armScrollTop = () => {
14857
+ clearScrollTopListeners();
14858
+ scrollTopShown.value = false;
14859
+ if (!props.scrollToTop || !rootRef.value) return;
14860
+ const root = rootRef.value;
14861
+ const viewport = root.querySelector("[data-iris-virtual-scroll]");
14862
+ root.addEventListener("scroll", onScrollTop);
14863
+ scrollTopListeners.push(root);
14864
+ if (viewport) {
14865
+ viewport.addEventListener("scroll", onScrollTop);
14866
+ scrollTopListeners.push(viewport);
14867
+ }
14868
+ onScrollTop();
14869
+ };
14870
+ vue.onMounted(armScrollTop);
14871
+ vue.watch(
14872
+ [
14873
+ () => props.scrollToTop,
14874
+ () => Boolean(props.virtualScroll),
14875
+ bodyData,
14876
+ tableLoading,
14877
+ tableError
14878
+ ],
14879
+ armScrollTop,
14880
+ { flush: "post" }
14881
+ );
14882
+ vue.onBeforeUnmount(clearScrollTopListeners);
14883
+ const scrollToTopOfTable = () => {
14884
+ const root = rootRef.value;
14885
+ if (!root) return;
14886
+ const viewport = root.querySelector("[data-iris-virtual-scroll]");
14887
+ const scroller = viewport ?? root;
14888
+ const behavior = prefersReducedMotion() ? "auto" : "smooth";
14889
+ if (typeof scroller.scrollTo === "function") {
14890
+ try {
14891
+ scroller.scrollTo({ top: 0, behavior });
14892
+ return;
14893
+ } catch {
14894
+ }
14895
+ }
14896
+ scroller.scrollTop = 0;
14897
+ };
14898
+ const buildBackTopSection = () => {
14899
+ if (!props.scrollToTop || !scrollTopShown.value || props.printable) return null;
14900
+ return vue.h("div", { "data-iris-back-top-anchor": "", style: BACK_TOP_ANCHOR_STYLE }, [
14901
+ vue.h(
14902
+ "button",
14903
+ {
14904
+ type: "button",
14905
+ "data-iris-back-top-table": "",
14906
+ "aria-label": t("backTop.label"),
14907
+ title: t("backTop.label"),
14908
+ onClick: scrollToTopOfTable,
14909
+ style: BACK_TOP_BUTTON_STYLE
14910
+ },
14911
+ "\u2191"
14912
+ )
14913
+ ]);
14914
+ };
13650
14915
  let responsiveObserver = null;
13651
14916
  const measureResponsiveWidth = () => {
13652
14917
  responsiveWidth.value = props.responsive && rootRef.value ? rootRef.value.clientWidth : 0;
@@ -13679,7 +14944,17 @@ var IrisTable = vue.defineComponent({
13679
14944
  root: () => rootRef.value,
13680
14945
  focused: focusedCell,
13681
14946
  range: cellRangeCtrl,
13682
- rangeState: cellRangeState
14947
+ rangeState: cellRangeState,
14948
+ serializeRange: serializeGridRange,
14949
+ pasteRange: (range) => {
14950
+ const clipAtStart = props.clipConfig;
14951
+ if (!clipAtStart || clipAtStart.paste === false) return;
14952
+ void readClipboardText().then((text) => {
14953
+ const clip = props.clipConfig;
14954
+ if (text === null || tableDisposed || !clip || clip.paste === false) return;
14955
+ pasteGridRange(text, range);
14956
+ });
14957
+ }
13683
14958
  });
13684
14959
  const { handleRootKeyDown, isInRange, activeCellRange, copyActiveRange } = keyboard;
13685
14960
  const scrollLeft = vue.ref(0);
@@ -13701,21 +14976,28 @@ var IrisTable = vue.defineComponent({
13701
14976
  ro = null;
13702
14977
  });
13703
14978
  }
13704
- const visibleColSet = vue.computed(
13705
- () => computeVisibleColSet(
14979
+ const visibleColSet = vue.computed(() => {
14980
+ const visible = computeVisibleColSet(
13706
14981
  props.columnVirtualization,
13707
14982
  leafColumns.value,
13708
14983
  scrollLeft.value,
13709
14984
  viewportWidth.value,
13710
- effectiveWidths.value
13711
- )
13712
- );
14985
+ effectiveWidths.value,
14986
+ pinOf
14987
+ );
14988
+ if (!visible) return null;
14989
+ const next = new Set(visible);
14990
+ leafColumns.value.forEach((column, index) => {
14991
+ if (fadeByLeaf.value[column.key]) next.add(index);
14992
+ });
14993
+ return next;
14994
+ });
13713
14995
  const pinnedOffsets = vue.computed(() => {
13714
14996
  const map = {};
13715
14997
  const widthOf = (col) => effectiveWidths.value[col.key] ?? resolveInitialWidth(col);
13716
14998
  let left = (props.rowDrag ? DRAG_COL_WIDTH : 0) + (props.seq ? SEQ_COL_WIDTH : 0) + (hasDetail.value ? EXPAND_COL_WIDTH : 0) + (props.selectable !== "none" ? SELECTION_COL_WIDTH : 0);
13717
14999
  for (const col of leafColumns.value) {
13718
- if (col.pinned === "left") {
15000
+ if (pinOf(col) === "left") {
13719
15001
  map[col.key] = { side: "left", offset: left };
13720
15002
  left += widthOf(col);
13721
15003
  }
@@ -13723,7 +15005,7 @@ var IrisTable = vue.defineComponent({
13723
15005
  let right = 0;
13724
15006
  for (let i = leafColumns.value.length - 1; i >= 0; i -= 1) {
13725
15007
  const col = leafColumns.value[i];
13726
- if (col?.pinned === "right") {
15008
+ if (col && pinOf(col) === "right") {
13727
15009
  map[col.key] = { side: "right", offset: right };
13728
15010
  right += widthOf(col);
13729
15011
  }
@@ -13769,12 +15051,24 @@ var IrisTable = vue.defineComponent({
13769
15051
  }
13770
15052
  });
13771
15053
  };
15054
+ const setColumnPinned = (key, side) => {
15055
+ if (props.pinnedColumns !== void 0) {
15056
+ columnsFeature.model.syncPinned(props.pinnedColumns);
15057
+ }
15058
+ columnsFeature.setPinned(key, side);
15059
+ };
13772
15060
  const pinnedDragHandle = createTablePinnedDrag({
13773
15061
  enabled: () => props.pinnedDrag === true,
13774
15062
  columns: () => leafColumns.value,
13775
15063
  widthOf: (column) => effectiveWidths.value[column.key] ?? resolveInitialWidth(column),
13776
- onColumnPinnedChange: props.onColumnPinnedChange,
13777
- onPinnedCountChange: props.onPinnedCountChange
15064
+ pinOf,
15065
+ setPinned: (key, side) => {
15066
+ if (pinnedPropControlled.value) {
15067
+ columnsFeature.model.syncPinned(props.pinnedColumns ?? {});
15068
+ columnsFeature.setPinned(key, side);
15069
+ } else props.onColumnPinnedChange?.(key, side);
15070
+ },
15071
+ onPinnedCountChange: (count) => props.onPinnedCountChange?.(count)
13778
15072
  });
13779
15073
  const rowDragCtrl = core.createSortable();
13780
15074
  const rowDragState = vue.shallowRef(rowDragCtrl.getState());
@@ -13823,16 +15117,66 @@ var IrisTable = vue.defineComponent({
13823
15117
  }
13824
15118
  const { activeId, overId } = rowDragCtrl.end();
13825
15119
  if (activeId !== null && overId !== null && activeId !== overId) {
13826
- const rows = [...bodyData.value];
13827
- const from = rows.findIndex((r, i) => String(rowId(r, i)) === activeId);
13828
- const to = rows.findIndex((r, i) => String(rowId(r, i)) === overId);
13829
- if (from >= 0 && to >= 0 && from !== to) {
13830
- const [moved] = rows.splice(from, 1);
13831
- rows.splice(to, 0, moved);
13832
- if (proxyCtrl.proxy.value) proxyCtrl.liveData.value = rows;
13833
- else localRowsOverride.value = rows;
13834
- props.onDataChange?.(rows);
13835
- props.rowDrag.onReorder(rows);
15120
+ const visibleRows = bodyData.value;
15121
+ const fromVisible = visibleRows.findIndex(
15122
+ (row, index) => String(rowId(row, index)) === activeId
15123
+ );
15124
+ const toVisible = visibleRows.findIndex(
15125
+ (row, index) => String(rowId(row, index)) === overId
15126
+ );
15127
+ const fromRow = fromVisible >= 0 ? visibleRows[fromVisible] : void 0;
15128
+ const toRow = toVisible >= 0 ? visibleRows[toVisible] : void 0;
15129
+ const fromKey = fromRow === void 0 ? void 0 : rowId(fromRow, fromVisible);
15130
+ const toKey = toRow === void 0 ? void 0 : rowId(toRow, toVisible);
15131
+ const modelFrom = fromKey === void 0 ? void 0 : gridRows.model.find(fromKey);
15132
+ const modelTo = toKey === void 0 ? void 0 : gridRows.model.find(toKey);
15133
+ const useRowsModel = fromKey !== void 0 && toKey !== void 0 && modelFrom === fromRow && modelTo === toRow;
15134
+ if (useRowsModel) {
15135
+ const position = fromVisible < toVisible ? "after" : "before";
15136
+ if (gridRows.model.reorder(fromKey, toKey, { reason: "row-drag", position })) {
15137
+ const rows = gridRows.model.get();
15138
+ props.onDataChange?.(rows);
15139
+ props.rowDrag.onReorder(rows);
15140
+ rowRectsRef.value = [];
15141
+ return;
15142
+ }
15143
+ }
15144
+ if (props.getSubRows !== void 0 || props.lazyLoad !== void 0) {
15145
+ const visibleKeys = new Map(
15146
+ visibleRows.map((row, index) => [row, String(rowId(row, index))])
15147
+ );
15148
+ if (fromVisible >= 0 && toVisible >= 0) {
15149
+ const result = core.reorderTreeRows(
15150
+ gridRows.model.get(),
15151
+ activeId,
15152
+ overId,
15153
+ {
15154
+ // Every drop target is visible; leave hidden descendants keyless
15155
+ // so a synthetic sibling index cannot mask a visible target.
15156
+ getRowKey: (row) => visibleKeys.get(row),
15157
+ getChildren: readRowChildren,
15158
+ setChildren: writeLazyChildren
15159
+ },
15160
+ fromVisible < toVisible ? "after" : "before"
15161
+ );
15162
+ if (result.changed) {
15163
+ const rows = result.rows;
15164
+ gridRows.model.commit(rows, { reason: "row-drag" });
15165
+ props.onDataChange?.(rows);
15166
+ props.rowDrag.onReorder(rows);
15167
+ }
15168
+ }
15169
+ } else {
15170
+ const rows = [...bodyData.value];
15171
+ const from = rows.findIndex((r, i) => String(rowId(r, i)) === activeId);
15172
+ const to = rows.findIndex((r, i) => String(rowId(r, i)) === overId);
15173
+ if (from >= 0 && to >= 0 && from !== to) {
15174
+ const [moved] = rows.splice(from, 1);
15175
+ rows.splice(to, 0, moved);
15176
+ gridRows.model.commit(rows, { reason: "row-drag" });
15177
+ props.onDataChange?.(rows);
15178
+ props.rowDrag.onReorder(rows);
15179
+ }
13836
15180
  }
13837
15181
  }
13838
15182
  rowRectsRef.value = [];
@@ -13881,6 +15225,9 @@ var IrisTable = vue.defineComponent({
13881
15225
  const [moved] = next.splice(from, 1);
13882
15226
  next.splice(to, 0, moved);
13883
15227
  props.columnDrag.onReorder(next);
15228
+ if (!grouped.value && orderPropControlled.value) {
15229
+ columnsFeature.setOrder(next.map((column) => column.key));
15230
+ }
13884
15231
  }
13885
15232
  }
13886
15233
  colRectsRef.value = [];
@@ -13889,8 +15236,7 @@ var IrisTable = vue.defineComponent({
13889
15236
  const tableExpose = {
13890
15237
  loadData: (rows) => {
13891
15238
  recordAudit(rows, "edit");
13892
- if (proxyCtrl.proxy.value) proxyCtrl.liveData.value = rows;
13893
- else localRowsOverride.value = rows;
15239
+ gridRows.model.loadData(rows);
13894
15240
  props.onDataChange?.(rows);
13895
15241
  },
13896
15242
  reloadData: () => {
@@ -13905,13 +15251,13 @@ var IrisTable = vue.defineComponent({
13905
15251
  return { page: s.params.page, pageSize: s.params.pageSize, total: s.total };
13906
15252
  },
13907
15253
  removeRows: (keys) => {
13908
- const { rows, removedKeys } = core.removeRowsFromList(tableData.value, props.rowKey, keys);
13909
- if (removedKeys.size === 0) return;
15254
+ const removedKeys = gridRows.model.removeMany(keys);
15255
+ if (removedKeys.length === 0) return;
15256
+ const rows = gridRows.model.get();
13910
15257
  recordAudit(rows, "remove");
13911
- if (proxyCtrl.proxy.value) proxyCtrl.liveData.value = rows;
13912
- else localRowsOverride.value = rows;
13913
15258
  const selected = displaySelection.value;
13914
- const nextSelection = selected.filter((key) => !removedKeys.has(key));
15259
+ const removed = new Set(removedKeys);
15260
+ const nextSelection = selected.filter((key) => !removed.has(key));
13915
15261
  if (nextSelection.length !== selected.length) {
13916
15262
  rebaseToProp();
13917
15263
  selectionModel.set(nextSelection);
@@ -13920,12 +15266,12 @@ var IrisTable = vue.defineComponent({
13920
15266
  },
13921
15267
  getFilteredData: () => [...bodyData.value],
13922
15268
  exportCurrentViewCsv: () => exportCsv(
13923
- withComputedFormulaCells(bodyData.value, leafColumns.value),
15269
+ withComputedFormulaCells(bodyData.value, leafColumns.value, props.formulaTables),
13924
15270
  leafColumns.value
13925
15271
  ),
13926
15272
  exportMultiCsv: () => {
13927
15273
  const current = exportCsv(
13928
- withComputedFormulaCells(bodyData.value, leafColumns.value),
15274
+ withComputedFormulaCells(bodyData.value, leafColumns.value, props.formulaTables),
13929
15275
  leafColumns.value
13930
15276
  );
13931
15277
  const names = props.exportNames;
@@ -14013,6 +15359,7 @@ ${refCsv}` : ""}`);
14013
15359
  densityToggle: props.densityToggle,
14014
15360
  effectiveDensity: effectiveDensity.value,
14015
15361
  onDensityToggle: cycleDensity,
15362
+ undo: { enabled: props.undo, controller: undoController, t },
14016
15363
  // Batch EN: the built-in audit trigger rides the toolbar (react
14017
15364
  // parity — the toolbar gate admits auditLog on its own).
14018
15365
  auditLog: props.auditLog,
@@ -14027,23 +15374,80 @@ ${refCsv}` : ""}`);
14027
15374
  const closeContextMenu = () => {
14028
15375
  if (contextMenuState.value) contextMenuState.value.open = false;
14029
15376
  };
15377
+ const virtualCursorAnchor = (event) => ({
15378
+ getBoundingClientRect: () => ({
15379
+ left: event.clientX,
15380
+ top: event.clientY,
15381
+ right: event.clientX,
15382
+ bottom: event.clientY,
15383
+ width: 0,
15384
+ height: 0,
15385
+ x: event.clientX,
15386
+ y: event.clientY,
15387
+ toJSON() {
15388
+ }
15389
+ })
15390
+ });
15391
+ const pinMenuState = vue.ref(null);
15392
+ const pinMenuAnchorRef = vue.ref(null);
15393
+ const pinMenuRef = vue.ref(null);
15394
+ const closePinMenu = () => {
15395
+ if (pinMenuState.value) pinMenuState.value.open = false;
15396
+ };
15397
+ vue.watch(
15398
+ () => props.columnPinMenu,
15399
+ (enabled) => {
15400
+ if (!enabled) closePinMenu();
15401
+ },
15402
+ { flush: "sync" }
15403
+ );
15404
+ const pinMenuItemsFor = (column) => pinOf(column) === null ? [{ key: PIN_LEFT_MENU_KEY, label: t("table.pinLeft") }] : [{ key: UNPIN_MENU_KEY, label: t("table.unpin") }];
15405
+ const handleHeaderContextMenu = (event, column) => {
15406
+ if (!props.columnPinMenu) return;
15407
+ event.preventDefault();
15408
+ event.stopPropagation();
15409
+ closeContextMenu();
15410
+ pinMenuAnchorRef.value = virtualCursorAnchor(event);
15411
+ const params = {
15412
+ row: void 0,
15413
+ column,
15414
+ rowIndex: -1,
15415
+ columnIndex: leafColumns.value.findIndex((candidate) => candidate.key === column.key)
15416
+ };
15417
+ pinMenuState.value = { open: true, items: pinMenuItemsFor(column), params };
15418
+ };
15419
+ const pinMenuConfig = {
15420
+ // The renderer receives the snapshot items above. This required config
15421
+ // member is intentionally empty: the pin menu has no caller items.
15422
+ items: () => [],
15423
+ onSelect: (key, params) => {
15424
+ const current = pinOf(params.column);
15425
+ if (key === PIN_LEFT_MENU_KEY && current === null) {
15426
+ setColumnPinned(params.column.key, "left");
15427
+ } else if (key === UNPIN_MENU_KEY && current !== null) {
15428
+ setColumnPinned(params.column.key, null);
15429
+ }
15430
+ }
15431
+ };
15432
+ vue.watch(
15433
+ () => {
15434
+ const state = pinMenuState.value;
15435
+ if (!state?.open) return null;
15436
+ const side = pinOf(state.params.column);
15437
+ const label = t(side === null ? "table.pinLeft" : "table.unpin");
15438
+ return `${side ?? "none"}\0${label}`;
15439
+ },
15440
+ () => {
15441
+ const state = pinMenuState.value;
15442
+ if (state?.open) state.items = pinMenuItemsFor(state.params.column);
15443
+ },
15444
+ { flush: "sync" }
15445
+ );
14030
15446
  const handleContextMenu = (e, row, col, idx, ci) => {
14031
15447
  if (!props.contextMenu) return;
14032
15448
  e.preventDefault();
14033
- contextAnchorRef.value = {
14034
- getBoundingClientRect: () => ({
14035
- left: e.clientX,
14036
- top: e.clientY,
14037
- right: e.clientX,
14038
- bottom: e.clientY,
14039
- width: 0,
14040
- height: 0,
14041
- x: e.clientX,
14042
- y: e.clientY,
14043
- toJSON() {
14044
- }
14045
- })
14046
- };
15449
+ closePinMenu();
15450
+ contextAnchorRef.value = virtualCursorAnchor(e);
14047
15451
  const params = {
14048
15452
  row,
14049
15453
  column: col,
@@ -14085,6 +15489,41 @@ ${refCsv}` : ""}`);
14085
15489
  close: closeContextMenu,
14086
15490
  contextMenu: props.contextMenu
14087
15491
  });
15492
+ const pinMenuOpen = vue.computed(() => props.columnPinMenu && pinMenuState.value?.open === true);
15493
+ const { floatingStyles: pinMenuStyles } = useFloating({
15494
+ anchor: pinMenuAnchorRef,
15495
+ floating: pinMenuRef,
15496
+ open: pinMenuOpen,
15497
+ placement: "bottom-start",
15498
+ // The zero-size virtual anchor must remain at the cursor; clamping it
15499
+ // would move the context menu away from the requested coordinates.
15500
+ flip: false,
15501
+ shift: false
15502
+ });
15503
+ useDismiss({
15504
+ enabled: pinMenuOpen,
15505
+ exclude: [pinMenuRef],
15506
+ onDismiss: closePinMenu
15507
+ });
15508
+ vue.watch(pinMenuOpen, (open) => {
15509
+ if (typeof document === "undefined") return;
15510
+ if (open) document.addEventListener("scroll", closePinMenu, true);
15511
+ else document.removeEventListener("scroll", closePinMenu, true);
15512
+ });
15513
+ vue.onScopeDispose(() => {
15514
+ if (typeof document === "undefined") return;
15515
+ document.removeEventListener("scroll", closePinMenu, true);
15516
+ });
15517
+ const buildPinMenuSection = () => {
15518
+ if (!props.columnPinMenu) return null;
15519
+ return renderContextMenuSection({
15520
+ state: pinMenuState,
15521
+ styles: pinMenuStyles,
15522
+ menuRef: pinMenuRef,
15523
+ close: closePinMenu,
15524
+ contextMenu: pinMenuConfig
15525
+ });
15526
+ };
14088
15527
  const filterPanelState = vue.ref(null);
14089
15528
  const filterAnchorRef = vue.ref(null);
14090
15529
  const filterPanelRef = vue.ref(null);
@@ -14095,7 +15534,7 @@ ${refCsv}` : ""}`);
14095
15534
  const openFilterPanel = (e, colKey) => {
14096
15535
  e.stopPropagation();
14097
15536
  filterAnchorRef.value = e.currentTarget;
14098
- filterDraft.value = [...props.filterValues?.[colKey] ?? []];
15537
+ filterDraft.value = [...effectiveFilterValues.value[colKey] ?? []];
14099
15538
  filterPanelState.value = { open: true, colKey };
14100
15539
  };
14101
15540
  const filterPanelOpen = vue.computed(() => filterPanelState.value?.open === true);
@@ -14120,12 +15559,15 @@ ${refCsv}` : ""}`);
14120
15559
  document.removeEventListener("scroll", closeFilterPanel, true);
14121
15560
  });
14122
15561
  const applyFilterValues = (colKey, values) => {
14123
- props.onFilterValuesChange?.({ ...props.filterValues ?? {}, [colKey]: values });
15562
+ filtering.model.setFilterValues({ ...effectiveFilterValues.value, [colKey]: values });
15563
+ if (props.recentFilters && values.length > 0) recent.record(colKey, values);
15564
+ };
15565
+ const applyRecentFilter = (entry) => {
15566
+ applyFilterValues(entry.key, entry.values);
15567
+ closeFilterPanel();
14124
15568
  };
14125
15569
  const clearFilterValues = (colKey) => {
14126
- const next = { ...props.filterValues ?? {} };
14127
- delete next[colKey];
14128
- props.onFilterValuesChange?.(next);
15570
+ filtering.model.clearColumnFilterValues(colKey);
14129
15571
  };
14130
15572
  const toggleFilterDraft = (value) => {
14131
15573
  filterDraft.value = filterDraft.value.includes(value) ? filterDraft.value.filter((v) => v !== value) : [...filterDraft.value, value];
@@ -14136,12 +15578,14 @@ ${refCsv}` : ""}`);
14136
15578
  panelRef: filterPanelRef,
14137
15579
  filterDraft,
14138
15580
  columns: displayColumns.value,
14139
- filterValues: props.filterValues,
15581
+ filterValues: effectiveFilterValues.value,
14140
15582
  t,
14141
15583
  close: closeFilterPanel,
14142
15584
  toggle: toggleFilterDraft,
14143
15585
  apply: applyFilterValues,
14144
- clear: clearFilterValues
15586
+ clear: clearFilterValues,
15587
+ recent: props.recentFilters ? recentEntries.value : [],
15588
+ onApplyRecent: applyRecentFilter
14145
15589
  });
14146
15590
  return () => {
14147
15591
  const showSelection = props.selectable !== "none";
@@ -14270,6 +15714,13 @@ ${refCsv}` : ""}`);
14270
15714
  multiSortSeq,
14271
15715
  renderFilterTrigger,
14272
15716
  pinnedDragHandle,
15717
+ pinOf,
15718
+ pinnedColumnsControlled: props.pinnedColumns !== void 0,
15719
+ columnPinMenu: props.columnPinMenu,
15720
+ pinnedStyle,
15721
+ onHeaderContextMenu: props.columnPinMenu ? handleHeaderContextMenu : void 0,
15722
+ columnFadeAttr,
15723
+ columnFadeStyle,
14273
15724
  gridTemplate
14274
15725
  },
14275
15726
  matrix
@@ -14370,7 +15821,8 @@ ${refCsv}` : ""}`);
14370
15821
  key: col.key,
14371
15822
  role: "columnheader",
14372
15823
  "data-iris-table-header": col.key,
14373
- "data-iris-table-pinned": col.pinned,
15824
+ "data-iris-table-pinned": pinOf(col),
15825
+ ...columnFadeAttrs(col),
14374
15826
  // Column drag-sort (vxe columnDragConfig parity, batch Y): the
14375
15827
  // header cell is the press target; grouped headers are NOT
14376
15828
  // supported (documented simplification — the reorder maps leaf
@@ -14379,6 +15831,7 @@ ${refCsv}` : ""}`);
14379
15831
  "data-iris-col-drag-over": props.columnDrag && colDragState.value.overId === col.key ? "true" : void 0,
14380
15832
  onPointerdown: props.columnDrag && !grouped.value ? (e) => handleColDragPointerDown(e, col.key) : void 0,
14381
15833
  onClick: () => onHeaderClick(col),
15834
+ ...props.columnPinMenu ? { onContextmenu: (event) => handleHeaderContextMenu(event, col) } : {},
14382
15835
  style: {
14383
15836
  position: "relative",
14384
15837
  display: "flex",
@@ -14395,8 +15848,9 @@ ${refCsv}` : ""}`);
14395
15848
  whiteSpace: "nowrap",
14396
15849
  overflow: "hidden",
14397
15850
  textOverflow: "ellipsis",
15851
+ ...columnFadeStyle(col) ?? {},
14398
15852
  ...visibleColSet.value ? { gridColumnStart: String(colTrack(ci)) } : {},
14399
- ...col.pinned ? { ...pinnedStyle(col.key), background: "var(--iris-surface)" } : {}
15853
+ ...pinOf(col) !== null ? { ...pinnedStyle(col.key), background: "var(--iris-surface)" } : {}
14400
15854
  },
14401
15855
  "aria-sort": ariaSortFor(col)
14402
15856
  },
@@ -14574,7 +16028,7 @@ ${refCsv}` : ""}`);
14574
16028
  const cellSlot = slots[`cell.${col.key}`];
14575
16029
  const isRowEditing = rowMode.value && rowEditing.value !== null && rowEditing.value.k === id && rowSessions.value.has(cellId(id, col.key));
14576
16030
  const isEditing = isRowEditing || editingCellId.value === cellId(id, col.key);
14577
- const patternHint = (props.pattern || props.patternFill) && !rowMode.value && editingColumnKey.value === col.key && !isEditing && editingDraft.value !== "" && String(getCellValue(row, col) ?? "") === editingDraft.value;
16031
+ const patternHint = (props.pattern || props.patternFill) && !rowMode.value && editingColumnKey.value === col.key && !isEditing && editingDraft.value !== "" && String(getCellValue2(row, col) ?? "") === editingDraft.value;
14578
16032
  let content;
14579
16033
  if (isEditing) {
14580
16034
  const editCellId = cellId(id, col.key);
@@ -14587,7 +16041,11 @@ ${refCsv}` : ""}`);
14587
16041
  rowSessions.value.get(editCellId)
14588
16042
  ) : buildCellEditContent(row, col, index, editCellId);
14589
16043
  } else {
14590
- content = cellSlot?.({ row, index, value: getCellValue(row, col) }) ?? core.tableDisplayText(row, col, getCellValue);
16044
+ content = cellSlot?.({ row, index, value: getCellValue2(row, col) }) ?? (props.searchHighlight ? (() => {
16045
+ const displayValue = core.applyTableMask(getCellValue2(row, col), col);
16046
+ const renderedValue = col.formatter ? col.formatter(displayValue, row) : displayValue;
16047
+ return applySearchHighlight(renderedValue, props.searchHighlight);
16048
+ })() : core.tableDisplayText(row, col, getCellValue2));
14591
16049
  }
14592
16050
  const treeIndent = treeMeta && ci === 0 ? buildTreeIndent(treeMeta, row) : null;
14593
16051
  const cellChildren = treeIndent ? [treeIndent, ...Array.isArray(content) ? content : [content]] : content;
@@ -14598,7 +16056,8 @@ ${refCsv}` : ""}`);
14598
16056
  key: col.key,
14599
16057
  role: "cell",
14600
16058
  "data-iris-table-cell": col.key,
14601
- "data-iris-table-pinned": col.pinned,
16059
+ "data-iris-table-pinned": pinOf(col),
16060
+ ...columnFadeAttrs(col),
14602
16061
  "data-editable": isEditableColumn(col) ? "" : void 0,
14603
16062
  "data-editing": isEditing ? "" : void 0,
14604
16063
  "data-iris-input-hint": patternHint ? "true" : void 0,
@@ -14651,7 +16110,8 @@ ${refCsv}` : ""}`);
14651
16110
  ...patternHint ? {
14652
16111
  backgroundImage: "linear-gradient(var(--iris-input-hint, rgba(251, 191, 36, 0.16)), var(--iris-input-hint, rgba(251, 191, 36, 0.16)))"
14653
16112
  } : {},
14654
- ...pinnedStyle(col.key)
16113
+ ...pinnedStyle(col.key),
16114
+ ...columnFadeStyle(col) ?? {}
14655
16115
  }
14656
16116
  },
14657
16117
  cellChildren
@@ -14681,7 +16141,7 @@ ${refCsv}` : ""}`);
14681
16141
  display: "grid",
14682
16142
  gridTemplateColumns: gridTemplate.value,
14683
16143
  background: selected ? "var(--iris-surface-hover)" : "transparent",
14684
- transition: "background-color 120ms ease",
16144
+ transition: columnFadeActive.value ? "background-color 120ms ease, grid-template-columns var(--iris-duration-md, 200ms) ease" : "background-color 120ms ease",
14685
16145
  cursor: "default",
14686
16146
  ...style
14687
16147
  }
@@ -14778,8 +16238,11 @@ ${refCsv}` : ""}`);
14778
16238
  visibleColSet: visibleColSet.value,
14779
16239
  gridTemplate: gridTemplate.value,
14780
16240
  leadingCells: leadSummaryCells(),
16241
+ columnFadeAttr,
16242
+ columnFadeStyle,
14781
16243
  colTrack,
14782
- getCellValue,
16244
+ getCellValue: getCellValue2,
16245
+ pinOf,
14783
16246
  pinnedStyle
14784
16247
  }) : null;
14785
16248
  const rootNodes = [
@@ -14809,6 +16272,7 @@ ${refCsv}` : ""}`);
14809
16272
  // grid/table role as before (treegrid implies managed cell focus).
14810
16273
  role: props.keyboardNavigation ? treeMode.value ? "treegrid" : "grid" : "table",
14811
16274
  "data-iris-table": "",
16275
+ "data-iris-column-fade-active": columnFadeActive.value ? "true" : void 0,
14812
16276
  "data-density": effectiveDensity.value,
14813
16277
  "data-printable": props.printable ? "true" : void 0,
14814
16278
  "data-virtual": props.virtualScroll ? "" : void 0,
@@ -14842,7 +16306,7 @@ ${refCsv}` : ""}`);
14842
16306
  ...attrs.style ?? {}
14843
16307
  }
14844
16308
  },
14845
- [headerRow, bodyNode, summaryRow, buildPagerSection()]
16309
+ [headerRow, bodyNode, summaryRow, buildPagerSection(), buildBackTopSection()]
14846
16310
  ),
14847
16311
  props.responsive && responsiveOverflow.value && !props.printable ? vue.h(
14848
16312
  "div",
@@ -14865,6 +16329,7 @@ ${refCsv}` : ""}`);
14865
16329
  [vue.h("span", { "aria-hidden": "true" }, "\u21C6"), vue.h("span", t("table.scrollHint"))]
14866
16330
  ) : null,
14867
16331
  buildContextMenuSection(),
16332
+ buildPinMenuSection(),
14868
16333
  buildFilterPanelSection(),
14869
16334
  buildAuditPanelSection()
14870
16335
  ].filter((n) => n !== null);
@@ -16156,16 +17621,11 @@ var IrisTree = vue.defineComponent({
16156
17621
  return out;
16157
17622
  });
16158
17623
  const checkNodes = vue.computed(() => {
16159
- const out = [];
16160
- const walk = (nodes, parentKey) => {
16161
- for (const node of nodes) {
16162
- out.push({ key: node.id, parentKey, disabled: node.disabled });
16163
- const kids = node.children ?? lazyCache.value.get(node.id);
16164
- if (kids && kids.length > 0) walk(kids, node.id);
16165
- }
16166
- };
16167
- walk(props.nodes, void 0);
16168
- return out;
17624
+ return core.flattenTreeSelectionNodes(props.nodes, {
17625
+ getKey: (node) => node.id,
17626
+ getChildren: (node) => node.children ?? lazyCache.value.get(node.id),
17627
+ isDisabled: (node) => node.disabled === true
17628
+ });
16169
17629
  });
16170
17630
  let checkModel = core.createTreeSelection({
16171
17631
  nodes: checkNodes.value,
@@ -16573,7 +18033,7 @@ var ARROW_BTN = {
16573
18033
  fontSize: "var(--iris-font-size-xl, 18px)",
16574
18034
  lineHeight: "1"
16575
18035
  };
16576
- function prefersReducedMotion() {
18036
+ function prefersReducedMotion2() {
16577
18037
  return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
16578
18038
  }
16579
18039
  function flattenSlides(nodes) {
@@ -16621,7 +18081,7 @@ var IrisCarousel = vue.defineComponent({
16621
18081
  };
16622
18082
  const start = () => {
16623
18083
  stop();
16624
- if (!props.autoplay || prefersReducedMotion()) return;
18084
+ if (!props.autoplay || prefersReducedMotion2()) return;
16625
18085
  timer = setInterval(() => {
16626
18086
  if (props.pauseOnHover && hovered.value || focusedWithin.value) return;
16627
18087
  const list = flattenSlides(slots.default?.() ?? []);
@@ -17395,7 +18855,7 @@ var IrisWatermark = vue.defineComponent({
17395
18855
  );
17396
18856
  }
17397
18857
  });
17398
- function prefersReducedMotion2() {
18858
+ function prefersReducedMotion3() {
17399
18859
  return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
17400
18860
  }
17401
18861
  var resolve = (target) => (target ? target() : window) ?? window;
@@ -17433,7 +18893,7 @@ var IrisBackTop = vue.defineComponent({
17433
18893
  });
17434
18894
  const scrollToTop = () => {
17435
18895
  if (!el) return;
17436
- const b = prefersReducedMotion2() ? "auto" : props.behavior;
18896
+ const b = prefersReducedMotion3() ? "auto" : props.behavior;
17437
18897
  if (typeof el.scrollTo === "function") {
17438
18898
  el.scrollTo({ top: 0, behavior: b });
17439
18899
  } else {
@@ -18921,7 +20381,7 @@ var IrisFloatButton = vue.defineComponent({
18921
20381
  };
18922
20382
  }
18923
20383
  });
18924
- function prefersReducedMotion3() {
20384
+ function prefersReducedMotion4() {
18925
20385
  return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
18926
20386
  }
18927
20387
  var resolve3 = (target) => (target ? target() : window) ?? window;
@@ -18965,7 +20425,7 @@ var IrisAnchor = vue.defineComponent({
18965
20425
  if (!node) return;
18966
20426
  if (typeof node.scrollIntoView === "function") {
18967
20427
  node.scrollIntoView({
18968
- behavior: prefersReducedMotion3() ? "auto" : "smooth",
20428
+ behavior: prefersReducedMotion4() ? "auto" : "smooth",
18969
20429
  block: "start"
18970
20430
  });
18971
20431
  }
@@ -20136,7 +21596,7 @@ var IrisGauge = vue.defineComponent({
20136
21596
  };
20137
21597
  }
20138
21598
  });
20139
- function prefersReducedMotion4() {
21599
+ function prefersReducedMotion5() {
20140
21600
  return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
20141
21601
  }
20142
21602
  var IrisMarquee = vue.defineComponent({
@@ -20155,7 +21615,7 @@ var IrisMarquee = vue.defineComponent({
20155
21615
  let anim = null;
20156
21616
  vue.onMounted(() => {
20157
21617
  const el = trackEl.value;
20158
- if (!el || typeof el.animate !== "function" || prefersReducedMotion4()) return;
21618
+ if (!el || typeof el.animate !== "function" || prefersReducedMotion5()) return;
20159
21619
  const frames = props.direction === "left" ? [{ transform: "translateX(0%)" }, { transform: "translateX(-50%)" }] : [{ transform: "translateX(-50%)" }, { transform: "translateX(0%)" }];
20160
21620
  anim = el.animate(frames, {
20161
21621
  duration: Math.max(1, props.duration) * 1e3,
@@ -21436,7 +22896,7 @@ var IrisColorPicker = vue.defineComponent({
21436
22896
  color: "var(--iris-foreground)",
21437
22897
  border: "1px solid var(--iris-border)",
21438
22898
  borderRadius: "var(--iris-radius-sm, 4px)",
21439
- fontSize: "12px",
22899
+ fontSize: "var(--iris-font-size-xs, 12px)",
21440
22900
  fontFamily: "inherit",
21441
22901
  textAlign: "center"
21442
22902
  };
@@ -21530,7 +22990,7 @@ var IrisColorPicker = vue.defineComponent({
21530
22990
  height: "4px",
21531
22991
  transform: "translateY(-50%)",
21532
22992
  border: "2px solid #fff",
21533
- borderRadius: "2px",
22993
+ borderRadius: "var(--iris-radius-sm, 4px)",
21534
22994
  boxShadow: "0 0 0 1px rgba(0,0,0,.4)",
21535
22995
  pointerEvents: "none"
21536
22996
  }
@@ -21582,7 +23042,7 @@ var IrisColorPicker = vue.defineComponent({
21582
23042
  display: "flex",
21583
23043
  gap: "6px",
21584
23044
  alignItems: "center",
21585
- fontSize: "12px"
23045
+ fontSize: "var(--iris-font-size-xs, 12px)"
21586
23046
  }
21587
23047
  },
21588
23048
  [
@@ -21664,12 +23124,12 @@ function defaultFilter(query, item) {
21664
23124
  if (!q) return 0;
21665
23125
  const haystacks = [item.label, ...item.keywords ?? []].map((s) => s.toLowerCase());
21666
23126
  let best = null;
21667
- for (const h137 of haystacks) {
23127
+ for (const h139 of haystacks) {
21668
23128
  let qi = 0;
21669
23129
  let lastIdx = -1;
21670
23130
  let score = 0;
21671
- for (let i = 0; i < h137.length && qi < q.length; i += 1) {
21672
- if (h137[i] === q[qi]) {
23131
+ for (let i = 0; i < h139.length && qi < q.length; i += 1) {
23132
+ if (h139[i] === q[qi]) {
21673
23133
  score += lastIdx === -1 ? i : i - lastIdx - 1;
21674
23134
  lastIdx = i;
21675
23135
  qi += 1;
@@ -23945,6 +25405,18 @@ exports.useFloating = useFloating;
23945
25405
  exports.useFocusTrap = useFocusTrap;
23946
25406
  exports.useForm = useForm;
23947
25407
  exports.useFormContext = useFormContext;
25408
+ exports.useGridClipboard = useGridClipboard;
25409
+ exports.useGridColumns = useGridColumns;
25410
+ exports.useGridCore = useGridCore;
25411
+ exports.useGridEditing = useGridEditing;
25412
+ exports.useGridExpansion = useGridExpansion;
25413
+ exports.useGridFiltering = useGridFiltering;
25414
+ exports.useGridPagination = useGridPagination;
25415
+ exports.useGridRange = useGridRange;
25416
+ exports.useGridRows = useGridRows;
25417
+ exports.useGridSelection = useGridSelection;
25418
+ exports.useGridSorting = useGridSorting;
25419
+ exports.useGridVirtual = useGridVirtual;
23948
25420
  exports.useGroupedView = useGroupedView;
23949
25421
  exports.useI18n = useI18n;
23950
25422
  exports.useMachine = useMachine;