snice 7.6.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.
Files changed (228) hide show
  1. package/adapters/react/SniceProvider.js +1 -1
  2. package/adapters/react/SniceRouter.js +1 -1
  3. package/adapters/react/calendar.d.ts +1 -0
  4. package/adapters/react/calendar.d.ts.map +1 -1
  5. package/adapters/react/calendar.js +1 -1
  6. package/adapters/react/calendar.js.map +1 -1
  7. package/adapters/react/calendar.tsx +2 -1
  8. package/adapters/react/matchRoute.js +1 -1
  9. package/adapters/react/useRequestHandler.js +1 -1
  10. package/bin/analyzer-contracts.js +8 -2
  11. package/custom-elements.json +15 -1
  12. package/dist/cdn/accordion/snice-accordion.js +1 -1
  13. package/dist/cdn/action-bar/snice-action-bar.js +1 -1
  14. package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
  15. package/dist/cdn/alert/snice-alert.js +1 -1
  16. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  17. package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
  18. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  19. package/dist/cdn/availability/snice-availability.js +1 -1
  20. package/dist/cdn/avatar/snice-avatar.js +1 -1
  21. package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
  22. package/dist/cdn/badge/snice-badge.js +1 -1
  23. package/dist/cdn/banner/snice-banner.js +1 -1
  24. package/dist/cdn/binpack/snice-binpack.js +1 -1
  25. package/dist/cdn/book/snice-book.js +1 -1
  26. package/dist/cdn/booking/snice-booking.js +1 -1
  27. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  28. package/dist/cdn/button/snice-button.js +2 -2
  29. package/dist/cdn/button/snice-button.js.map +1 -1
  30. package/dist/cdn/button/snice-button.min.js +1 -1
  31. package/dist/cdn/button/snice-button.min.js.map +1 -1
  32. package/dist/cdn/calendar/README.md +2 -2
  33. package/dist/cdn/calendar/snice-calendar.js +26 -2
  34. package/dist/cdn/calendar/snice-calendar.js.map +1 -1
  35. package/dist/cdn/calendar/snice-calendar.min.js +2 -2
  36. package/dist/cdn/calendar/snice-calendar.min.js.map +1 -1
  37. package/dist/cdn/camera/snice-camera.js +1 -1
  38. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  39. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  40. package/dist/cdn/card/snice-card.js +1 -1
  41. package/dist/cdn/carousel/snice-carousel.js +1 -1
  42. package/dist/cdn/cart/snice-cart.js +1 -1
  43. package/dist/cdn/chart/snice-chart.js +1 -1
  44. package/dist/cdn/chat/snice-chat.js +1 -1
  45. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  46. package/dist/cdn/chip/snice-chip.js +1 -1
  47. package/dist/cdn/code-block/snice-code-block.js +1 -1
  48. package/dist/cdn/color-display/snice-color-display.js +1 -1
  49. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  50. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  51. package/dist/cdn/comments/snice-comments.js +1 -1
  52. package/dist/cdn/countdown/snice-countdown.js +1 -1
  53. package/dist/cdn/cropper/snice-cropper.js +1 -1
  54. package/dist/cdn/data-card/snice-data-card.js +1 -1
  55. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  56. package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
  57. package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
  58. package/dist/cdn/diff/snice-diff.js +1 -1
  59. package/dist/cdn/divider/snice-divider.js +1 -1
  60. package/dist/cdn/doc/snice-doc.js +1 -1
  61. package/dist/cdn/draw/snice-draw.js +1 -1
  62. package/dist/cdn/drawer/snice-drawer.js +1 -1
  63. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  64. package/dist/cdn/estimate/snice-estimate.js +1 -1
  65. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  66. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  67. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  68. package/dist/cdn/flow/snice-flow.js +1 -1
  69. package/dist/cdn/form-layout/snice-form-layout.js +1 -1
  70. package/dist/cdn/funnel/snice-funnel.js +1 -1
  71. package/dist/cdn/gantt/snice-gantt.js +1 -1
  72. package/dist/cdn/gauge/snice-gauge.js +1 -1
  73. package/dist/cdn/grid/snice-grid.js +1 -1
  74. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  75. package/dist/cdn/image/snice-image.js +1 -1
  76. package/dist/cdn/input/snice-input.js +1 -1
  77. package/dist/cdn/invoice/snice-invoice.js +1 -1
  78. package/dist/cdn/kanban/snice-kanban.js +1 -1
  79. package/dist/cdn/key-value/snice-key-value.js +1 -1
  80. package/dist/cdn/kpi/snice-kpi.js +1 -1
  81. package/dist/cdn/layout/snice-layout.js +1 -1
  82. package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
  83. package/dist/cdn/link/snice-link.js +1 -1
  84. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  85. package/dist/cdn/list/snice-list.js +1 -1
  86. package/dist/cdn/location/snice-location.js +1 -1
  87. package/dist/cdn/login/README.md +1 -1
  88. package/dist/cdn/login/snice-login.js +2 -2
  89. package/dist/cdn/login/snice-login.js.map +1 -1
  90. package/dist/cdn/login/snice-login.min.js +1 -1
  91. package/dist/cdn/login/snice-login.min.js.map +1 -1
  92. package/dist/cdn/map/snice-map.js +1 -1
  93. package/dist/cdn/markdown/snice-markdown.js +1 -1
  94. package/dist/cdn/masonry/snice-masonry.js +1 -1
  95. package/dist/cdn/menu/snice-menu.js +1 -1
  96. package/dist/cdn/message-strip/snice-message-strip.js +1 -1
  97. package/dist/cdn/modal/snice-modal.js +1 -1
  98. package/dist/cdn/music-player/snice-music-player.js +1 -1
  99. package/dist/cdn/nav/snice-nav.js +1 -1
  100. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  101. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  102. package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
  103. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  104. package/dist/cdn/pagination/snice-pagination.js +1 -1
  105. package/dist/cdn/paint/snice-paint.js +1 -1
  106. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  107. package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
  108. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  109. package/dist/cdn/popover/snice-popover.js +1 -1
  110. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  111. package/dist/cdn/product-card/snice-product-card.js +1 -1
  112. package/dist/cdn/progress/snice-progress.js +1 -1
  113. package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
  114. package/dist/cdn/qr-code/snice-qr-code.js +1 -1
  115. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  116. package/dist/cdn/radio/snice-radio.js +1 -1
  117. package/dist/cdn/range-slider/snice-range-slider.js +1 -1
  118. package/dist/cdn/rating/snice-rating.js +1 -1
  119. package/dist/cdn/receipt/snice-receipt.js +1 -1
  120. package/dist/cdn/recipe/snice-recipe.js +1 -1
  121. package/dist/cdn/runtime/README.md +2 -2
  122. package/dist/cdn/runtime/snice-runtime.esm.js +130 -31
  123. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  124. package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
  125. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  126. package/dist/cdn/runtime/snice-runtime.js +130 -31
  127. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  128. package/dist/cdn/runtime/snice-runtime.min.js +1 -1
  129. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  130. package/dist/cdn/sankey/snice-sankey.js +1 -1
  131. package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
  132. package/dist/cdn/select/snice-select.js +1 -1
  133. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  134. package/dist/cdn/slider/snice-slider.js +1 -1
  135. package/dist/cdn/sortable/snice-sortable.js +1 -1
  136. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  137. package/dist/cdn/spinner/snice-spinner.js +1 -1
  138. package/dist/cdn/split-button/snice-split-button.js +1 -1
  139. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  140. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  141. package/dist/cdn/stat-group/snice-stat-group.js +1 -1
  142. package/dist/cdn/step-input/snice-step-input.js +1 -1
  143. package/dist/cdn/stepper/snice-stepper.js +1 -1
  144. package/dist/cdn/switch/snice-switch.js +1 -1
  145. package/dist/cdn/table/README.md +2 -2
  146. package/dist/cdn/table/snice-table.esm.js +227 -36
  147. package/dist/cdn/table/snice-table.esm.js.map +1 -1
  148. package/dist/cdn/table/snice-table.esm.min.js +69 -48
  149. package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
  150. package/dist/cdn/table/snice-table.js +226 -35
  151. package/dist/cdn/table/snice-table.js.map +1 -1
  152. package/dist/cdn/table/snice-table.min.js +47 -26
  153. package/dist/cdn/table/snice-table.min.js.map +1 -1
  154. package/dist/cdn/tabs/snice-tabs.js +1 -1
  155. package/dist/cdn/tag/snice-tag.js +1 -1
  156. package/dist/cdn/tag-input/README.md +2 -2
  157. package/dist/cdn/tag-input/snice-tag-input.js +10 -16
  158. package/dist/cdn/tag-input/snice-tag-input.js.map +1 -1
  159. package/dist/cdn/tag-input/snice-tag-input.min.js +2 -2
  160. package/dist/cdn/tag-input/snice-tag-input.min.js.map +1 -1
  161. package/dist/cdn/terminal/snice-terminal.js +1 -1
  162. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  163. package/dist/cdn/textarea/snice-textarea.js +1 -1
  164. package/dist/cdn/time-picker/snice-time-picker.js +1 -1
  165. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  166. package/dist/cdn/timeline/snice-timeline.js +1 -1
  167. package/dist/cdn/timer/snice-timer.js +1 -1
  168. package/dist/cdn/toast/snice-toast.js +1 -1
  169. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  170. package/dist/cdn/tree/snice-tree.js +1 -1
  171. package/dist/cdn/treemap/snice-treemap.js +1 -1
  172. package/dist/cdn/user-card/snice-user-card.js +1 -1
  173. package/dist/cdn/video-player/snice-video-player.js +1 -1
  174. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  175. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  176. package/dist/cdn/weather/snice-weather.js +1 -1
  177. package/dist/cdn/work-order/snice-work-order.js +1 -1
  178. package/dist/components/button/snice-button.js +1 -1
  179. package/dist/components/button/snice-button.js.map +1 -1
  180. package/dist/components/calendar/snice-calendar.d.ts +2 -0
  181. package/dist/components/calendar/snice-calendar.js +25 -1
  182. package/dist/components/calendar/snice-calendar.js.map +1 -1
  183. package/dist/components/table/snice-row.d.ts +6 -0
  184. package/dist/components/table/snice-row.js +11 -3
  185. package/dist/components/table/snice-row.js.map +1 -1
  186. package/dist/components/table/snice-table.d.ts +40 -3
  187. package/dist/components/table/snice-table.js +157 -17
  188. package/dist/components/table/snice-table.js.map +1 -1
  189. package/dist/components/table/table-cell-presentation.d.ts +11 -0
  190. package/dist/components/table/table-cell-presentation.js +25 -8
  191. package/dist/components/table/table-cell-presentation.js.map +1 -1
  192. package/dist/components/table/table-export.d.ts +6 -0
  193. package/dist/components/table/table-export.js +11 -2
  194. package/dist/components/table/table-export.js.map +1 -1
  195. package/dist/components/table/table-keyboard.js +1 -1
  196. package/dist/components/table/table-keyboard.js.map +1 -1
  197. package/dist/components/table/table-master-detail.d.ts +11 -2
  198. package/dist/components/table/table-master-detail.js +20 -3
  199. package/dist/components/table/table-master-detail.js.map +1 -1
  200. package/dist/components/tag-input/snice-tag-input.d.ts +2 -2
  201. package/dist/components/tag-input/snice-tag-input.js +9 -15
  202. package/dist/components/tag-input/snice-tag-input.js.map +1 -1
  203. package/dist/index.cjs +127 -28
  204. package/dist/index.cjs.map +1 -1
  205. package/dist/index.esm.js +127 -28
  206. package/dist/index.esm.js.map +1 -1
  207. package/dist/index.iife.js +127 -28
  208. package/dist/index.iife.js.map +1 -1
  209. package/dist/react/SniceProvider.js +1 -1
  210. package/dist/react/SniceRouter.js +1 -1
  211. package/dist/react/index.js +1 -1
  212. package/dist/react/matchRoute.js +1 -1
  213. package/dist/react/useRequestHandler.js +1 -1
  214. package/dist/request-response.d.ts +24 -1
  215. package/dist/symbols.cjs +1 -1
  216. package/dist/symbols.esm.js +1 -1
  217. package/dist/testing-dom.cjs +1 -1
  218. package/dist/testing-dom.esm.js +1 -1
  219. package/dist/transitions.cjs +1 -1
  220. package/dist/transitions.esm.js +1 -1
  221. package/docs/ai/DEVELOPMENT.md +1 -1
  222. package/docs/ai/components/calendar.md +8 -0
  223. package/docs/ai/components/tag-input.md +4 -0
  224. package/docs/ai/controllers.md +22 -6
  225. package/docs/components/calendar.md +24 -0
  226. package/docs/components/tag-input.md +6 -1
  227. package/docs/controllers.md +57 -6
  228. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  /*!
2
- * snice v7.5.1
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.
6
6
  */
7
- import { html, unsafeHTML, element, state, property, query, render, styles, ready, reconnect, watch, dispatch, dispose, css, queryAll, request, isSafeUrl, on, SimpleArray } from 'snice';
7
+ import { html, unsafeHTML, element, state, property, query, render, styles, ready, reconnect, watch, dispatch, dispose, css, queryAll, request, isSafeUrl, on, SimpleArray, observe } from 'snice';
8
8
 
9
9
  /******************************************************************************
10
10
  Copyright (c) Microsoft Corporation.
@@ -3051,7 +3051,7 @@ var cssContent$j = ":host([hidden]){display:none}:host{display:inline-block;font
3051
3051
  return _classThis;
3052
3052
  })();
3053
3053
 
3054
- var cssContent$i = ":host([hidden]){display:none}:host{display:inline-block;position:relative;width:auto;cursor:pointer}:host([disabled]){cursor:not-allowed}:host([justify-text=start]) .button{justify-content:flex-start}:host([justify-text=end]) .button{justify-content:flex-end}:host([justify-text=center]) .button{justify-content:center}.button{display:inline-flex;align-items:center;justify-content:center;width:100%;border:1px solid;font-family:var(--snice-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);font-weight:var(--snice-font-weight-medium,500);text-decoration:none;user-select:none;white-space:nowrap;vertical-align:middle;padding:0;box-shadow:var(--snice-shadow-xs,0 1px 2px 0 rgb(0 0 0 / .03));transition:color var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),background-color var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),border-color var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),box-shadow var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),transform 180ms var(--snice-ease-spring, cubic-bezier(.34,1.56,.64,1));cursor:inherit;position:relative}.button::before{content:'';position:absolute;inset:0;border-radius:inherit;background:var(--snice-gradient-convex,none);pointer-events:none}.button:active:not(.button--disabled){transition:color 50ms var(--snice-ease-out, ease),background-color 50ms var(--snice-ease-out, ease),border-color 50ms var(--snice-ease-out, ease),box-shadow 50ms var(--snice-ease-out, ease),transform 80ms var(--snice-ease-out, ease);transform:translateY(0) scale(.97)}.button:focus-visible{outline:0;box-shadow:0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.button--disabled{opacity:.5;cursor:not-allowed;box-shadow:none}.button--small{font-size:var(--snice-font-size-sm, .875rem);height:2rem;padding:0 var(--snice-spacing-sm,.75rem);border-radius:var(--snice-border-radius-md,.25rem)}.button--medium{font-size:var(--snice-font-size-md, 1rem);height:2.5rem;padding:0 var(--snice-spacing-md,1rem);border-radius:var(--snice-border-radius-md,.25rem)}.button--large{font-size:var(--snice-font-size-lg, 1.125rem);height:3rem;padding:0 var(--snice-spacing-lg,1.5rem);border-radius:var(--snice-border-radius-md,.25rem)}.button--default{background-color:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text,rgb(23 23 23));border-color:var(--snice-color-border,rgb(226 226 226))}.button--default:hover:not(.button--disabled){background-color:var(--snice-color-surface-container-low,rgb(250 250 250));border-color:var(--snice-color-border-hover,rgb(204 204 204));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--default:active:not(.button--disabled){background-color:var(--snice-color-surface-container,rgb(248 248 248));border-color:var(--snice-color-border-hover,rgb(204 204 204));box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--primary{background-color:var(--snice-color-primary,rgb(37 99 235));color:var(--snice-color-text-inverse,rgb(250 250 250));border-color:var(--snice-color-primary,rgb(37 99 235))}.button--primary:hover:not(.button--disabled){background-color:var(--snice-color-primary-hover,rgb(29 78 216));border-color:var(--snice-color-primary-hover,rgb(29 78 216));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--primary:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-primary,rgb(37 99 235)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-primary,rgb(37 99 235)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--success{background-color:var(--snice-color-success,rgb(22 163 74));color:var(--snice-color-text-inverse,rgb(250 250 250));border-color:var(--snice-color-success,rgb(22 163 74))}.button--success:hover:not(.button--disabled){background-color:var(--snice-color-success-hover,rgb(21 128 61));border-color:var(--snice-color-success-hover,rgb(21 128 61));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--success:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-success,rgb(22 163 74)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-success,rgb(22 163 74)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--warning{background-color:var(--snice-color-warning,rgb(202 138 4));color:var(--button-warning-text,rgb(69 26 3));border-color:var(--snice-color-warning,rgb(202 138 4))}.button--warning:hover:not(.button--disabled){background-color:var(--snice-color-warning-hover,rgb(161 98 7));border-color:var(--snice-color-warning-hover,rgb(161 98 7));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--warning:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-warning,rgb(202 138 4)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-warning,rgb(202 138 4)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--danger{background-color:var(--snice-color-danger,rgb(220 38 38));color:var(--snice-color-text-inverse,rgb(250 250 250));border-color:var(--snice-color-danger,rgb(220 38 38))}.button--danger:hover:not(.button--disabled){background-color:var(--snice-color-danger-hover,rgb(185 28 28));border-color:var(--snice-color-danger-hover,rgb(185 28 28));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--danger:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-danger,rgb(220 38 38)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-danger,rgb(220 38 38)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--text{background-color:transparent;color:var(--snice-color-primary,rgb(37 99 235));border-color:transparent;box-shadow:none}.button--text::before{display:none}.button--text:hover:not(.button--disabled){background-color:var(--snice-color-primary-alpha,rgb(37 99 235 / .1));color:var(--snice-color-primary-hover,rgb(29 78 216));box-shadow:none;transform:none}.button--text:active:not(.button--disabled){background-color:var(--snice-color-primary-alpha-strong,rgb(37 99 235 / .15));box-shadow:none;transform:none}.button--outline{box-shadow:none}.button--outline::before{display:none}.button--outline.button--default{background-color:transparent;color:var(--snice-color-text,rgb(23 23 23))}.button--outline.button--primary{background-color:transparent;color:var(--snice-color-primary,rgb(37 99 235))}.button--outline.button--success{background-color:transparent;color:var(--snice-color-success,rgb(22 163 74))}.button--outline.button--warning{background-color:transparent;color:var(--snice-color-warning,rgb(202 138 4))}.button--outline.button--danger{background-color:transparent;color:var(--snice-color-danger,rgb(220 38 38))}.button--outline.button--default:hover:not(.button--disabled){background-color:var(--snice-color-surface-hover,rgb(245 245 245));box-shadow:none;transform:none}.button--outline.button--primary:hover:not(.button--disabled){background-color:var(--snice-color-primary-subtle,rgb(239 246 255));color:var(--snice-color-primary-hover,rgb(29 78 216));box-shadow:none;transform:none}.button--outline.button--success:hover:not(.button--disabled){background-color:var(--snice-color-success-subtle,rgb(220 252 231));color:var(--snice-color-success-hover,rgb(21 128 61));box-shadow:none;transform:none}.button--outline.button--warning:hover:not(.button--disabled){background-color:var(--snice-color-warning-subtle,rgb(254 249 195));color:var(--snice-color-warning-hover,rgb(161 98 7));box-shadow:none;transform:none}.button--outline.button--danger:hover:not(.button--disabled){background-color:var(--snice-color-danger-subtle,rgb(254 226 226));color:var(--snice-color-danger-hover,rgb(185 28 28));box-shadow:none;transform:none}.button--outline:active:not(.button--disabled){background-color:var(--snice-color-primary-subtle-hover,var(--snice-color-surface-active,rgb(235 235 235)));box-shadow:none;transform:none}.button--pill{border-radius:var(--snice-border-radius-pill,9999px)}.button--circle{border-radius:var(--snice-border-radius-circle,50%);width:var(--size);padding:0}.button--small.button--circle{--size:2rem}.button--medium.button--circle{--size:2.5rem}.button--large.button--circle{--size:3rem}.button--loading{position:relative;cursor:wait}.button--loading .label{visibility:hidden}.spinner{display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:1em;height:1em}.button--loading .spinner{display:block}.spinner::after{content:'';display:block;width:100%;height:100%;box-sizing:border-box;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:.6s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.icon-slot{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.25em;height:1.25em;line-height:1}.icon-slot:empty{display:none}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.25em;height:1.25em;line-height:1}.icon img{width:100%;height:100%;object-fit:contain}.icon svg{width:100%;height:100%}.icon-slot ::slotted(*){display:flex;align-items:center;justify-content:center;font-size:1.25em;line-height:1}.icon-slot ::slotted(svg){width:1.25em;height:1.25em}.icon-slot ::slotted(img){width:1.25em;height:1.25em;object-fit:contain}.button--icon-start .icon,.button--icon-start .icon-slot{margin-right:.5em}.button--icon-end .icon,.button--icon-end .icon-slot{margin-left:.5em}.button--circle .icon,.button--circle .icon-slot{margin:0;transform:none;width:auto;height:auto}.button--circle .icon-slot ::slotted(*){font-size:1.25em;line-height:1;display:flex;align-items:center;justify-content:center}.button--circle .icon-slot ::slotted(svg){width:1.25em;height:1.25em}@media (prefers-reduced-motion:reduce){.icon-slot,.spinner,button,button:active{transition-duration:0s!important;animation-duration:0s!important}button:active{transform:none}}";
3054
+ var cssContent$i = ":host([hidden]){display:none}:host{display:inline-block;position:relative;width:auto;cursor:pointer}:host([disabled]){cursor:not-allowed}:host([justify-text=start]) .button{justify-content:flex-start}:host([justify-text=end]) .button{justify-content:flex-end}:host([justify-text=center]) .button{justify-content:center}.button{display:inline-flex;align-items:center;justify-content:center;width:100%;border:1px solid;font-family:var(--snice-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);font-weight:var(--snice-font-weight-medium,500);text-decoration:none;user-select:none;white-space:nowrap;vertical-align:middle;padding:0;box-shadow:var(--snice-shadow-xs,0 1px 2px 0 rgb(0 0 0 / .03));transition:color var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),background-color var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),border-color var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),box-shadow var(--snice-transition-fast, 150ms) var(--snice-ease-out, cubic-bezier(.22,1,.36,1)),transform 180ms var(--snice-ease-spring, cubic-bezier(.34,1.56,.64,1));cursor:inherit;position:relative}.button::before{content:'';position:absolute;inset:0;border-radius:inherit;background:var(--snice-gradient-convex,none);pointer-events:none}.button:active:not(.button--disabled){transition:color 50ms var(--snice-ease-out, ease),background-color 50ms var(--snice-ease-out, ease),border-color 50ms var(--snice-ease-out, ease),box-shadow 50ms var(--snice-ease-out, ease),transform 80ms var(--snice-ease-out, ease);transform:translateY(0) scale(.97)}.button:focus-visible{outline:0;box-shadow:0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.button--disabled{opacity:.5;cursor:not-allowed;box-shadow:none}.button--small{font-size:var(--snice-font-size-sm, .875rem);height:2rem;padding:0 var(--snice-spacing-sm,.75rem);border-radius:var(--snice-border-radius-md,.25rem)}.button--medium{font-size:var(--snice-font-size-md, 1rem);height:2.5rem;padding:0 var(--snice-spacing-md,1rem);border-radius:var(--snice-border-radius-md,.25rem)}.button--large{font-size:var(--snice-font-size-lg, 1.125rem);height:3rem;padding:0 var(--snice-spacing-lg,1.5rem);border-radius:var(--snice-border-radius-md,.25rem)}.button--default{background-color:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text,rgb(23 23 23));border-color:var(--snice-color-border,rgb(226 226 226))}.button--default:hover:not(.button--disabled){background-color:var(--snice-color-surface-container-low,rgb(250 250 250));border-color:var(--snice-color-border-hover,rgb(204 204 204));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--default:active:not(.button--disabled){background-color:var(--snice-color-surface-container,rgb(248 248 248));border-color:var(--snice-color-border-hover,rgb(204 204 204));box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--primary{background-color:var(--snice-color-primary,rgb(37 99 235));color:var(--snice-color-text-inverse,rgb(250 250 250));border-color:var(--snice-color-primary,rgb(37 99 235))}.button--primary:hover:not(.button--disabled){background-color:var(--snice-color-primary-hover,rgb(29 78 216));border-color:var(--snice-color-primary-hover,rgb(29 78 216));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--primary:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-primary,rgb(37 99 235)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-primary,rgb(37 99 235)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--success{background-color:var(--snice-color-success,rgb(22 163 74));color:var(--snice-color-text-inverse,rgb(250 250 250));border-color:var(--snice-color-success,rgb(22 163 74))}.button--success:hover:not(.button--disabled){background-color:var(--snice-color-success-hover,rgb(21 128 61));border-color:var(--snice-color-success-hover,rgb(21 128 61));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--success:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-success,rgb(22 163 74)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-success,rgb(22 163 74)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--warning{background-color:var(--snice-color-warning,rgb(202 138 4));color:var(--button-warning-text,rgb(69 26 3));border-color:var(--snice-color-warning,rgb(202 138 4))}.button--warning:hover:not(.button--disabled){background-color:var(--snice-color-warning-hover,rgb(161 98 7));border-color:var(--snice-color-warning-hover,rgb(161 98 7));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--warning:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-warning,rgb(202 138 4)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-warning,rgb(202 138 4)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--danger{background-color:var(--snice-color-danger,rgb(220 38 38));color:var(--snice-color-text-inverse,rgb(250 250 250));border-color:var(--snice-color-danger,rgb(220 38 38))}.button--danger:hover:not(.button--disabled){background-color:var(--snice-color-danger-hover,rgb(185 28 28));border-color:var(--snice-color-danger-hover,rgb(185 28 28));box-shadow:var(--snice-shadow-sm,0 1px 2px 0 rgb(0 0 0 / .05));transform:translateY(-1px)}.button--danger:active:not(.button--disabled){background-color:color-mix(in srgb,var(--snice-color-danger,rgb(220 38 38)) 85%,#fff);border-color:color-mix(in srgb,var(--snice-color-danger,rgb(220 38 38)) 85%,#fff);box-shadow:var(--snice-shadow-inset-sm,inset 0 1px 2px rgb(0 0 0 / .05));transform:translateY(0) scale(.97)}.button--text{background-color:transparent;color:var(--snice-color-primary,rgb(37 99 235));border-color:transparent;box-shadow:none}.button--text::before{display:none}.button--text:hover:not(.button--disabled){background-color:var(--snice-color-primary-alpha,rgb(37 99 235 / .1));color:var(--snice-color-primary-hover,rgb(29 78 216));box-shadow:none;transform:none}.button--text:active:not(.button--disabled){background-color:var(--snice-color-primary-alpha-strong,rgb(37 99 235 / .15));box-shadow:none;transform:none}.button--outline{box-shadow:none}.button--outline::before{display:none}.button--outline.button--default{background-color:transparent;color:var(--snice-color-text,rgb(23 23 23))}.button--outline.button--primary{background-color:transparent;color:var(--snice-color-primary,rgb(37 99 235))}.button--outline.button--success{background-color:transparent;color:var(--snice-color-success,rgb(22 163 74))}.button--outline.button--warning{background-color:transparent;color:var(--snice-color-warning,rgb(202 138 4))}.button--outline.button--danger{background-color:transparent;color:var(--snice-color-danger,rgb(220 38 38))}.button--outline.button--default:hover:not(.button--disabled){background-color:var(--snice-color-surface-hover,rgb(245 245 245));box-shadow:none;transform:none}.button--outline.button--primary:hover:not(.button--disabled){background-color:var(--snice-color-primary-subtle,rgb(239 246 255));color:var(--snice-color-primary-hover,rgb(29 78 216));box-shadow:none;transform:none}.button--outline.button--success:hover:not(.button--disabled){background-color:var(--snice-color-success-subtle,rgb(220 252 231));color:var(--snice-color-success-hover,rgb(21 128 61));box-shadow:none;transform:none}.button--outline.button--warning:hover:not(.button--disabled){background-color:var(--snice-color-warning-subtle,rgb(254 249 195));color:var(--snice-color-warning-hover,rgb(161 98 7));box-shadow:none;transform:none}.button--outline.button--danger:hover:not(.button--disabled){background-color:var(--snice-color-danger-subtle,rgb(254 226 226));color:var(--snice-color-danger-hover,rgb(185 28 28));box-shadow:none;transform:none}.button--outline:active:not(.button--disabled){background-color:var(--snice-color-primary-subtle-hover,var(--snice-color-surface-active,rgb(235 235 235)));box-shadow:none;transform:none}.button--pill{border-radius:var(--snice-border-radius-pill,9999px)}.button--circle{border-radius:var(--snice-border-radius-circle,50%);width:var(--size);padding:0}.button--small.button--circle{--size:2rem}.button--medium.button--circle{--size:2.5rem}.button--large.button--circle{--size:3rem}.button--loading{position:relative;cursor:wait}.button--loading .label{visibility:hidden}.spinner{display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:1em;height:1em}.button--loading .spinner{display:block}.spinner::after{content:'';display:block;width:100%;height:100%;box-sizing:border-box;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:.6s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.icon-slot{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.25em;height:1.25em;line-height:1}.icon-slot:empty{display:none}.icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.25em;height:1.25em;line-height:1}.icon img{width:100%;height:100%;object-fit:contain}.icon svg{width:100%;height:100%}.icon-slot ::slotted(*){display:flex;align-items:center;justify-content:center;font-size:1.25em;line-height:1}.icon-slot ::slotted(svg){width:1.25em;height:1.25em}.icon-slot ::slotted(img){width:1.25em;height:1.25em;object-fit:contain}.button--icon-start .icon,.button--icon-start .icon-slot{margin-right:.5em}.button--icon-end .icon,.button--icon-end .icon-slot{margin-left:.5em}.button--circle .icon,.button--circle .icon-slot{margin:0;transform:none;width:auto;height:auto}.button--circle .icon svg{width:1.25em;height:1.25em}.button--circle .icon-slot ::slotted(*){font-size:1.25em;line-height:1;display:flex;align-items:center;justify-content:center}.button--circle .icon-slot ::slotted(svg){width:1.25em;height:1.25em}@media (prefers-reduced-motion:reduce){.icon-slot,.spinner,button,button:active{transition-duration:0s!important;animation-duration:0s!important}button:active{transform:none}}";
3055
3055
 
3056
3056
  (() => {
3057
3057
  let _classDecorators = [element('snice-button', { formAssociated: true, delegatesFocus: true })];
@@ -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
- if (formatterOverride || column.valueFormatter) {
4322
- const formatter = formatterOverride
4323
- ? (column.formatter || column.valueFormatter)
4324
- : column.valueFormatter;
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 (formatter && content) {
4327
- const value = String(formatter(host.value, host.rowData) ?? '');
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.data[column.key];
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)}">
@@ -10935,7 +10960,7 @@ class TableKeyboard {
10935
10960
  });
10936
10961
  }
10937
10962
  // Body rows
10938
- const bodyRows = table.querySelectorAll('tbody tr:not(.virtual-spacer)');
10963
+ const bodyRows = table.querySelectorAll('tbody tr:not(.virtual-spacer):not(.table-fill-row)');
10939
10964
  bodyRows.forEach((row, i) => {
10940
10965
  row.setAttribute('role', 'row');
10941
10966
  row.setAttribute('aria-rowindex', String(this.ariaRowOffset + i + 2)); // +2 for 1-based + header
@@ -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[c.key];
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[c.key];
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
- /** Eagerly construct detail content when lazy=false. */
11398
- prepare(rows) {
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))
@@ -13191,6 +13242,7 @@ class TableColumnDnD {
13191
13242
  let _getTableData_decorators;
13192
13243
  let _styles_decorators;
13193
13244
  let _render_decorators;
13245
+ let _handleFrameResize_decorators;
13194
13246
  let _initialize_decorators;
13195
13247
  let _handleDeclarativeColumnChange_decorators;
13196
13248
  let _handleSelectorOptionsChange_decorators;
@@ -13288,6 +13340,7 @@ class TableColumnDnD {
13288
13340
  _getTableData_decorators = [request('table/data')];
13289
13341
  _styles_decorators = [styles()];
13290
13342
  _render_decorators = [render()];
13343
+ _handleFrameResize_decorators = [observe('resize', '.table-frame')];
13291
13344
  _initialize_decorators = [ready()];
13292
13345
  _handleDeclarativeColumnChange_decorators = [on('column-changed')];
13293
13346
  _handleSelectorOptionsChange_decorators = [watch('selectorOptions')];
@@ -13336,6 +13389,7 @@ class TableColumnDnD {
13336
13389
  __esDecorate(this, null, _getTableData_decorators, { kind: "method", name: "getTableData", static: false, private: false, access: { has: obj => "getTableData" in obj, get: obj => obj.getTableData }, metadata: _metadata }, null, _instanceExtraInitializers);
13337
13390
  __esDecorate(this, null, _styles_decorators, { kind: "method", name: "styles", static: false, private: false, access: { has: obj => "styles" in obj, get: obj => obj.styles }, metadata: _metadata }, null, _instanceExtraInitializers);
13338
13391
  __esDecorate(this, null, _render_decorators, { kind: "method", name: "render", static: false, private: false, access: { has: obj => "render" in obj, get: obj => obj.render }, metadata: _metadata }, null, _instanceExtraInitializers);
13392
+ __esDecorate(this, null, _handleFrameResize_decorators, { kind: "method", name: "handleFrameResize", static: false, private: false, access: { has: obj => "handleFrameResize" in obj, get: obj => obj.handleFrameResize }, metadata: _metadata }, null, _instanceExtraInitializers);
13339
13393
  __esDecorate(this, null, _initialize_decorators, { kind: "method", name: "initialize", static: false, private: false, access: { has: obj => "initialize" in obj, get: obj => obj.initialize }, metadata: _metadata }, null, _instanceExtraInitializers);
13340
13394
  __esDecorate(this, null, _handleDeclarativeColumnChange_decorators, { kind: "method", name: "handleDeclarativeColumnChange", static: false, private: false, access: { has: obj => "handleDeclarativeColumnChange" in obj, get: obj => obj.handleDeclarativeColumnChange }, metadata: _metadata }, null, _instanceExtraInitializers);
13341
13395
  __esDecorate(this, null, _handleSelectorOptionsChange_decorators, { kind: "method", name: "handleSelectorOptionsChange", static: false, private: false, access: { has: obj => "handleSelectorOptionsChange" in obj, get: obj => obj.handleSelectorOptionsChange }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -13654,6 +13708,27 @@ class TableColumnDnD {
13654
13708
  border-bottom: none;
13655
13709
  }
13656
13710
 
13711
+ /* Height fill: when the host is taller than its rows, an inert filler
13712
+ row (cloned from the last data row, so column widths and pinning
13713
+ track the table engine) keeps the column grid running to the bottom
13714
+ edge instead of the borders cutting off where the rows end. */
13715
+ tbody tr.table-fill-row {
13716
+ pointer-events: none;
13717
+ }
13718
+
13719
+ tbody tr.table-fill-row td {
13720
+ border-bottom: none;
13721
+ padding: 0;
13722
+ }
13723
+
13724
+ :host([striped]) tbody tr.table-fill-row {
13725
+ background: transparent;
13726
+ }
13727
+
13728
+ :host([hoverable]) tbody tr.table-fill-row:hover {
13729
+ background: transparent;
13730
+ }
13731
+
13657
13732
  th {
13658
13733
  padding: var(--snice-spacing-sm, 0.75rem) var(--snice-spacing-sm, 0.75rem);
13659
13734
  }
@@ -14677,6 +14752,11 @@ class TableColumnDnD {
14677
14752
  }
14678
14753
  container.appendChild(controlsDiv);
14679
14754
  }
14755
+ // Re-derive the fill row whenever the frame's box changes (host resized,
14756
+ // fullscreen toggled) — the leftover space below the rows changes with it.
14757
+ handleFrameResize() {
14758
+ this.updateFillRow();
14759
+ }
14680
14760
  async initialize() {
14681
14761
  // Listen for controller attached event
14682
14762
  this.addEventListener('controller-attached', this.onAttached);
@@ -14978,8 +15058,14 @@ class TableColumnDnD {
14978
15058
  if (this.settingSortedData)
14979
15059
  return;
14980
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();
14981
15067
  this.rebuildRowIndex();
14982
- this.masterDetail.prepare(this.data);
15068
+ this.masterDetail.prepare(this.data, row => this.rowValueSignature(row));
14983
15069
  if (this.settingDataImperative)
14984
15070
  return; // imperative setData(): caller renders
14985
15071
  this.scheduleRender('body');
@@ -15435,6 +15521,7 @@ class TableColumnDnD {
15435
15521
  // Still render pagination
15436
15522
  if (this.pagination)
15437
15523
  this.renderPagination();
15524
+ this.updateSelectAllState();
15438
15525
  return;
15439
15526
  }
15440
15527
  if (this.data.length === 0 && this.columns.length > 0) {
@@ -15651,10 +15738,52 @@ class TableColumnDnD {
15651
15738
  this.reconcileRows(entries);
15652
15739
  // Re-establish grid role + roving tabindex after the body changed.
15653
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();
15654
15745
  // Render pagination after body
15655
15746
  if (this.pagination) {
15656
15747
  this.renderPagination();
15657
15748
  }
15749
+ this.updateFillRow();
15750
+ }
15751
+ /**
15752
+ * When the host is taller than its rows, append an inert filler row so the
15753
+ * column grid runs to the bottom edge of the frame. Cloned from the last
15754
+ * data row, so column widths, pinning, and alignment track the table
15755
+ * engine natively. Removed entirely when content fills (or overflows) the
15756
+ * frame, restoring the plain last-row styling.
15757
+ */
15758
+ updateFillRow() {
15759
+ const frame = this.shadowRoot?.querySelector('.table-frame');
15760
+ const tableEl = frame?.querySelector('table');
15761
+ const tbody = tableEl?.querySelector('tbody');
15762
+ if (!frame || !tableEl || !tbody)
15763
+ return;
15764
+ tbody.querySelector('tr.table-fill-row')?.remove();
15765
+ if (this.list && this.listRenderer)
15766
+ return;
15767
+ const rows = tbody.querySelectorAll('tr[data-index]');
15768
+ const lastRow = rows[rows.length - 1];
15769
+ if (!lastRow)
15770
+ return;
15771
+ const leftover = Math.floor(frame.clientHeight - tableEl.offsetHeight);
15772
+ if (leftover <= 1)
15773
+ return;
15774
+ const fill = lastRow.cloneNode(true);
15775
+ fill.className = 'table-fill-row';
15776
+ for (const attr of ['data-index', 'data-selected', 'role', 'aria-rowindex', 'aria-selected', 'tabindex', 'id']) {
15777
+ fill.removeAttribute(attr);
15778
+ }
15779
+ fill.setAttribute('aria-hidden', 'true');
15780
+ fill.querySelectorAll('td').forEach(td => {
15781
+ td.replaceChildren();
15782
+ for (const attr of ['role', 'aria-colindex', 'tabindex', 'aria-selected'])
15783
+ td.removeAttribute(attr);
15784
+ });
15785
+ fill.style.height = `${leftover}px`;
15786
+ tbody.appendChild(fill);
15658
15787
  }
15659
15788
  // ── Task B: render-path reconciler ──────────────────────────────────────
15660
15789
  //
@@ -15745,15 +15874,41 @@ class TableColumnDnD {
15745
15874
  `tree=${this.treeData.isEnabled() ? this.treeData.getGroupColumn() : ''}`,
15746
15875
  ].join('|');
15747
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
+ }
15748
15903
  /**
15749
15904
  * The per-row rebuild signature. Selection and the plain data-index are
15750
15905
  * re-stamped on reuse (not here) so they never force a rebuild; this captures
15751
- * only what a re-stamp cannot repair: tree node state, per-row height, and —
15752
- * when a feature embeds the index in a row-scoped closure (row DnD drag index,
15753
- * 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.
15754
15909
  */
15755
15910
  rowSignature(rowData, index, structuralSig, treeRow) {
15756
- const parts = [structuralSig];
15911
+ const parts = [structuralSig, `v:${this.rowValueSignature(rowData)}`];
15757
15912
  if (treeRow)
15758
15913
  parts.push(`t:${treeRow.depth}:${treeRow.expanded ? 1 : 0}:${treeRow.hasChildren ? 1 : 0}`);
15759
15914
  if (this.rowHeightCallback)
@@ -15950,6 +16105,15 @@ class TableColumnDnD {
15950
16105
  const active = page === this.currentPage ? ' pagination__btn--active' : '';
15951
16106
  return `<button class="pagination__btn pagination__page${active}" data-page="${page}">${page}</button>`;
15952
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
+ }
15953
16117
  /** Create a cell element safely using createElement + setAttribute */
15954
16118
  createCellElement(column, value, row) {
15955
16119
  // E2: a custom renderCell bypasses the type-based cell entirely. A string
@@ -15974,22 +16138,38 @@ class TableColumnDnD {
15974
16138
  if (column.align)
15975
16139
  el.setAttribute('align', column.align);
15976
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 };
15977
16152
  // Set the property directly so arrays/objects reach progress, sparkline,
15978
16153
  // JSON, action, and tag cells without a lossy attribute conversion. Keep a
15979
16154
  // primitive attribute for declarative inspection and CSS selectors.
15980
- if (column.type === 'sparkline' && value !== null && typeof value === 'object') {
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') {
15981
16161
  el.setAttribute('value', JSON.stringify(value));
15982
16162
  if (Array.isArray(value))
15983
16163
  el.data = value;
15984
16164
  }
15985
16165
  else {
15986
16166
  el.value = value ?? '';
15987
- }
15988
- if (value === null || typeof value !== 'object') {
15989
- el.setAttribute('value', String(value ?? ''));
16167
+ if (value === null || typeof value !== 'object') {
16168
+ el.setAttribute('value', String(value ?? ''));
16169
+ }
15990
16170
  }
15991
16171
  // Column definition as property (for formatter access etc.)
15992
- el.column = column;
16172
+ el.column = cellColumn;
15993
16173
  // Row-aware formatters, conditional formatting, and action-cell events all
15994
16174
  // consume the originating row through the cell's property API.
15995
16175
  el.rowData = row ?? null;
@@ -16279,7 +16459,12 @@ class TableColumnDnD {
16279
16459
  this.sortLocalData();
16280
16460
  }
16281
16461
  }
16282
- sortLocalData() {
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() {
16283
16468
  if (!this.unsortedData.length) {
16284
16469
  this.unsortedData = [...this.data];
16285
16470
  }
@@ -16294,16 +16479,19 @@ class TableColumnDnD {
16294
16479
  for (const { column, direction } of this.currentSort) {
16295
16480
  const colDef = this.columns.find(c => c.key === column);
16296
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];
16297
16488
  if (customComparator) {
16298
- const cmp = customComparator(a[column], b[column], direction);
16489
+ const cmp = customComparator(aVal, bVal, direction);
16299
16490
  if (cmp !== 0)
16300
16491
  return cmp;
16301
16492
  }
16302
16493
  else {
16303
- // Use value pipeline getter if available
16304
- const aVal = colDef?.valueGetter ? colDef.valueGetter(a[column], a) : (a[column] ?? '');
16305
- const bVal = colDef?.valueGetter ? colDef.valueGetter(b[column], b) : (b[column] ?? '');
16306
- const cmp = String(aVal).localeCompare(String(bVal), undefined, { numeric: true });
16494
+ const cmp = String(aVal ?? '').localeCompare(String(bVal ?? ''), undefined, { numeric: true });
16307
16495
  if (cmp !== 0)
16308
16496
  return direction === 'asc' ? cmp : -cmp;
16309
16497
  }
@@ -16312,6 +16500,9 @@ class TableColumnDnD {
16312
16500
  });
16313
16501
  }
16314
16502
  this.settingSortedData = false;
16503
+ }
16504
+ sortLocalData() {
16505
+ this.applyLocalSortOrder();
16315
16506
  // Sort replaced this.data (new order + reference) — resync the row-index
16316
16507
  // map and drop the stale filtered snapshot before re-rendering. Render is
16317
16508
  // queued (not synchronous): sortLocalData is reachable from the currentSort