snice 7.7.0 → 7.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/react/SniceProvider.js +1 -1
- package/adapters/react/SniceRouter.js +1 -1
- package/adapters/react/calendar.d.ts +1 -0
- package/adapters/react/calendar.d.ts.map +1 -1
- package/adapters/react/calendar.js +1 -1
- package/adapters/react/calendar.js.map +1 -1
- package/adapters/react/calendar.tsx +2 -1
- package/adapters/react/matchRoute.js +1 -1
- package/adapters/react/useRequestHandler.js +1 -1
- package/bin/analyzer-contracts.js +8 -2
- package/custom-elements.json +5 -1
- package/dist/cdn/accordion/snice-accordion.js +1 -1
- package/dist/cdn/action-bar/snice-action-bar.js +1 -1
- package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
- package/dist/cdn/alert/snice-alert.js +1 -1
- package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
- package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
- package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
- package/dist/cdn/availability/snice-availability.js +1 -1
- package/dist/cdn/avatar/snice-avatar.js +1 -1
- package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
- package/dist/cdn/badge/snice-badge.js +1 -1
- package/dist/cdn/banner/snice-banner.js +1 -1
- package/dist/cdn/binpack/snice-binpack.js +1 -1
- package/dist/cdn/book/snice-book.js +1 -1
- package/dist/cdn/booking/snice-booking.js +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
- package/dist/cdn/button/snice-button.js +1 -1
- package/dist/cdn/calendar/README.md +2 -2
- package/dist/cdn/calendar/snice-calendar.js +26 -2
- package/dist/cdn/calendar/snice-calendar.js.map +1 -1
- package/dist/cdn/calendar/snice-calendar.min.js +2 -2
- package/dist/cdn/calendar/snice-calendar.min.js.map +1 -1
- package/dist/cdn/camera/snice-camera.js +1 -1
- package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
- package/dist/cdn/candlestick/snice-candlestick.js +1 -1
- package/dist/cdn/card/snice-card.js +1 -1
- package/dist/cdn/carousel/snice-carousel.js +1 -1
- package/dist/cdn/cart/snice-cart.js +1 -1
- package/dist/cdn/chart/snice-chart.js +1 -1
- package/dist/cdn/chat/snice-chat.js +1 -1
- package/dist/cdn/checkbox/snice-checkbox.js +1 -1
- package/dist/cdn/chip/snice-chip.js +1 -1
- package/dist/cdn/code-block/snice-code-block.js +1 -1
- package/dist/cdn/color-display/snice-color-display.js +1 -1
- package/dist/cdn/color-picker/snice-color-picker.js +1 -1
- package/dist/cdn/command-palette/snice-command-palette.js +1 -1
- package/dist/cdn/comments/snice-comments.js +1 -1
- package/dist/cdn/countdown/snice-countdown.js +1 -1
- package/dist/cdn/cropper/snice-cropper.js +1 -1
- package/dist/cdn/data-card/snice-data-card.js +1 -1
- package/dist/cdn/date-picker/snice-date-picker.js +1 -1
- package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
- package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
- package/dist/cdn/diff/snice-diff.js +1 -1
- package/dist/cdn/divider/snice-divider.js +1 -1
- package/dist/cdn/doc/snice-doc.js +1 -1
- package/dist/cdn/draw/snice-draw.js +1 -1
- package/dist/cdn/drawer/snice-drawer.js +1 -1
- package/dist/cdn/empty-state/snice-empty-state.js +1 -1
- package/dist/cdn/estimate/snice-estimate.js +1 -1
- package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
- package/dist/cdn/file-upload/snice-file-upload.js +1 -1
- package/dist/cdn/flip-card/snice-flip-card.js +1 -1
- package/dist/cdn/flow/snice-flow.js +1 -1
- package/dist/cdn/form-layout/snice-form-layout.js +1 -1
- package/dist/cdn/funnel/snice-funnel.js +1 -1
- package/dist/cdn/gantt/snice-gantt.js +1 -1
- package/dist/cdn/gauge/snice-gauge.js +1 -1
- package/dist/cdn/grid/snice-grid.js +1 -1
- package/dist/cdn/heatmap/snice-heatmap.js +1 -1
- package/dist/cdn/image/snice-image.js +1 -1
- package/dist/cdn/input/snice-input.js +1 -1
- package/dist/cdn/invoice/snice-invoice.js +1 -1
- package/dist/cdn/kanban/snice-kanban.js +1 -1
- package/dist/cdn/key-value/snice-key-value.js +1 -1
- package/dist/cdn/kpi/snice-kpi.js +1 -1
- package/dist/cdn/layout/snice-layout.js +1 -1
- package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
- package/dist/cdn/link/snice-link.js +1 -1
- package/dist/cdn/link-preview/snice-link-preview.js +1 -1
- package/dist/cdn/list/snice-list.js +1 -1
- package/dist/cdn/location/snice-location.js +1 -1
- package/dist/cdn/login/snice-login.js +1 -1
- package/dist/cdn/map/snice-map.js +1 -1
- package/dist/cdn/markdown/snice-markdown.js +1 -1
- package/dist/cdn/masonry/snice-masonry.js +1 -1
- package/dist/cdn/menu/snice-menu.js +1 -1
- package/dist/cdn/message-strip/snice-message-strip.js +1 -1
- package/dist/cdn/modal/snice-modal.js +1 -1
- package/dist/cdn/music-player/snice-music-player.js +1 -1
- package/dist/cdn/nav/snice-nav.js +1 -1
- package/dist/cdn/network-graph/snice-network-graph.js +1 -1
- package/dist/cdn/notification-center/snice-notification-center.js +1 -1
- package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
- package/dist/cdn/org-chart/snice-org-chart.js +1 -1
- package/dist/cdn/pagination/snice-pagination.js +1 -1
- package/dist/cdn/paint/snice-paint.js +1 -1
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
- package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
- package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
- package/dist/cdn/popover/snice-popover.js +1 -1
- package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
- package/dist/cdn/product-card/snice-product-card.js +1 -1
- package/dist/cdn/progress/snice-progress.js +1 -1
- package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
- package/dist/cdn/qr-code/snice-qr-code.js +1 -1
- package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
- package/dist/cdn/radio/snice-radio.js +1 -1
- package/dist/cdn/range-slider/snice-range-slider.js +1 -1
- package/dist/cdn/rating/snice-rating.js +1 -1
- package/dist/cdn/receipt/snice-receipt.js +1 -1
- package/dist/cdn/recipe/snice-recipe.js +1 -1
- package/dist/cdn/runtime/README.md +2 -2
- package/dist/cdn/runtime/snice-runtime.esm.js +130 -31
- package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.js +130 -31
- package/dist/cdn/runtime/snice-runtime.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
- package/dist/cdn/sankey/snice-sankey.js +1 -1
- package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
- package/dist/cdn/select/snice-select.js +1 -1
- package/dist/cdn/skeleton/snice-skeleton.js +1 -1
- package/dist/cdn/slider/snice-slider.js +1 -1
- package/dist/cdn/sortable/snice-sortable.js +1 -1
- package/dist/cdn/sparkline/snice-sparkline.js +1 -1
- package/dist/cdn/spinner/snice-spinner.js +1 -1
- package/dist/cdn/split-button/snice-split-button.js +1 -1
- package/dist/cdn/split-pane/snice-split-pane.js +1 -1
- package/dist/cdn/spotlight/snice-spotlight.js +1 -1
- package/dist/cdn/stat-group/snice-stat-group.js +1 -1
- package/dist/cdn/step-input/snice-step-input.js +1 -1
- package/dist/cdn/stepper/snice-stepper.js +1 -1
- package/dist/cdn/switch/snice-switch.js +1 -1
- package/dist/cdn/table/README.md +2 -2
- package/dist/cdn/table/snice-table.esm.js +157 -33
- package/dist/cdn/table/snice-table.esm.js.map +1 -1
- package/dist/cdn/table/snice-table.esm.min.js +4 -4
- package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
- package/dist/cdn/table/snice-table.js +157 -33
- package/dist/cdn/table/snice-table.js.map +1 -1
- package/dist/cdn/table/snice-table.min.js +33 -33
- package/dist/cdn/table/snice-table.min.js.map +1 -1
- package/dist/cdn/tabs/snice-tabs.js +1 -1
- package/dist/cdn/tag/snice-tag.js +1 -1
- package/dist/cdn/tag-input/README.md +2 -2
- package/dist/cdn/tag-input/snice-tag-input.js +10 -16
- package/dist/cdn/tag-input/snice-tag-input.js.map +1 -1
- package/dist/cdn/tag-input/snice-tag-input.min.js +2 -2
- package/dist/cdn/tag-input/snice-tag-input.min.js.map +1 -1
- package/dist/cdn/terminal/snice-terminal.js +1 -1
- package/dist/cdn/testimonial/snice-testimonial.js +1 -1
- package/dist/cdn/textarea/snice-textarea.js +1 -1
- package/dist/cdn/time-picker/snice-time-picker.js +1 -1
- package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
- package/dist/cdn/timeline/snice-timeline.js +1 -1
- package/dist/cdn/timer/snice-timer.js +1 -1
- package/dist/cdn/toast/snice-toast.js +1 -1
- package/dist/cdn/tooltip/snice-tooltip.js +1 -1
- package/dist/cdn/tree/snice-tree.js +1 -1
- package/dist/cdn/treemap/snice-treemap.js +1 -1
- package/dist/cdn/user-card/snice-user-card.js +1 -1
- package/dist/cdn/video-player/snice-video-player.js +1 -1
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
- package/dist/cdn/waterfall/snice-waterfall.js +1 -1
- package/dist/cdn/weather/snice-weather.js +1 -1
- package/dist/cdn/work-order/snice-work-order.js +1 -1
- package/dist/components/calendar/snice-calendar.d.ts +2 -0
- package/dist/components/calendar/snice-calendar.js +25 -1
- package/dist/components/calendar/snice-calendar.js.map +1 -1
- package/dist/components/table/snice-row.d.ts +6 -0
- package/dist/components/table/snice-row.js +11 -3
- package/dist/components/table/snice-row.js.map +1 -1
- package/dist/components/table/snice-table.d.ts +31 -3
- package/dist/components/table/snice-table.js +89 -16
- package/dist/components/table/snice-table.js.map +1 -1
- package/dist/components/table/table-cell-presentation.d.ts +11 -0
- package/dist/components/table/table-cell-presentation.js +25 -8
- package/dist/components/table/table-cell-presentation.js.map +1 -1
- package/dist/components/table/table-export.d.ts +6 -0
- package/dist/components/table/table-export.js +11 -2
- package/dist/components/table/table-export.js.map +1 -1
- package/dist/components/table/table-master-detail.d.ts +11 -2
- package/dist/components/table/table-master-detail.js +20 -3
- package/dist/components/table/table-master-detail.js.map +1 -1
- package/dist/components/tag-input/snice-tag-input.d.ts +2 -2
- package/dist/components/tag-input/snice-tag-input.js +9 -15
- package/dist/components/tag-input/snice-tag-input.js.map +1 -1
- package/dist/index.cjs +127 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +127 -28
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +127 -28
- package/dist/index.iife.js.map +1 -1
- package/dist/react/SniceProvider.js +1 -1
- package/dist/react/SniceRouter.js +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/matchRoute.js +1 -1
- package/dist/react/useRequestHandler.js +1 -1
- package/dist/request-response.d.ts +24 -1
- package/dist/symbols.cjs +1 -1
- package/dist/symbols.esm.js +1 -1
- package/dist/testing-dom.cjs +1 -1
- package/dist/testing-dom.esm.js +1 -1
- package/dist/transitions.cjs +1 -1
- package/dist/transitions.esm.js +1 -1
- package/docs/ai/components/calendar.md +8 -0
- package/docs/ai/components/tag-input.md +4 -0
- package/docs/ai/controllers.md +22 -6
- package/docs/components/calendar.md +24 -0
- package/docs/components/tag-input.md +6 -1
- package/docs/controllers.md +57 -6
- package/package.json +1 -1
package/dist/cdn/table/README.md
CHANGED
|
@@ -11,8 +11,8 @@ CDN build of the Snice table component. Requires the Snice runtime (`snice-runti
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Size
|
|
14
|
-
- Minified:
|
|
15
|
-
- Gzipped: 110.
|
|
14
|
+
- Minified: 548.9 KB
|
|
15
|
+
- Gzipped: 110.5 KB
|
|
16
16
|
|
|
17
17
|
## Bundled Components
|
|
18
18
|
This CDN build includes the following child components:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
2
|
+
* snice v7.7.0
|
|
3
3
|
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
@@ -4272,6 +4272,17 @@ const appliedClasses = new WeakMap();
|
|
|
4272
4272
|
* Give every standalone specialized table cell the common ColumnDefinition
|
|
4273
4273
|
* presentation contract. The observer reapplies after Snice's differential
|
|
4274
4274
|
* renderer changes shadow content; equality guards prevent mutation loops.
|
|
4275
|
+
*
|
|
4276
|
+
* Display formatters
|
|
4277
|
+
* ------------------
|
|
4278
|
+
* `formatter` is the column's display formatter and `valueFormatter` its
|
|
4279
|
+
* fallback, so `formatter` wins. `formatterOverride` says the cell does NOT
|
|
4280
|
+
* apply `column.formatter` itself, so this module owns both formatters for it.
|
|
4281
|
+
*
|
|
4282
|
+
* A host marked `formatted` was handed its display text as `value` by the
|
|
4283
|
+
* table, which already resolved the pipeline. Its content is that text
|
|
4284
|
+
* verbatim — the marker exists so a rating/progress/link/tag cell, which
|
|
4285
|
+
* renders structure rather than text, still shows the formatted string.
|
|
4275
4286
|
*/
|
|
4276
4287
|
function installCellPresentation(host, formatterOverride = false) {
|
|
4277
4288
|
if (installed.has(host))
|
|
@@ -4318,15 +4329,21 @@ function installCellPresentation(host, formatterOverride = false) {
|
|
|
4318
4329
|
? column.tooltip(host.value, host.rowData)
|
|
4319
4330
|
: (host.value == null ? '' : String(host.value));
|
|
4320
4331
|
}
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4332
|
+
// `formatter` beats `valueFormatter`. A cell that applies `formatter`
|
|
4333
|
+
// itself has already painted it, so the fallback must not overwrite that
|
|
4334
|
+
// content — only a formatter-less column falls through to valueFormatter.
|
|
4335
|
+
const formatter = column.formatter
|
|
4336
|
+
? (formatterOverride ? column.formatter : null)
|
|
4337
|
+
: column.valueFormatter;
|
|
4338
|
+
// Pre-formatted hosts read the attribute, not the property: a cell whose
|
|
4339
|
+
// value property is typed (boolean, number) coerces the display string.
|
|
4340
|
+
const display = host.hasAttribute('formatted')
|
|
4341
|
+
? (host.getAttribute('value') ?? '')
|
|
4342
|
+
: (formatter ? String(formatter(host.value, host.rowData) ?? '') : null);
|
|
4343
|
+
if (display !== null) {
|
|
4325
4344
|
const content = host.shadowRoot?.querySelector('[part~="content"]');
|
|
4326
|
-
if (
|
|
4327
|
-
|
|
4328
|
-
if (content.textContent !== value || content.childElementCount > 0)
|
|
4329
|
-
content.textContent = value;
|
|
4345
|
+
if (content && (content.textContent !== display || content.childElementCount > 0)) {
|
|
4346
|
+
content.textContent = display;
|
|
4330
4347
|
}
|
|
4331
4348
|
}
|
|
4332
4349
|
}
|
|
@@ -9419,6 +9436,15 @@ var cssContent$1 = ":host([hidden]){display:none}:host{display:block;width:100%;
|
|
|
9419
9436
|
this.updateRowAttributes();
|
|
9420
9437
|
this.configureCells();
|
|
9421
9438
|
}
|
|
9439
|
+
/**
|
|
9440
|
+
* The column's working value for this row: `valueGetter` runs for display
|
|
9441
|
+
* here exactly as it does on the table's own render path, so a column whose
|
|
9442
|
+
* key names a server field rather than a row field still paints.
|
|
9443
|
+
*/
|
|
9444
|
+
cellValue(column) {
|
|
9445
|
+
const raw = this.data?.[column.key];
|
|
9446
|
+
return column.valueGetter ? column.valueGetter(raw, this.data) : raw;
|
|
9447
|
+
}
|
|
9422
9448
|
configureCells() {
|
|
9423
9449
|
if (!this.columns) {
|
|
9424
9450
|
return;
|
|
@@ -9426,7 +9452,7 @@ var cssContent$1 = ":host([hidden]){display:none}:host{display:block;width:100%;
|
|
|
9426
9452
|
this.columns.forEach((column, index) => {
|
|
9427
9453
|
const cellElement = this.shadowRoot?.querySelector(`[data-column-index="${index}"]`);
|
|
9428
9454
|
if (cellElement) {
|
|
9429
|
-
const value = this.
|
|
9455
|
+
const value = this.cellValue(column);
|
|
9430
9456
|
if (typeof column.renderCell === 'function') {
|
|
9431
9457
|
const output = column.renderCell(value, this.data, column);
|
|
9432
9458
|
const replacement = output instanceof HTMLElement ? output : document.createElement('span');
|
|
@@ -9438,7 +9464,7 @@ var cssContent$1 = ":host([hidden]){display:none}:host{display:block;width:100%;
|
|
|
9438
9464
|
return;
|
|
9439
9465
|
}
|
|
9440
9466
|
const formatter = column.formatter || column.valueFormatter;
|
|
9441
|
-
cellElement.value = formatter ? formatter(value, this.data) : value;
|
|
9467
|
+
cellElement.value = formatter ? String(formatter(value, this.data) ?? '') : value;
|
|
9442
9468
|
cellElement.column = formatter
|
|
9443
9469
|
? { ...column, type: 'text', formatter: undefined, valueFormatter: undefined }
|
|
9444
9470
|
: column;
|
|
@@ -9528,7 +9554,6 @@ var cssContent$1 = ":host([hidden]){display:none}:host{display:block;width:100%;
|
|
|
9528
9554
|
`;
|
|
9529
9555
|
}
|
|
9530
9556
|
renderCell(column, columnIndex) {
|
|
9531
|
-
this.data[column.key];
|
|
9532
9557
|
const cellComponent = this.getCellComponent(column.formatter || column.valueFormatter ? 'text' : column.type);
|
|
9533
9558
|
return html /*html*/ `
|
|
9534
9559
|
<div class="cell cell--${column.type}" part="cell" style="${this.getCellStyles(column)}">
|
|
@@ -11142,7 +11167,7 @@ class TableExport {
|
|
|
11142
11167
|
// Data rows
|
|
11143
11168
|
for (const row of data) {
|
|
11144
11169
|
const cells = exportColumns.map(c => {
|
|
11145
|
-
const value = row
|
|
11170
|
+
const value = this.cellValue(row, c);
|
|
11146
11171
|
return this.escapeCSV(value == null ? '' : String(value), delimiter);
|
|
11147
11172
|
});
|
|
11148
11173
|
rows.push(cells.join(delimiter));
|
|
@@ -11254,7 +11279,7 @@ class TableExport {
|
|
|
11254
11279
|
if (rows.length === 0)
|
|
11255
11280
|
return false;
|
|
11256
11281
|
const text = rows.map(row => columns.map(c => {
|
|
11257
|
-
const value = row
|
|
11282
|
+
const value = this.cellValue(row, c);
|
|
11258
11283
|
return useFormatted ? this.formatValue(value, row, c) : this.rawValue(value);
|
|
11259
11284
|
}).join(delimiter)).join('\n');
|
|
11260
11285
|
try {
|
|
@@ -11279,6 +11304,15 @@ class TableExport {
|
|
|
11279
11304
|
}
|
|
11280
11305
|
return value;
|
|
11281
11306
|
}
|
|
11307
|
+
/**
|
|
11308
|
+
* The column's working value for a row: `valueGetter` "runs for display,
|
|
11309
|
+
* sort, aggregation" — and for export, which must ship what the table shows,
|
|
11310
|
+
* not a row field the column may not even have.
|
|
11311
|
+
*/
|
|
11312
|
+
cellValue(row, column) {
|
|
11313
|
+
const raw = row?.[column.key];
|
|
11314
|
+
return column.valueGetter ? column.valueGetter(raw, row) : raw;
|
|
11315
|
+
}
|
|
11282
11316
|
rawValue(value) {
|
|
11283
11317
|
if (value == null)
|
|
11284
11318
|
return '';
|
|
@@ -11383,6 +11417,8 @@ class TableMasterDetail {
|
|
|
11383
11417
|
this.options = null;
|
|
11384
11418
|
this.tableElement = null;
|
|
11385
11419
|
this.contentCache = new Map();
|
|
11420
|
+
/** Last value signature seen per row, so in-place mutations evict the cache. */
|
|
11421
|
+
this.fingerprints = new Map();
|
|
11386
11422
|
this.rowByIndex = new Map();
|
|
11387
11423
|
this.measuredHeights = new Map();
|
|
11388
11424
|
this.onHeightChange = null;
|
|
@@ -11391,18 +11427,33 @@ class TableMasterDetail {
|
|
|
11391
11427
|
this.tableElement = tableEl;
|
|
11392
11428
|
this.options = options;
|
|
11393
11429
|
this.contentCache.clear();
|
|
11430
|
+
this.fingerprints.clear();
|
|
11394
11431
|
this.rowByIndex.clear();
|
|
11395
11432
|
this.measuredHeights.clear();
|
|
11396
11433
|
}
|
|
11397
|
-
/**
|
|
11398
|
-
|
|
11434
|
+
/**
|
|
11435
|
+
* Eagerly construct detail content when lazy=false.
|
|
11436
|
+
*
|
|
11437
|
+
* `fingerprint` (the table's per-row value signature) also evicts rows whose
|
|
11438
|
+
* fields were mutated in place: the cache is keyed by row identity, so an
|
|
11439
|
+
* unchanged identity would otherwise keep serving a panel built from the
|
|
11440
|
+
* pre-mutation values.
|
|
11441
|
+
*/
|
|
11442
|
+
prepare(rows, fingerprint) {
|
|
11399
11443
|
if (!this.options)
|
|
11400
11444
|
return;
|
|
11401
11445
|
this.rowByIndex = new Map(rows.map((row, index) => [index, row]));
|
|
11402
11446
|
const currentRows = new Set(rows);
|
|
11403
|
-
for (const row of this.contentCache.keys()) {
|
|
11447
|
+
for (const row of [...this.contentCache.keys()]) {
|
|
11404
11448
|
if (!currentRows.has(row))
|
|
11405
11449
|
this.contentCache.delete(row);
|
|
11450
|
+
else if (fingerprint && this.fingerprints.get(row) !== fingerprint(row)) {
|
|
11451
|
+
this.contentCache.delete(row);
|
|
11452
|
+
this.measuredHeights.delete(row);
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
if (fingerprint) {
|
|
11456
|
+
this.fingerprints = new Map(rows.map(row => [row, fingerprint(row)]));
|
|
11406
11457
|
}
|
|
11407
11458
|
for (const row of this.measuredHeights.keys()) {
|
|
11408
11459
|
if (!currentRows.has(row))
|
|
@@ -15007,8 +15058,14 @@ class TableColumnDnD {
|
|
|
15007
15058
|
if (this.settingSortedData)
|
|
15008
15059
|
return;
|
|
15009
15060
|
this.unsortedData = [...this.data];
|
|
15061
|
+
// C3: rows delivered while a client-side sort is active must land in that
|
|
15062
|
+
// sorted order — the currentSort watcher only fires when the sort itself
|
|
15063
|
+
// changes, so without this the body silently contradicts the header. In
|
|
15064
|
+
// remote mode the server owns the ordering.
|
|
15065
|
+
if (this.mode !== 'remote' && this.currentSort.length > 0)
|
|
15066
|
+
this.applyLocalSortOrder();
|
|
15010
15067
|
this.rebuildRowIndex();
|
|
15011
|
-
this.masterDetail.prepare(this.data);
|
|
15068
|
+
this.masterDetail.prepare(this.data, row => this.rowValueSignature(row));
|
|
15012
15069
|
if (this.settingDataImperative)
|
|
15013
15070
|
return; // imperative setData(): caller renders
|
|
15014
15071
|
this.scheduleRender('body');
|
|
@@ -15464,6 +15521,7 @@ class TableColumnDnD {
|
|
|
15464
15521
|
// Still render pagination
|
|
15465
15522
|
if (this.pagination)
|
|
15466
15523
|
this.renderPagination();
|
|
15524
|
+
this.updateSelectAllState();
|
|
15467
15525
|
return;
|
|
15468
15526
|
}
|
|
15469
15527
|
if (this.data.length === 0 && this.columns.length > 0) {
|
|
@@ -15680,6 +15738,10 @@ class TableColumnDnD {
|
|
|
15680
15738
|
this.reconcileRows(entries);
|
|
15681
15739
|
// Re-establish grid role + roving tabindex after the body changed.
|
|
15682
15740
|
this.keyboard.refresh();
|
|
15741
|
+
// The select-all control describes the *filtered* row set, so it goes stale
|
|
15742
|
+
// whenever the denominator moves (rows delivered, filter cleared) even
|
|
15743
|
+
// though `selectedRows` — its only other trigger — never changed.
|
|
15744
|
+
this.updateSelectAllState();
|
|
15683
15745
|
// Render pagination after body
|
|
15684
15746
|
if (this.pagination) {
|
|
15685
15747
|
this.renderPagination();
|
|
@@ -15812,15 +15874,41 @@ class TableColumnDnD {
|
|
|
15812
15874
|
`tree=${this.treeData.isEnabled() ? this.treeData.getGroupColumn() : ''}`,
|
|
15813
15875
|
].join('|');
|
|
15814
15876
|
}
|
|
15877
|
+
/**
|
|
15878
|
+
* A fingerprint of a row's own field values, so a row object that was mutated
|
|
15879
|
+
* IN PLACE (the same identity re-delivered by a poll/websocket patch, or an
|
|
15880
|
+
* array the app edits) is rebuilt instead of recycled with stale cells.
|
|
15881
|
+
*
|
|
15882
|
+
* Tradeoff, deliberately chosen: this walks the row's OWN, SHALLOW fields
|
|
15883
|
+
* rather than running each column's valueGetter/formatter. It therefore costs
|
|
15884
|
+
* one string build per rendered row — never a user callback, so call counts
|
|
15885
|
+
* for `valueGetter` stay exactly one per row per render — and it still catches
|
|
15886
|
+
* getter-derived columns, because a getter reads the fields this covers.
|
|
15887
|
+
* Nested objects collapse to their String() form, so a mutation buried inside
|
|
15888
|
+
* a nested object (or an object rendered by a JSON/progress cell) is not
|
|
15889
|
+
* detected; those callers should hand over a new row object. Recycling keeps
|
|
15890
|
+
* its payoff: rows whose values did not change are still reused untouched.
|
|
15891
|
+
*/
|
|
15892
|
+
rowValueSignature(rowData) {
|
|
15893
|
+
if (rowData === null || typeof rowData !== 'object')
|
|
15894
|
+
return String(rowData);
|
|
15895
|
+
const parts = [];
|
|
15896
|
+
for (const key of Object.keys(rowData)) {
|
|
15897
|
+
const value = rowData[key];
|
|
15898
|
+
parts.push(`${key}=${value === null || value === undefined ? '' : String(value)}`);
|
|
15899
|
+
}
|
|
15900
|
+
// A separator keeps {a:'1b'} from colliding with {a:'1',b:''}.
|
|
15901
|
+
return parts.join('|');
|
|
15902
|
+
}
|
|
15815
15903
|
/**
|
|
15816
15904
|
* The per-row rebuild signature. Selection and the plain data-index are
|
|
15817
15905
|
* re-stamped on reuse (not here) so they never force a rebuild; this captures
|
|
15818
|
-
*
|
|
15819
|
-
* when a feature embeds the index in a row-scoped
|
|
15820
|
-
* master-detail toggle index) — the index itself.
|
|
15906
|
+
* the row's cell values plus what a re-stamp cannot repair: tree node state,
|
|
15907
|
+
* per-row height, and — when a feature embeds the index in a row-scoped
|
|
15908
|
+
* closure (row DnD drag index, master-detail toggle index) — the index itself.
|
|
15821
15909
|
*/
|
|
15822
15910
|
rowSignature(rowData, index, structuralSig, treeRow) {
|
|
15823
|
-
const parts = [structuralSig];
|
|
15911
|
+
const parts = [structuralSig, `v:${this.rowValueSignature(rowData)}`];
|
|
15824
15912
|
if (treeRow)
|
|
15825
15913
|
parts.push(`t:${treeRow.depth}:${treeRow.expanded ? 1 : 0}:${treeRow.hasChildren ? 1 : 0}`);
|
|
15826
15914
|
if (this.rowHeightCallback)
|
|
@@ -16017,6 +16105,15 @@ class TableColumnDnD {
|
|
|
16017
16105
|
const active = page === this.currentPage ? ' pagination__btn--active' : '';
|
|
16018
16106
|
return `<button class="pagination__btn pagination__page${active}" data-page="${page}">${page}</button>`;
|
|
16019
16107
|
}
|
|
16108
|
+
/**
|
|
16109
|
+
* The column's display text, or null when the column declares no display
|
|
16110
|
+
* formatter. `formatter` (row-aware) is the display formatter and
|
|
16111
|
+
* `valueFormatter` its documented fallback, so `formatter` wins.
|
|
16112
|
+
*/
|
|
16113
|
+
cellDisplayText(column, value, row) {
|
|
16114
|
+
const formatter = column.formatter || column.valueFormatter;
|
|
16115
|
+
return formatter ? String(formatter(value, row) ?? '') : null;
|
|
16116
|
+
}
|
|
16020
16117
|
/** Create a cell element safely using createElement + setAttribute */
|
|
16021
16118
|
createCellElement(column, value, row) {
|
|
16022
16119
|
// E2: a custom renderCell bypasses the type-based cell entirely. A string
|
|
@@ -16041,22 +16138,38 @@ class TableColumnDnD {
|
|
|
16041
16138
|
if (column.align)
|
|
16042
16139
|
el.setAttribute('align', column.align);
|
|
16043
16140
|
el.setAttribute('in-table', 'true');
|
|
16141
|
+
// A declared display formatter makes the cell's value its DISPLAY text:
|
|
16142
|
+
// the pipeline (valueGetter -> formatter, valueFormatter as the fallback)
|
|
16143
|
+
// is resolved here, once, so the `value` channel a caller reads is what the
|
|
16144
|
+
// cell actually shows. The cell is marked `formatted` and handed a column
|
|
16145
|
+
// without the formatters, which keeps it from formatting again and tells
|
|
16146
|
+
// the shared presentation contract to paint the text over any type-specific
|
|
16147
|
+
// rendering (a rating/progress/link cell cannot render display text).
|
|
16148
|
+
const displayText = this.cellDisplayText(column, value, row);
|
|
16149
|
+
const cellColumn = displayText === null
|
|
16150
|
+
? column
|
|
16151
|
+
: { ...column, formatter: undefined, valueFormatter: undefined };
|
|
16044
16152
|
// Set the property directly so arrays/objects reach progress, sparkline,
|
|
16045
16153
|
// JSON, action, and tag cells without a lossy attribute conversion. Keep a
|
|
16046
16154
|
// primitive attribute for declarative inspection and CSS selectors.
|
|
16047
|
-
if (
|
|
16155
|
+
if (displayText !== null) {
|
|
16156
|
+
el.setAttribute('formatted', '');
|
|
16157
|
+
el.value = displayText;
|
|
16158
|
+
el.setAttribute('value', displayText);
|
|
16159
|
+
}
|
|
16160
|
+
else if (column.type === 'sparkline' && value !== null && typeof value === 'object') {
|
|
16048
16161
|
el.setAttribute('value', JSON.stringify(value));
|
|
16049
16162
|
if (Array.isArray(value))
|
|
16050
16163
|
el.data = value;
|
|
16051
16164
|
}
|
|
16052
16165
|
else {
|
|
16053
16166
|
el.value = value ?? '';
|
|
16054
|
-
|
|
16055
|
-
|
|
16056
|
-
|
|
16167
|
+
if (value === null || typeof value !== 'object') {
|
|
16168
|
+
el.setAttribute('value', String(value ?? ''));
|
|
16169
|
+
}
|
|
16057
16170
|
}
|
|
16058
16171
|
// Column definition as property (for formatter access etc.)
|
|
16059
|
-
el.column =
|
|
16172
|
+
el.column = cellColumn;
|
|
16060
16173
|
// Row-aware formatters, conditional formatting, and action-cell events all
|
|
16061
16174
|
// consume the originating row through the cell's property API.
|
|
16062
16175
|
el.rowData = row ?? null;
|
|
@@ -16346,7 +16459,12 @@ class TableColumnDnD {
|
|
|
16346
16459
|
this.sortLocalData();
|
|
16347
16460
|
}
|
|
16348
16461
|
}
|
|
16349
|
-
|
|
16462
|
+
/**
|
|
16463
|
+
* Reorder `this.data` to the current client-side sort (or back to delivery
|
|
16464
|
+
* order when the sort was cleared). Split out of sortLocalData so a fresh
|
|
16465
|
+
* delivery can land sorted without re-entering the render/index bookkeeping.
|
|
16466
|
+
*/
|
|
16467
|
+
applyLocalSortOrder() {
|
|
16350
16468
|
if (!this.unsortedData.length) {
|
|
16351
16469
|
this.unsortedData = [...this.data];
|
|
16352
16470
|
}
|
|
@@ -16361,16 +16479,19 @@ class TableColumnDnD {
|
|
|
16361
16479
|
for (const { column, direction } of this.currentSort) {
|
|
16362
16480
|
const colDef = this.columns.find(c => c.key === column);
|
|
16363
16481
|
const customComparator = colDef?.sortComparator;
|
|
16482
|
+
// The value pipeline feeds BOTH sort paths: valueGetter "runs for
|
|
16483
|
+
// display, sort, aggregation", so a custom comparator sees the same
|
|
16484
|
+
// derived value the cell shows (remote columns routinely key on the
|
|
16485
|
+
// server's sort field and bridge the display with a getter).
|
|
16486
|
+
const aVal = colDef?.valueGetter ? colDef.valueGetter(a[column], a) : a[column];
|
|
16487
|
+
const bVal = colDef?.valueGetter ? colDef.valueGetter(b[column], b) : b[column];
|
|
16364
16488
|
if (customComparator) {
|
|
16365
|
-
const cmp = customComparator(
|
|
16489
|
+
const cmp = customComparator(aVal, bVal, direction);
|
|
16366
16490
|
if (cmp !== 0)
|
|
16367
16491
|
return cmp;
|
|
16368
16492
|
}
|
|
16369
16493
|
else {
|
|
16370
|
-
|
|
16371
|
-
const aVal = colDef?.valueGetter ? colDef.valueGetter(a[column], a) : (a[column] ?? '');
|
|
16372
|
-
const bVal = colDef?.valueGetter ? colDef.valueGetter(b[column], b) : (b[column] ?? '');
|
|
16373
|
-
const cmp = String(aVal).localeCompare(String(bVal), undefined, { numeric: true });
|
|
16494
|
+
const cmp = String(aVal ?? '').localeCompare(String(bVal ?? ''), undefined, { numeric: true });
|
|
16374
16495
|
if (cmp !== 0)
|
|
16375
16496
|
return direction === 'asc' ? cmp : -cmp;
|
|
16376
16497
|
}
|
|
@@ -16379,6 +16500,9 @@ class TableColumnDnD {
|
|
|
16379
16500
|
});
|
|
16380
16501
|
}
|
|
16381
16502
|
this.settingSortedData = false;
|
|
16503
|
+
}
|
|
16504
|
+
sortLocalData() {
|
|
16505
|
+
this.applyLocalSortOrder();
|
|
16382
16506
|
// Sort replaced this.data (new order + reference) — resync the row-index
|
|
16383
16507
|
// map and drop the stale filtered snapshot before re-rendering. Render is
|
|
16384
16508
|
// queued (not synchronous): sortLocalData is reachable from the currentSort
|