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
|
@@ -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.
|
|
@@ -4274,6 +4274,17 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
4274
4274
|
* Give every standalone specialized table cell the common ColumnDefinition
|
|
4275
4275
|
* presentation contract. The observer reapplies after Snice's differential
|
|
4276
4276
|
* renderer changes shadow content; equality guards prevent mutation loops.
|
|
4277
|
+
*
|
|
4278
|
+
* Display formatters
|
|
4279
|
+
* ------------------
|
|
4280
|
+
* `formatter` is the column's display formatter and `valueFormatter` its
|
|
4281
|
+
* fallback, so `formatter` wins. `formatterOverride` says the cell does NOT
|
|
4282
|
+
* apply `column.formatter` itself, so this module owns both formatters for it.
|
|
4283
|
+
*
|
|
4284
|
+
* A host marked `formatted` was handed its display text as `value` by the
|
|
4285
|
+
* table, which already resolved the pipeline. Its content is that text
|
|
4286
|
+
* verbatim — the marker exists so a rating/progress/link/tag cell, which
|
|
4287
|
+
* renders structure rather than text, still shows the formatted string.
|
|
4277
4288
|
*/
|
|
4278
4289
|
function installCellPresentation(host, formatterOverride = false) {
|
|
4279
4290
|
if (installed.has(host))
|
|
@@ -4320,15 +4331,21 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
4320
4331
|
? column.tooltip(host.value, host.rowData)
|
|
4321
4332
|
: (host.value == null ? '' : String(host.value));
|
|
4322
4333
|
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4334
|
+
// `formatter` beats `valueFormatter`. A cell that applies `formatter`
|
|
4335
|
+
// itself has already painted it, so the fallback must not overwrite that
|
|
4336
|
+
// content — only a formatter-less column falls through to valueFormatter.
|
|
4337
|
+
const formatter = column.formatter
|
|
4338
|
+
? (formatterOverride ? column.formatter : null)
|
|
4339
|
+
: column.valueFormatter;
|
|
4340
|
+
// Pre-formatted hosts read the attribute, not the property: a cell whose
|
|
4341
|
+
// value property is typed (boolean, number) coerces the display string.
|
|
4342
|
+
const display = host.hasAttribute('formatted')
|
|
4343
|
+
? (host.getAttribute('value') ?? '')
|
|
4344
|
+
: (formatter ? String(formatter(host.value, host.rowData) ?? '') : null);
|
|
4345
|
+
if (display !== null) {
|
|
4327
4346
|
const content = host.shadowRoot?.querySelector('[part~="content"]');
|
|
4328
|
-
if (
|
|
4329
|
-
|
|
4330
|
-
if (content.textContent !== value || content.childElementCount > 0)
|
|
4331
|
-
content.textContent = value;
|
|
4347
|
+
if (content && (content.textContent !== display || content.childElementCount > 0)) {
|
|
4348
|
+
content.textContent = display;
|
|
4332
4349
|
}
|
|
4333
4350
|
}
|
|
4334
4351
|
}
|
|
@@ -9421,6 +9438,15 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
9421
9438
|
this.updateRowAttributes();
|
|
9422
9439
|
this.configureCells();
|
|
9423
9440
|
}
|
|
9441
|
+
/**
|
|
9442
|
+
* The column's working value for this row: `valueGetter` runs for display
|
|
9443
|
+
* here exactly as it does on the table's own render path, so a column whose
|
|
9444
|
+
* key names a server field rather than a row field still paints.
|
|
9445
|
+
*/
|
|
9446
|
+
cellValue(column) {
|
|
9447
|
+
const raw = this.data?.[column.key];
|
|
9448
|
+
return column.valueGetter ? column.valueGetter(raw, this.data) : raw;
|
|
9449
|
+
}
|
|
9424
9450
|
configureCells() {
|
|
9425
9451
|
if (!this.columns) {
|
|
9426
9452
|
return;
|
|
@@ -9428,7 +9454,7 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
9428
9454
|
this.columns.forEach((column, index) => {
|
|
9429
9455
|
const cellElement = this.shadowRoot?.querySelector(`[data-column-index="${index}"]`);
|
|
9430
9456
|
if (cellElement) {
|
|
9431
|
-
const value = this.
|
|
9457
|
+
const value = this.cellValue(column);
|
|
9432
9458
|
if (typeof column.renderCell === 'function') {
|
|
9433
9459
|
const output = column.renderCell(value, this.data, column);
|
|
9434
9460
|
const replacement = output instanceof HTMLElement ? output : document.createElement('span');
|
|
@@ -9440,7 +9466,7 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
9440
9466
|
return;
|
|
9441
9467
|
}
|
|
9442
9468
|
const formatter = column.formatter || column.valueFormatter;
|
|
9443
|
-
cellElement.value = formatter ? formatter(value, this.data) : value;
|
|
9469
|
+
cellElement.value = formatter ? String(formatter(value, this.data) ?? '') : value;
|
|
9444
9470
|
cellElement.column = formatter
|
|
9445
9471
|
? { ...column, type: 'text', formatter: undefined, valueFormatter: undefined }
|
|
9446
9472
|
: column;
|
|
@@ -9530,7 +9556,6 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
9530
9556
|
`;
|
|
9531
9557
|
}
|
|
9532
9558
|
renderCell(column, columnIndex) {
|
|
9533
|
-
this.data[column.key];
|
|
9534
9559
|
const cellComponent = this.getCellComponent(column.formatter || column.valueFormatter ? 'text' : column.type);
|
|
9535
9560
|
return snice.html /*html*/ `
|
|
9536
9561
|
<div class="cell cell--${column.type}" part="cell" style="${this.getCellStyles(column)}">
|
|
@@ -11144,7 +11169,7 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
11144
11169
|
// Data rows
|
|
11145
11170
|
for (const row of data) {
|
|
11146
11171
|
const cells = exportColumns.map(c => {
|
|
11147
|
-
const value = row
|
|
11172
|
+
const value = this.cellValue(row, c);
|
|
11148
11173
|
return this.escapeCSV(value == null ? '' : String(value), delimiter);
|
|
11149
11174
|
});
|
|
11150
11175
|
rows.push(cells.join(delimiter));
|
|
@@ -11256,7 +11281,7 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
11256
11281
|
if (rows.length === 0)
|
|
11257
11282
|
return false;
|
|
11258
11283
|
const text = rows.map(row => columns.map(c => {
|
|
11259
|
-
const value = row
|
|
11284
|
+
const value = this.cellValue(row, c);
|
|
11260
11285
|
return useFormatted ? this.formatValue(value, row, c) : this.rawValue(value);
|
|
11261
11286
|
}).join(delimiter)).join('\n');
|
|
11262
11287
|
try {
|
|
@@ -11281,6 +11306,15 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
11281
11306
|
}
|
|
11282
11307
|
return value;
|
|
11283
11308
|
}
|
|
11309
|
+
/**
|
|
11310
|
+
* The column's working value for a row: `valueGetter` "runs for display,
|
|
11311
|
+
* sort, aggregation" — and for export, which must ship what the table shows,
|
|
11312
|
+
* not a row field the column may not even have.
|
|
11313
|
+
*/
|
|
11314
|
+
cellValue(row, column) {
|
|
11315
|
+
const raw = row?.[column.key];
|
|
11316
|
+
return column.valueGetter ? column.valueGetter(raw, row) : raw;
|
|
11317
|
+
}
|
|
11284
11318
|
rawValue(value) {
|
|
11285
11319
|
if (value == null)
|
|
11286
11320
|
return '';
|
|
@@ -11385,6 +11419,8 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
11385
11419
|
this.options = null;
|
|
11386
11420
|
this.tableElement = null;
|
|
11387
11421
|
this.contentCache = new Map();
|
|
11422
|
+
/** Last value signature seen per row, so in-place mutations evict the cache. */
|
|
11423
|
+
this.fingerprints = new Map();
|
|
11388
11424
|
this.rowByIndex = new Map();
|
|
11389
11425
|
this.measuredHeights = new Map();
|
|
11390
11426
|
this.onHeightChange = null;
|
|
@@ -11393,18 +11429,33 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
11393
11429
|
this.tableElement = tableEl;
|
|
11394
11430
|
this.options = options;
|
|
11395
11431
|
this.contentCache.clear();
|
|
11432
|
+
this.fingerprints.clear();
|
|
11396
11433
|
this.rowByIndex.clear();
|
|
11397
11434
|
this.measuredHeights.clear();
|
|
11398
11435
|
}
|
|
11399
|
-
/**
|
|
11400
|
-
|
|
11436
|
+
/**
|
|
11437
|
+
* Eagerly construct detail content when lazy=false.
|
|
11438
|
+
*
|
|
11439
|
+
* `fingerprint` (the table's per-row value signature) also evicts rows whose
|
|
11440
|
+
* fields were mutated in place: the cache is keyed by row identity, so an
|
|
11441
|
+
* unchanged identity would otherwise keep serving a panel built from the
|
|
11442
|
+
* pre-mutation values.
|
|
11443
|
+
*/
|
|
11444
|
+
prepare(rows, fingerprint) {
|
|
11401
11445
|
if (!this.options)
|
|
11402
11446
|
return;
|
|
11403
11447
|
this.rowByIndex = new Map(rows.map((row, index) => [index, row]));
|
|
11404
11448
|
const currentRows = new Set(rows);
|
|
11405
|
-
for (const row of this.contentCache.keys()) {
|
|
11449
|
+
for (const row of [...this.contentCache.keys()]) {
|
|
11406
11450
|
if (!currentRows.has(row))
|
|
11407
11451
|
this.contentCache.delete(row);
|
|
11452
|
+
else if (fingerprint && this.fingerprints.get(row) !== fingerprint(row)) {
|
|
11453
|
+
this.contentCache.delete(row);
|
|
11454
|
+
this.measuredHeights.delete(row);
|
|
11455
|
+
}
|
|
11456
|
+
}
|
|
11457
|
+
if (fingerprint) {
|
|
11458
|
+
this.fingerprints = new Map(rows.map(row => [row, fingerprint(row)]));
|
|
11408
11459
|
}
|
|
11409
11460
|
for (const row of this.measuredHeights.keys()) {
|
|
11410
11461
|
if (!currentRows.has(row))
|
|
@@ -15009,8 +15060,14 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
15009
15060
|
if (this.settingSortedData)
|
|
15010
15061
|
return;
|
|
15011
15062
|
this.unsortedData = [...this.data];
|
|
15063
|
+
// C3: rows delivered while a client-side sort is active must land in that
|
|
15064
|
+
// sorted order — the currentSort watcher only fires when the sort itself
|
|
15065
|
+
// changes, so without this the body silently contradicts the header. In
|
|
15066
|
+
// remote mode the server owns the ordering.
|
|
15067
|
+
if (this.mode !== 'remote' && this.currentSort.length > 0)
|
|
15068
|
+
this.applyLocalSortOrder();
|
|
15012
15069
|
this.rebuildRowIndex();
|
|
15013
|
-
this.masterDetail.prepare(this.data);
|
|
15070
|
+
this.masterDetail.prepare(this.data, row => this.rowValueSignature(row));
|
|
15014
15071
|
if (this.settingDataImperative)
|
|
15015
15072
|
return; // imperative setData(): caller renders
|
|
15016
15073
|
this.scheduleRender('body');
|
|
@@ -15466,6 +15523,7 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
15466
15523
|
// Still render pagination
|
|
15467
15524
|
if (this.pagination)
|
|
15468
15525
|
this.renderPagination();
|
|
15526
|
+
this.updateSelectAllState();
|
|
15469
15527
|
return;
|
|
15470
15528
|
}
|
|
15471
15529
|
if (this.data.length === 0 && this.columns.length > 0) {
|
|
@@ -15682,6 +15740,10 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
15682
15740
|
this.reconcileRows(entries);
|
|
15683
15741
|
// Re-establish grid role + roving tabindex after the body changed.
|
|
15684
15742
|
this.keyboard.refresh();
|
|
15743
|
+
// The select-all control describes the *filtered* row set, so it goes stale
|
|
15744
|
+
// whenever the denominator moves (rows delivered, filter cleared) even
|
|
15745
|
+
// though `selectedRows` — its only other trigger — never changed.
|
|
15746
|
+
this.updateSelectAllState();
|
|
15685
15747
|
// Render pagination after body
|
|
15686
15748
|
if (this.pagination) {
|
|
15687
15749
|
this.renderPagination();
|
|
@@ -15814,15 +15876,41 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
15814
15876
|
`tree=${this.treeData.isEnabled() ? this.treeData.getGroupColumn() : ''}`,
|
|
15815
15877
|
].join('|');
|
|
15816
15878
|
}
|
|
15879
|
+
/**
|
|
15880
|
+
* A fingerprint of a row's own field values, so a row object that was mutated
|
|
15881
|
+
* IN PLACE (the same identity re-delivered by a poll/websocket patch, or an
|
|
15882
|
+
* array the app edits) is rebuilt instead of recycled with stale cells.
|
|
15883
|
+
*
|
|
15884
|
+
* Tradeoff, deliberately chosen: this walks the row's OWN, SHALLOW fields
|
|
15885
|
+
* rather than running each column's valueGetter/formatter. It therefore costs
|
|
15886
|
+
* one string build per rendered row — never a user callback, so call counts
|
|
15887
|
+
* for `valueGetter` stay exactly one per row per render — and it still catches
|
|
15888
|
+
* getter-derived columns, because a getter reads the fields this covers.
|
|
15889
|
+
* Nested objects collapse to their String() form, so a mutation buried inside
|
|
15890
|
+
* a nested object (or an object rendered by a JSON/progress cell) is not
|
|
15891
|
+
* detected; those callers should hand over a new row object. Recycling keeps
|
|
15892
|
+
* its payoff: rows whose values did not change are still reused untouched.
|
|
15893
|
+
*/
|
|
15894
|
+
rowValueSignature(rowData) {
|
|
15895
|
+
if (rowData === null || typeof rowData !== 'object')
|
|
15896
|
+
return String(rowData);
|
|
15897
|
+
const parts = [];
|
|
15898
|
+
for (const key of Object.keys(rowData)) {
|
|
15899
|
+
const value = rowData[key];
|
|
15900
|
+
parts.push(`${key}=${value === null || value === undefined ? '' : String(value)}`);
|
|
15901
|
+
}
|
|
15902
|
+
// A separator keeps {a:'1b'} from colliding with {a:'1',b:''}.
|
|
15903
|
+
return parts.join('|');
|
|
15904
|
+
}
|
|
15817
15905
|
/**
|
|
15818
15906
|
* The per-row rebuild signature. Selection and the plain data-index are
|
|
15819
15907
|
* re-stamped on reuse (not here) so they never force a rebuild; this captures
|
|
15820
|
-
*
|
|
15821
|
-
* when a feature embeds the index in a row-scoped
|
|
15822
|
-
* master-detail toggle index) — the index itself.
|
|
15908
|
+
* the row's cell values plus what a re-stamp cannot repair: tree node state,
|
|
15909
|
+
* per-row height, and — when a feature embeds the index in a row-scoped
|
|
15910
|
+
* closure (row DnD drag index, master-detail toggle index) — the index itself.
|
|
15823
15911
|
*/
|
|
15824
15912
|
rowSignature(rowData, index, structuralSig, treeRow) {
|
|
15825
|
-
const parts = [structuralSig];
|
|
15913
|
+
const parts = [structuralSig, `v:${this.rowValueSignature(rowData)}`];
|
|
15826
15914
|
if (treeRow)
|
|
15827
15915
|
parts.push(`t:${treeRow.depth}:${treeRow.expanded ? 1 : 0}:${treeRow.hasChildren ? 1 : 0}`);
|
|
15828
15916
|
if (this.rowHeightCallback)
|
|
@@ -16019,6 +16107,15 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
16019
16107
|
const active = page === this.currentPage ? ' pagination__btn--active' : '';
|
|
16020
16108
|
return `<button class="pagination__btn pagination__page${active}" data-page="${page}">${page}</button>`;
|
|
16021
16109
|
}
|
|
16110
|
+
/**
|
|
16111
|
+
* The column's display text, or null when the column declares no display
|
|
16112
|
+
* formatter. `formatter` (row-aware) is the display formatter and
|
|
16113
|
+
* `valueFormatter` its documented fallback, so `formatter` wins.
|
|
16114
|
+
*/
|
|
16115
|
+
cellDisplayText(column, value, row) {
|
|
16116
|
+
const formatter = column.formatter || column.valueFormatter;
|
|
16117
|
+
return formatter ? String(formatter(value, row) ?? '') : null;
|
|
16118
|
+
}
|
|
16022
16119
|
/** Create a cell element safely using createElement + setAttribute */
|
|
16023
16120
|
createCellElement(column, value, row) {
|
|
16024
16121
|
// E2: a custom renderCell bypasses the type-based cell entirely. A string
|
|
@@ -16043,22 +16140,38 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
16043
16140
|
if (column.align)
|
|
16044
16141
|
el.setAttribute('align', column.align);
|
|
16045
16142
|
el.setAttribute('in-table', 'true');
|
|
16143
|
+
// A declared display formatter makes the cell's value its DISPLAY text:
|
|
16144
|
+
// the pipeline (valueGetter -> formatter, valueFormatter as the fallback)
|
|
16145
|
+
// is resolved here, once, so the `value` channel a caller reads is what the
|
|
16146
|
+
// cell actually shows. The cell is marked `formatted` and handed a column
|
|
16147
|
+
// without the formatters, which keeps it from formatting again and tells
|
|
16148
|
+
// the shared presentation contract to paint the text over any type-specific
|
|
16149
|
+
// rendering (a rating/progress/link cell cannot render display text).
|
|
16150
|
+
const displayText = this.cellDisplayText(column, value, row);
|
|
16151
|
+
const cellColumn = displayText === null
|
|
16152
|
+
? column
|
|
16153
|
+
: { ...column, formatter: undefined, valueFormatter: undefined };
|
|
16046
16154
|
// Set the property directly so arrays/objects reach progress, sparkline,
|
|
16047
16155
|
// JSON, action, and tag cells without a lossy attribute conversion. Keep a
|
|
16048
16156
|
// primitive attribute for declarative inspection and CSS selectors.
|
|
16049
|
-
if (
|
|
16157
|
+
if (displayText !== null) {
|
|
16158
|
+
el.setAttribute('formatted', '');
|
|
16159
|
+
el.value = displayText;
|
|
16160
|
+
el.setAttribute('value', displayText);
|
|
16161
|
+
}
|
|
16162
|
+
else if (column.type === 'sparkline' && value !== null && typeof value === 'object') {
|
|
16050
16163
|
el.setAttribute('value', JSON.stringify(value));
|
|
16051
16164
|
if (Array.isArray(value))
|
|
16052
16165
|
el.data = value;
|
|
16053
16166
|
}
|
|
16054
16167
|
else {
|
|
16055
16168
|
el.value = value ?? '';
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16169
|
+
if (value === null || typeof value !== 'object') {
|
|
16170
|
+
el.setAttribute('value', String(value ?? ''));
|
|
16171
|
+
}
|
|
16059
16172
|
}
|
|
16060
16173
|
// Column definition as property (for formatter access etc.)
|
|
16061
|
-
el.column =
|
|
16174
|
+
el.column = cellColumn;
|
|
16062
16175
|
// Row-aware formatters, conditional formatting, and action-cell events all
|
|
16063
16176
|
// consume the originating row through the cell's property API.
|
|
16064
16177
|
el.rowData = row ?? null;
|
|
@@ -16348,7 +16461,12 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
16348
16461
|
this.sortLocalData();
|
|
16349
16462
|
}
|
|
16350
16463
|
}
|
|
16351
|
-
|
|
16464
|
+
/**
|
|
16465
|
+
* Reorder `this.data` to the current client-side sort (or back to delivery
|
|
16466
|
+
* order when the sort was cleared). Split out of sortLocalData so a fresh
|
|
16467
|
+
* delivery can land sorted without re-entering the render/index bookkeeping.
|
|
16468
|
+
*/
|
|
16469
|
+
applyLocalSortOrder() {
|
|
16352
16470
|
if (!this.unsortedData.length) {
|
|
16353
16471
|
this.unsortedData = [...this.data];
|
|
16354
16472
|
}
|
|
@@ -16363,16 +16481,19 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
16363
16481
|
for (const { column, direction } of this.currentSort) {
|
|
16364
16482
|
const colDef = this.columns.find(c => c.key === column);
|
|
16365
16483
|
const customComparator = colDef?.sortComparator;
|
|
16484
|
+
// The value pipeline feeds BOTH sort paths: valueGetter "runs for
|
|
16485
|
+
// display, sort, aggregation", so a custom comparator sees the same
|
|
16486
|
+
// derived value the cell shows (remote columns routinely key on the
|
|
16487
|
+
// server's sort field and bridge the display with a getter).
|
|
16488
|
+
const aVal = colDef?.valueGetter ? colDef.valueGetter(a[column], a) : a[column];
|
|
16489
|
+
const bVal = colDef?.valueGetter ? colDef.valueGetter(b[column], b) : b[column];
|
|
16366
16490
|
if (customComparator) {
|
|
16367
|
-
const cmp = customComparator(
|
|
16491
|
+
const cmp = customComparator(aVal, bVal, direction);
|
|
16368
16492
|
if (cmp !== 0)
|
|
16369
16493
|
return cmp;
|
|
16370
16494
|
}
|
|
16371
16495
|
else {
|
|
16372
|
-
|
|
16373
|
-
const aVal = colDef?.valueGetter ? colDef.valueGetter(a[column], a) : (a[column] ?? '');
|
|
16374
|
-
const bVal = colDef?.valueGetter ? colDef.valueGetter(b[column], b) : (b[column] ?? '');
|
|
16375
|
-
const cmp = String(aVal).localeCompare(String(bVal), undefined, { numeric: true });
|
|
16496
|
+
const cmp = String(aVal ?? '').localeCompare(String(bVal ?? ''), undefined, { numeric: true });
|
|
16376
16497
|
if (cmp !== 0)
|
|
16377
16498
|
return direction === 'asc' ? cmp : -cmp;
|
|
16378
16499
|
}
|
|
@@ -16381,6 +16502,9 @@ if(typeof globalThis.Snice==="undefined"){console.warn("[snice] snice-runtime.mi
|
|
|
16381
16502
|
});
|
|
16382
16503
|
}
|
|
16383
16504
|
this.settingSortedData = false;
|
|
16505
|
+
}
|
|
16506
|
+
sortLocalData() {
|
|
16507
|
+
this.applyLocalSortOrder();
|
|
16384
16508
|
// Sort replaced this.data (new order + reference) — resync the row-index
|
|
16385
16509
|
// map and drop the stale filtered snapshot before re-rendering. Render is
|
|
16386
16510
|
// queued (not synchronous): sortLocalData is reachable from the currentSort
|