snice 7.8.0 → 7.9.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 (302) hide show
  1. package/README.md +1 -1
  2. package/adapters/react/SniceProvider.js +1 -1
  3. package/adapters/react/SniceRouter.js +1 -1
  4. package/adapters/react/matchRoute.js +1 -1
  5. package/adapters/react/table.d.ts +1 -0
  6. package/adapters/react/table.d.ts.map +1 -1
  7. package/adapters/react/table.js +1 -1
  8. package/adapters/react/table.js.map +1 -1
  9. package/adapters/react/table.tsx +2 -1
  10. package/adapters/react/useRequestHandler.js +1 -1
  11. package/bin/analyzer-contracts.js +17 -1
  12. package/custom-elements.json +57 -3
  13. package/dist/cdn/accordion/snice-accordion.js +1 -1
  14. package/dist/cdn/action-bar/snice-action-bar.js +1 -1
  15. package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
  16. package/dist/cdn/alert/snice-alert.js +1 -1
  17. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  18. package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
  19. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  20. package/dist/cdn/availability/snice-availability.js +1 -1
  21. package/dist/cdn/avatar/snice-avatar.js +1 -1
  22. package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
  23. package/dist/cdn/badge/snice-badge.js +1 -1
  24. package/dist/cdn/banner/snice-banner.js +1 -1
  25. package/dist/cdn/binpack/snice-binpack.js +1 -1
  26. package/dist/cdn/book/snice-book.js +1 -1
  27. package/dist/cdn/booking/snice-booking.js +1 -1
  28. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  29. package/dist/cdn/button/snice-button.js +1 -1
  30. package/dist/cdn/calendar/README.md +2 -2
  31. package/dist/cdn/calendar/snice-calendar.js +537 -44
  32. package/dist/cdn/calendar/snice-calendar.js.map +1 -1
  33. package/dist/cdn/calendar/snice-calendar.min.js +2 -2
  34. package/dist/cdn/calendar/snice-calendar.min.js.map +1 -1
  35. package/dist/cdn/camera/snice-camera.js +1 -1
  36. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  37. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  38. package/dist/cdn/card/snice-card.js +1 -1
  39. package/dist/cdn/carousel/snice-carousel.js +1 -1
  40. package/dist/cdn/cart/snice-cart.js +1 -1
  41. package/dist/cdn/chart/snice-chart.js +1 -1
  42. package/dist/cdn/chat/snice-chat.js +1 -1
  43. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  44. package/dist/cdn/chip/snice-chip.js +1 -1
  45. package/dist/cdn/code-block/README.md +2 -2
  46. package/dist/cdn/code-block/snice-code-block.js +177 -167
  47. package/dist/cdn/code-block/snice-code-block.js.map +1 -1
  48. package/dist/cdn/code-block/snice-code-block.min.js +2 -2
  49. package/dist/cdn/code-block/snice-code-block.min.js.map +1 -1
  50. package/dist/cdn/color-display/snice-color-display.js +1 -1
  51. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  52. package/dist/cdn/command-palette/README.md +1 -1
  53. package/dist/cdn/command-palette/snice-command-palette.js +2 -2
  54. package/dist/cdn/command-palette/snice-command-palette.js.map +1 -1
  55. package/dist/cdn/command-palette/snice-command-palette.min.js +1 -1
  56. package/dist/cdn/command-palette/snice-command-palette.min.js.map +1 -1
  57. package/dist/cdn/comments/snice-comments.js +1 -1
  58. package/dist/cdn/countdown/snice-countdown.js +1 -1
  59. package/dist/cdn/cropper/snice-cropper.js +1 -1
  60. package/dist/cdn/data-card/snice-data-card.js +1 -1
  61. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  62. package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
  63. package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
  64. package/dist/cdn/diff/snice-diff.js +1 -1
  65. package/dist/cdn/divider/snice-divider.js +1 -1
  66. package/dist/cdn/doc/snice-doc.js +1 -1
  67. package/dist/cdn/draw/snice-draw.js +1 -1
  68. package/dist/cdn/drawer/snice-drawer.js +1 -1
  69. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  70. package/dist/cdn/estimate/snice-estimate.js +1 -1
  71. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  72. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  73. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  74. package/dist/cdn/flow/snice-flow.js +1 -1
  75. package/dist/cdn/form-layout/snice-form-layout.js +1 -1
  76. package/dist/cdn/funnel/snice-funnel.js +1 -1
  77. package/dist/cdn/gantt/snice-gantt.js +1 -1
  78. package/dist/cdn/gauge/snice-gauge.js +1 -1
  79. package/dist/cdn/grid/snice-grid.js +1 -1
  80. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  81. package/dist/cdn/image/snice-image.js +1 -1
  82. package/dist/cdn/input/snice-input.js +1 -1
  83. package/dist/cdn/invoice/snice-invoice.js +1 -1
  84. package/dist/cdn/kanban/snice-kanban.js +1 -1
  85. package/dist/cdn/key-value/snice-key-value.js +1 -1
  86. package/dist/cdn/kpi/snice-kpi.js +1 -1
  87. package/dist/cdn/layout/snice-layout.js +1 -1
  88. package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
  89. package/dist/cdn/link/snice-link.js +1 -1
  90. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  91. package/dist/cdn/list/snice-list.js +1 -1
  92. package/dist/cdn/location/snice-location.js +1 -1
  93. package/dist/cdn/login/snice-login.js +1 -1
  94. package/dist/cdn/map/snice-map.js +1 -1
  95. package/dist/cdn/markdown/snice-markdown.js +1 -1
  96. package/dist/cdn/masonry/snice-masonry.js +1 -1
  97. package/dist/cdn/menu/snice-menu.js +1 -1
  98. package/dist/cdn/message-strip/snice-message-strip.js +1 -1
  99. package/dist/cdn/modal/snice-modal.js +1 -1
  100. package/dist/cdn/music-player/snice-music-player.js +1 -1
  101. package/dist/cdn/nav/snice-nav.js +1 -1
  102. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  103. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  104. package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
  105. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  106. package/dist/cdn/pagination/snice-pagination.js +1 -1
  107. package/dist/cdn/paint/snice-paint.js +1 -1
  108. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  109. package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
  110. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  111. package/dist/cdn/popover/snice-popover.js +1 -1
  112. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  113. package/dist/cdn/product-card/snice-product-card.js +1 -1
  114. package/dist/cdn/progress/snice-progress.js +2 -2
  115. package/dist/cdn/progress/snice-progress.js.map +1 -1
  116. package/dist/cdn/progress/snice-progress.min.js +1 -1
  117. package/dist/cdn/progress/snice-progress.min.js.map +1 -1
  118. package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
  119. package/dist/cdn/qr-code/README.md +2 -2
  120. package/dist/cdn/qr-code/snice-qr-code.js +19 -3
  121. package/dist/cdn/qr-code/snice-qr-code.js.map +1 -1
  122. package/dist/cdn/qr-code/snice-qr-code.min.js +2 -2
  123. package/dist/cdn/qr-code/snice-qr-code.min.js.map +1 -1
  124. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  125. package/dist/cdn/radio/snice-radio.js +1 -1
  126. package/dist/cdn/range-slider/snice-range-slider.js +1 -1
  127. package/dist/cdn/rating/snice-rating.js +1 -1
  128. package/dist/cdn/receipt/snice-receipt.js +5 -2
  129. package/dist/cdn/receipt/snice-receipt.js.map +1 -1
  130. package/dist/cdn/receipt/snice-receipt.min.js +1 -1
  131. package/dist/cdn/receipt/snice-receipt.min.js.map +1 -1
  132. package/dist/cdn/recipe/snice-recipe.js +1 -1
  133. package/dist/cdn/runtime/snice-runtime.esm.js +4 -4
  134. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  135. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  136. package/dist/cdn/runtime/snice-runtime.js +4 -4
  137. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  138. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  139. package/dist/cdn/sankey/snice-sankey.js +1 -1
  140. package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
  141. package/dist/cdn/select/README.md +2 -2
  142. package/dist/cdn/select/snice-select.js +6 -9
  143. package/dist/cdn/select/snice-select.js.map +1 -1
  144. package/dist/cdn/select/snice-select.min.js +2 -2
  145. package/dist/cdn/select/snice-select.min.js.map +1 -1
  146. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  147. package/dist/cdn/slider/snice-slider.js +1 -1
  148. package/dist/cdn/sortable/snice-sortable.js +1 -1
  149. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  150. package/dist/cdn/spinner/snice-spinner.js +1 -1
  151. package/dist/cdn/split-button/snice-split-button.js +1 -1
  152. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  153. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  154. package/dist/cdn/stat-group/snice-stat-group.js +1 -1
  155. package/dist/cdn/step-input/snice-step-input.js +1 -1
  156. package/dist/cdn/stepper/snice-stepper.js +1 -1
  157. package/dist/cdn/switch/snice-switch.js +1 -1
  158. package/dist/cdn/table/README.md +2 -2
  159. package/dist/cdn/table/snice-table.esm.js +1940 -407
  160. package/dist/cdn/table/snice-table.esm.js.map +1 -1
  161. package/dist/cdn/table/snice-table.esm.min.js +242 -53
  162. package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
  163. package/dist/cdn/table/snice-table.js +1940 -407
  164. package/dist/cdn/table/snice-table.js.map +1 -1
  165. package/dist/cdn/table/snice-table.min.js +234 -45
  166. package/dist/cdn/table/snice-table.min.js.map +1 -1
  167. package/dist/cdn/tabs/snice-tabs.js +1 -1
  168. package/dist/cdn/tag/snice-tag.js +1 -1
  169. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  170. package/dist/cdn/terminal/snice-terminal.js +1 -1
  171. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  172. package/dist/cdn/textarea/snice-textarea.js +1 -1
  173. package/dist/cdn/time-picker/snice-time-picker.js +1 -1
  174. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  175. package/dist/cdn/timeline/snice-timeline.js +1 -1
  176. package/dist/cdn/timer/snice-timer.js +1 -1
  177. package/dist/cdn/toast/snice-toast.js +1 -1
  178. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  179. package/dist/cdn/tree/snice-tree.js +1 -1
  180. package/dist/cdn/treemap/snice-treemap.js +1 -1
  181. package/dist/cdn/user-card/snice-user-card.js +1 -1
  182. package/dist/cdn/video-player/snice-video-player.js +1 -1
  183. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  184. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  185. package/dist/cdn/weather/snice-weather.js +1 -1
  186. package/dist/cdn/work-order/snice-work-order.js +1 -1
  187. package/dist/components/calendar/snice-calendar.d.ts +187 -5
  188. package/dist/components/calendar/snice-calendar.js +537 -44
  189. package/dist/components/calendar/snice-calendar.js.map +1 -1
  190. package/dist/components/calendar/snice-calendar.types.d.ts +2 -1
  191. package/dist/components/code-block/formatter.d.ts +6 -0
  192. package/dist/components/code-block/formatter.js +174 -164
  193. package/dist/components/code-block/formatter.js.map +1 -1
  194. package/dist/components/code-block/grammars/snice.json +1 -1
  195. package/dist/components/code-block/grammars/typescript.json +1 -1
  196. package/dist/components/command-palette/snice-command-palette.js +1 -1
  197. package/dist/components/command-palette/snice-command-palette.js.map +1 -1
  198. package/dist/components/progress/snice-progress.js +1 -1
  199. package/dist/components/progress/snice-progress.js.map +1 -1
  200. package/dist/components/qr-code/qrcode.d.ts +1 -1
  201. package/dist/components/qr-code/qrcode.js +18 -2
  202. package/dist/components/qr-code/qrcode.js.map +1 -1
  203. package/dist/components/receipt/snice-receipt.js +4 -1
  204. package/dist/components/receipt/snice-receipt.js.map +1 -1
  205. package/dist/components/select/snice-select.d.ts +1 -1
  206. package/dist/components/select/snice-select.js +5 -8
  207. package/dist/components/select/snice-select.js.map +1 -1
  208. package/dist/components/table/snice-cell-actions.js +1 -1
  209. package/dist/components/table/snice-cell-actions.js.map +1 -1
  210. package/dist/components/table/snice-cell-color.js +1 -1
  211. package/dist/components/table/snice-cell-color.js.map +1 -1
  212. package/dist/components/table/snice-cell-date.js +20 -8
  213. package/dist/components/table/snice-cell-date.js.map +1 -1
  214. package/dist/components/table/snice-cell-email.js +1 -1
  215. package/dist/components/table/snice-cell-email.js.map +1 -1
  216. package/dist/components/table/snice-cell-image.js +1 -1
  217. package/dist/components/table/snice-cell-image.js.map +1 -1
  218. package/dist/components/table/snice-cell-json.js +1 -1
  219. package/dist/components/table/snice-cell-json.js.map +1 -1
  220. package/dist/components/table/snice-cell-link.js +1 -1
  221. package/dist/components/table/snice-cell-link.js.map +1 -1
  222. package/dist/components/table/snice-cell-location.js +1 -1
  223. package/dist/components/table/snice-cell-location.js.map +1 -1
  224. package/dist/components/table/snice-cell-phone.js +1 -1
  225. package/dist/components/table/snice-cell-phone.js.map +1 -1
  226. package/dist/components/table/snice-cell-status.js +1 -1
  227. package/dist/components/table/snice-cell-status.js.map +1 -1
  228. package/dist/components/table/snice-cell.d.ts +10 -0
  229. package/dist/components/table/snice-cell.js +59 -13
  230. package/dist/components/table/snice-cell.js.map +1 -1
  231. package/dist/components/table/snice-row.d.ts +0 -1
  232. package/dist/components/table/snice-row.js +4 -10
  233. package/dist/components/table/snice-row.js.map +1 -1
  234. package/dist/components/table/snice-table.d.ts +170 -2
  235. package/dist/components/table/snice-table.js +868 -189
  236. package/dist/components/table/snice-table.js.map +1 -1
  237. package/dist/components/table/snice-table.types.d.ts +17 -0
  238. package/dist/components/table/table-cell-presentation.d.ts +17 -4
  239. package/dist/components/table/table-cell-presentation.js +37 -11
  240. package/dist/components/table/table-cell-presentation.js.map +1 -1
  241. package/dist/components/table/table-column-manager.d.ts +153 -2
  242. package/dist/components/table/table-column-manager.js +364 -43
  243. package/dist/components/table/table-column-manager.js.map +1 -1
  244. package/dist/components/table/table-date.d.ts +29 -0
  245. package/dist/components/table/table-date.js +78 -0
  246. package/dist/components/table/table-date.js.map +1 -0
  247. package/dist/components/table/table-export.js +6 -2
  248. package/dist/components/table/table-export.js.map +1 -1
  249. package/dist/components/table/table-filter-engine.d.ts +9 -0
  250. package/dist/components/table/table-filter-engine.js +58 -14
  251. package/dist/components/table/table-filter-engine.js.map +1 -1
  252. package/dist/components/table/table-keyboard.d.ts +8 -0
  253. package/dist/components/table/table-keyboard.js +14 -3
  254. package/dist/components/table/table-keyboard.js.map +1 -1
  255. package/dist/components/table/table-toolbar.js +1 -0
  256. package/dist/components/table/table-toolbar.js.map +1 -1
  257. package/dist/components/table/table-tree-data.d.ts +2 -0
  258. package/dist/components/table/table-tree-data.js +35 -9
  259. package/dist/components/table/table-tree-data.js.map +1 -1
  260. package/dist/components/table/table-virtualizer.d.ts +44 -7
  261. package/dist/components/table/table-virtualizer.js +101 -54
  262. package/dist/components/table/table-virtualizer.js.map +1 -1
  263. package/dist/components/theme/theme.css +9 -0
  264. package/dist/index.cjs +1 -1
  265. package/dist/index.esm.js +1 -1
  266. package/dist/index.iife.js +1 -1
  267. package/dist/react/SniceProvider.js +1 -1
  268. package/dist/react/SniceRouter.js +1 -1
  269. package/dist/react/index.js +1 -1
  270. package/dist/react/matchRoute.js +1 -1
  271. package/dist/react/useRequestHandler.js +1 -1
  272. package/dist/symbols.cjs +1 -1
  273. package/dist/symbols.esm.js +1 -1
  274. package/dist/testing-dom.cjs +1 -1
  275. package/dist/testing-dom.esm.js +1 -1
  276. package/dist/transitions.cjs +1 -1
  277. package/dist/transitions.esm.js +1 -1
  278. package/docs/ai/DEVELOPMENT.md +93 -2
  279. package/docs/ai/components/calendar.md +113 -10
  280. package/docs/ai/components/code-block.md +4 -0
  281. package/docs/ai/components/doc.md +1 -1
  282. package/docs/ai/components/progress.md +1 -0
  283. package/docs/ai/components/receipt.md +1 -1
  284. package/docs/ai/components/table.md +29 -30
  285. package/docs/ai/components/theme.md +1 -0
  286. package/docs/ai/events.md +30 -0
  287. package/docs/ai/theme.md +1 -0
  288. package/docs/components/calendar.md +171 -11
  289. package/docs/components/code-block.md +2 -0
  290. package/docs/components/doc.md +1 -1
  291. package/docs/components/progress.md +13 -0
  292. package/docs/components/receipt.md +1 -0
  293. package/docs/components/table.md +274 -15
  294. package/docs/components/theme.md +5 -0
  295. package/docs/events.md +49 -0
  296. package/docs/plans/2026-03-06-date-range-picker-plan.md +4 -4
  297. package/docs/plans/2026-03-07-table-mui-parity-design.md +1 -1
  298. package/docs/plans/2026-03-09-action-bar-plan.md +11 -11
  299. package/docs/superpowers/specs/2026-07-09-table-incredible-design.md +1 -1
  300. package/docs/superpowers/specs/2026-07-09-table-phase0-implementation.md +2 -2
  301. package/package.json +3 -1
  302. package/vscode.html-custom-data.json +13 -1
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Date parsing for the table's cell layer.
3
+ *
4
+ * A column of type `date` usually carries calendar dates: `'2026-03-15'` names
5
+ * a day, not an instant. `new Date('2026-03-15')` nevertheless resolves the
6
+ * ISO date-only forms as UTC midnight (ECMA-262 21.4.3.2), so every consumer
7
+ * in a negative-offset zone formatted the day BEFORE the authored one, while
8
+ * an `<input type="date">` editor holding the same string showed the authored
9
+ * day. Display, export, and filtering must all agree with the string the
10
+ * application wrote.
11
+ *
12
+ * The rule: a value that carries NO time is a calendar day and is read at
13
+ * local midnight; anything that carries a time (or an offset, or is already a
14
+ * `Date`/epoch number) is an instant and is left to the platform.
15
+ */
16
+ /** ISO calendar dates with no time part: `YYYY`, `YYYY-MM`, `YYYY-MM-DD`. */
17
+ const DATE_ONLY = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/;
18
+ /**
19
+ * Parse a cell value into a `Date`, or `null` when there is nothing to show.
20
+ *
21
+ * `null` covers the empty cases (`null`, `undefined`, `''`) and unparseable
22
+ * input alike, so callers fall back to the raw value rather than rendering
23
+ * "Invalid Date" or the epoch.
24
+ */
25
+ function parseCellDate(value) {
26
+ if (value === null || value === undefined || value === '')
27
+ return null;
28
+ if (value instanceof Date) {
29
+ return Number.isNaN(value.getTime()) ? null : value;
30
+ }
31
+ if (typeof value === 'number') {
32
+ const fromEpoch = new Date(value);
33
+ return Number.isNaN(fromEpoch.getTime()) ? null : fromEpoch;
34
+ }
35
+ if (typeof value === 'string') {
36
+ const trimmed = value.trim();
37
+ if (trimmed === '')
38
+ return null;
39
+ const calendarDay = DATE_ONLY.exec(trimmed);
40
+ if (calendarDay) {
41
+ const year = Number(calendarDay[1]);
42
+ const month = calendarDay[2] === undefined ? 1 : Number(calendarDay[2]);
43
+ const day = calendarDay[3] === undefined ? 1 : Number(calendarDay[3]);
44
+ // Local midnight: the same calendar day in every timezone.
45
+ const local = new Date(year, month - 1, day);
46
+ // Years 0-99 mean themselves, not 1900+n.
47
+ if (year >= 0 && year <= 99)
48
+ local.setFullYear(year, month - 1, day);
49
+ if (Number.isNaN(local.getTime()))
50
+ return null;
51
+ // `new Date(2026, 12, 1)` rolls over to 2027 instead of rejecting, so a
52
+ // typo like '2026-13-01' or '2026-02-30' would render as a confident but
53
+ // WRONG day. The platform calls those strings invalid; so do we, and the
54
+ // caller falls back to showing the raw value.
55
+ if (local.getFullYear() !== year ||
56
+ local.getMonth() !== month - 1 ||
57
+ local.getDate() !== day) {
58
+ return null;
59
+ }
60
+ return local;
61
+ }
62
+ const parsed = new Date(trimmed);
63
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
64
+ }
65
+ const coerced = new Date(value);
66
+ return Number.isNaN(coerced.getTime()) ? null : coerced;
67
+ }
68
+ /**
69
+ * Midnight on the calendar day a value falls on, in local time. Used by
70
+ * day-granularity comparisons (today/past/future classification) so an
71
+ * instant is never bumped across a day boundary by the UTC offset.
72
+ */
73
+ function startOfLocalDay(date) {
74
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
75
+ }
76
+
77
+ export { parseCellDate, startOfLocalDay };
78
+ //# sourceMappingURL=table-date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-date.js","sources":["../../../../components/table/table-date.ts"],"sourcesContent":["/**\n * Date parsing for the table's cell layer.\n *\n * A column of type `date` usually carries calendar dates: `'2026-03-15'` names\n * a day, not an instant. `new Date('2026-03-15')` nevertheless resolves the\n * ISO date-only forms as UTC midnight (ECMA-262 21.4.3.2), so every consumer\n * in a negative-offset zone formatted the day BEFORE the authored one, while\n * an `<input type=\"date\">` editor holding the same string showed the authored\n * day. Display, export, and filtering must all agree with the string the\n * application wrote.\n *\n * The rule: a value that carries NO time is a calendar day and is read at\n * local midnight; anything that carries a time (or an offset, or is already a\n * `Date`/epoch number) is an instant and is left to the platform.\n */\n\n/** ISO calendar dates with no time part: `YYYY`, `YYYY-MM`, `YYYY-MM-DD`. */\nconst DATE_ONLY = /^(\\d{4})(?:-(\\d{2}))?(?:-(\\d{2}))?$/;\n\n/**\n * Parse a cell value into a `Date`, or `null` when there is nothing to show.\n *\n * `null` covers the empty cases (`null`, `undefined`, `''`) and unparseable\n * input alike, so callers fall back to the raw value rather than rendering\n * \"Invalid Date\" or the epoch.\n */\nexport function parseCellDate(value: unknown): Date | null {\n if (value === null || value === undefined || value === '') return null;\n\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value;\n }\n\n if (typeof value === 'number') {\n const fromEpoch = new Date(value);\n return Number.isNaN(fromEpoch.getTime()) ? null : fromEpoch;\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed === '') return null;\n\n const calendarDay = DATE_ONLY.exec(trimmed);\n if (calendarDay) {\n const year = Number(calendarDay[1]);\n const month = calendarDay[2] === undefined ? 1 : Number(calendarDay[2]);\n const day = calendarDay[3] === undefined ? 1 : Number(calendarDay[3]);\n // Local midnight: the same calendar day in every timezone.\n const local = new Date(year, month - 1, day);\n // Years 0-99 mean themselves, not 1900+n.\n if (year >= 0 && year <= 99) local.setFullYear(year, month - 1, day);\n if (Number.isNaN(local.getTime())) return null;\n // `new Date(2026, 12, 1)` rolls over to 2027 instead of rejecting, so a\n // typo like '2026-13-01' or '2026-02-30' would render as a confident but\n // WRONG day. The platform calls those strings invalid; so do we, and the\n // caller falls back to showing the raw value.\n if (\n local.getFullYear() !== year ||\n local.getMonth() !== month - 1 ||\n local.getDate() !== day\n ) {\n return null;\n }\n return local;\n }\n\n const parsed = new Date(trimmed);\n return Number.isNaN(parsed.getTime()) ? null : parsed;\n }\n\n const coerced = new Date(value as any);\n return Number.isNaN(coerced.getTime()) ? null : coerced;\n}\n\n/**\n * Midnight on the calendar day a value falls on, in local time. Used by\n * day-granularity comparisons (today/past/future classification) so an\n * instant is never bumped across a day boundary by the UTC offset.\n */\nexport function startOfLocalDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;AAcG;AAEH;AACA,MAAM,SAAS,GAAG,qCAAqC;AAEvD;;;;;;AAMG;AACG,SAAU,aAAa,CAAC,KAAc,EAAA;IAC1C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,IAAI;AAEtE,IAAA,IAAI,KAAK,YAAY,IAAI,EAAE;AACzB,QAAA,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,KAAK;IACrD;AAEA,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC7B,QAAA,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC;AACjC,QAAA,OAAO,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS;IAC7D;AAEA,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC7B,QAAA,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE;QAC5B,IAAI,OAAO,KAAK,EAAE;AAAE,YAAA,OAAO,IAAI;QAE/B,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QAC3C,IAAI,WAAW,EAAE;YACf,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACvE,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;;AAErE,YAAA,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC;;AAE5C,YAAA,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;gBAAE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC;YACpE,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;AAAE,gBAAA,OAAO,IAAI;;;;;AAK9C,YAAA,IACE,KAAK,CAAC,WAAW,EAAE,KAAK,IAAI;AAC5B,gBAAA,KAAK,CAAC,QAAQ,EAAE,KAAK,KAAK,GAAG,CAAC;AAC9B,gBAAA,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,EACvB;AACA,gBAAA,OAAO,IAAI;YACb;AACA,YAAA,OAAO,KAAK;QACd;AAEA,QAAA,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC;AAChC,QAAA,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,MAAM;IACvD;AAEA,IAAA,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,KAAY,CAAC;AACtC,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,OAAO;AACzD;AAEA;;;;AAIG;AACG,SAAU,eAAe,CAAC,IAAU,EAAA;AACxC,IAAA,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;AACtE;;;;"}
@@ -1,3 +1,5 @@
1
+ import { parseCellDate } from './table-date.js';
2
+
1
3
  class TableExport {
2
4
  /**
3
5
  * Export data to CSV and trigger download.
@@ -229,8 +231,10 @@ class TableExport {
229
231
  return `${num.toFixed(column.percentageFormat?.decimals ?? 2)}%`;
230
232
  }
231
233
  case 'date': {
232
- const date = new Date(value);
233
- if (Number.isNaN(date.getTime()))
234
+ // Exported dates must read as the cell reads them: a calendar day is
235
+ // local midnight, never UTC midnight (see table-date.ts).
236
+ const date = parseCellDate(value);
237
+ if (!date)
234
238
  return this.rawValue(value);
235
239
  const format = column.dateFormat || {};
236
240
  const dateStyle = format.format === 'custom' ? undefined : (format.format || 'short');
@@ -1 +1 @@
1
- {"version":3,"file":"table-export.js","sources":["../../../../components/table/table-export.ts"],"sourcesContent":["/**\n * Export and clipboard functionality for snice-table.\n * Handles: CSV export, print, clipboard copy.\n */\nimport type { ColumnDefinition } from './snice-table.types';\n\nexport interface CSVExportOptions {\n /** CSV delimiter. Default: ',' */\n delimiter?: string;\n /** Filename for download. Default: 'export.csv' */\n filename?: string;\n /** Include column headers. Default: true */\n includeHeaders?: boolean;\n /** Export only selected rows. Default: false */\n selectedOnly?: boolean;\n /** Specific column keys to export. Default: all visible */\n columns?: string[];\n /** Add UTF-8 BOM for Excel compatibility. Default: true */\n utf8BOM?: boolean;\n}\n\nexport interface PrintOptions {\n /** Hide footer in print. Default: false */\n hideFooter?: boolean;\n /** Hide toolbar in print. Default: false */\n hideToolbar?: boolean;\n /** Include selection checkboxes. Default: false */\n includeCheckboxes?: boolean;\n /** Custom page CSS. */\n pageStyles?: string;\n}\n\nexport interface ClipboardOptions {\n /** Delimiter for copied cells. Default: '\\t' */\n delimiter?: string;\n /** Use formatted values or raw. Default: true (formatted) */\n useFormatted?: boolean;\n}\n\nexport class TableExport {\n /**\n * Export data to CSV and trigger download.\n */\n exportCSV(\n data: any[],\n columns: { key: string; label: string; exportable?: boolean }[],\n options: CSVExportOptions = {}\n ) {\n const {\n delimiter = ',',\n filename = 'export.csv',\n includeHeaders = true,\n columns: columnKeys,\n utf8BOM = true,\n } = options;\n\n // Filter columns\n let exportColumns = columns.filter(c => (c as any).exportable !== false);\n if (columnKeys) {\n exportColumns = exportColumns.filter(c => columnKeys.includes(c.key));\n }\n\n const rows: string[] = [];\n\n // Header row\n if (includeHeaders) {\n rows.push(exportColumns.map(c => this.escapeCSV(c.label, delimiter)).join(delimiter));\n }\n\n // Data rows\n for (const row of data) {\n const cells = exportColumns.map(c => {\n const value = this.cellValue(row, c);\n return this.escapeCSV(value == null ? '' : String(value), delimiter);\n });\n rows.push(cells.join(delimiter));\n }\n\n const csv = rows.join('\\n');\n const bom = utf8BOM ? '\\ufeff' : '';\n const blob = new Blob([bom + csv], { type: 'text/csv;charset=utf-8;' });\n\n // Download\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.style.display = 'none';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }\n\n /**\n * Open browser print dialog with table content.\n */\n print(\n tableElement: HTMLElement,\n options: PrintOptions = {}\n ) {\n const {\n hideFooter = false,\n hideToolbar = false,\n includeCheckboxes = false,\n pageStyles = '',\n } = options;\n\n // Clone the table content\n const shadowRoot = tableElement.shadowRoot;\n if (!shadowRoot) return;\n\n const table = shadowRoot.querySelector('table');\n if (!table) return;\n\n const tableClone = table.cloneNode(true) as HTMLTableElement;\n\n // Shadow-root cell contents are not included by outerHTML. Flatten each\n // rendered custom cell into plain print text so the printout contains what\n // the user actually sees rather than empty custom-element tags.\n const originalCells = Array.from(table.querySelectorAll('snice-cell, [in-table], snice-checkbox'));\n const clonedCells = Array.from(tableClone.querySelectorAll('snice-cell, [in-table], snice-checkbox'));\n originalCells.forEach((original: any, index) => {\n const clone = clonedCells[index] as HTMLElement | undefined;\n if (!clone) return;\n if (original.localName === 'snice-checkbox') {\n clone.replaceWith(document.createTextNode(original.checked ? '☑' : '☐'));\n return;\n }\n const content = original.shadowRoot?.querySelector('[part~=\"content\"]');\n clone.replaceWith(document.createTextNode((content?.textContent || original.textContent || '').trim()));\n });\n\n if (!includeCheckboxes) {\n tableClone.querySelectorAll('.select-column').forEach((cell) => cell.remove());\n }\n\n const plainSection = (selector: string, className: string): string => {\n const source = shadowRoot.querySelector(selector);\n if (!source) return '';\n const clone = source.cloneNode(true) as HTMLElement;\n clone.querySelectorAll('style').forEach((style) => style.remove());\n clone.querySelectorAll('button').forEach((button) => {\n if (!button.textContent?.trim()) button.textContent = button.getAttribute('aria-label') || '';\n });\n const text = clone.textContent?.replace(/\\s+/g, ' ').trim();\n return text ? `<div class=\"${className}\">${this.escapeHTML(text)}</div>` : '';\n };\n const toolbarHTML = hideToolbar ? '' : plainSection('.table-controls-container', 'print-toolbar');\n const footerHTML = hideFooter ? '' : plainSection('.table-pagination-container', 'print-footer');\n\n const printWindow = window.open('', '_blank');\n if (!printWindow) return;\n\n const html = `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Print</title>\n <style>\n body { font-family: system-ui, -apple-system, sans-serif; margin: 1rem; }\n table { width: 100%; border-collapse: collapse; }\n th, td { padding: 8px 12px; border: 1px solid #ddd; text-align: left; }\n th { background: #f5f5f5; font-weight: 600; }\n .print-toolbar, .print-footer { margin: 0 0 0.75rem; color: #444; }\n .print-footer { margin: 0.75rem 0 0; }\n @media print {\n body { margin: 0; }\n table { page-break-inside: auto; }\n tr { page-break-inside: avoid; }\n }\n ${pageStyles}\n </style>\n </head>\n <body>\n ${toolbarHTML}\n ${tableClone.outerHTML}\n ${footerHTML}\n </body>\n </html>\n `;\n\n printWindow.document.write(html);\n printWindow.document.close();\n printWindow.onload = () => {\n printWindow.print();\n printWindow.close();\n };\n }\n\n /**\n * Copy cells to clipboard as tab-separated text.\n */\n async copyToClipboard(\n data: any[],\n columns: ColumnDefinition[],\n options: ClipboardOptions = {}\n ): Promise<boolean> {\n const { delimiter = '\\t', useFormatted = true } = options;\n const rows = data;\n\n if (rows.length === 0) return false;\n\n const text = rows.map(row =>\n columns.map(c => {\n const value = this.cellValue(row, c);\n return useFormatted ? this.formatValue(value, row, c) : this.rawValue(value);\n }).join(delimiter)\n ).join('\\n');\n\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch {\n // Fallback for older browsers\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.cssText = 'position:fixed;left:-9999px;';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n }\n\n private escapeCSV(value: string, delimiter: string): string {\n if (value.includes(delimiter) || value.includes('\"') || value.includes('\\n') || value.includes('\\r')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n }\n\n /**\n * The column's working value for a row: `valueGetter` \"runs for display,\n * sort, aggregation\" — and for export, which must ship what the table shows,\n * not a row field the column may not even have.\n */\n private cellValue(row: any, column: ColumnDefinition): any {\n const raw = row?.[column.key];\n return column.valueGetter ? column.valueGetter(raw, row) : raw;\n }\n\n private rawValue(value: any): string {\n if (value == null) return '';\n if (typeof value === 'object') {\n try { return JSON.stringify(value); } catch { return String(value); }\n }\n return String(value);\n }\n\n private formatValue(value: any, row: any, column: ColumnDefinition): string {\n const override = column.formatter || column.valueFormatter;\n if (override) return String(override(value, row) ?? '');\n if (value == null) return '';\n\n const num = Number(value);\n switch (column.type) {\n case 'number':\n case 'accounting': {\n if (!Number.isFinite(num)) return this.rawValue(value);\n const format = column.numberFormat || {};\n const decimals = format.decimals ?? (column.type === 'accounting' ? 2 : 0);\n const grouped = new Intl.NumberFormat('en-US', {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n useGrouping: format.thousandsSeparator ?? false,\n }).format(Math.abs(num));\n let output = num < 0 && (format.negativeStyle === 'parentheses' || column.type === 'accounting')\n ? `(${grouped})`\n : `${num < 0 ? '-' : ''}${grouped}`;\n if (format.prefix) output = format.prefix + output;\n if (format.suffix) output += format.suffix;\n return output;\n }\n case 'currency': {\n if (!Number.isFinite(num)) return this.rawValue(value);\n const format = column.currencyFormat || {};\n const decimals = format.decimals ?? 2;\n const display = format.currencyDisplay || format.display || 'symbol';\n const output = new Intl.NumberFormat(format.locale || 'en-US', {\n style: 'currency',\n currency: format.currency || 'USD',\n currencyDisplay: display,\n useGrouping: format.thousandsSeparator ?? true,\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(Math.abs(num));\n if (num >= 0) return output;\n return format.negativeStyle === 'parentheses' ? `(${output})` : `-${output}`;\n }\n case 'percent':\n case 'percentage': {\n if (!Number.isFinite(num)) return this.rawValue(value);\n return `${num.toFixed(column.percentageFormat?.decimals ?? 2)}%`;\n }\n case 'date': {\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return this.rawValue(value);\n const format = column.dateFormat || {};\n const dateStyle = format.format === 'custom' ? undefined : (format.format || 'short');\n return new Intl.DateTimeFormat(format.locale || 'en-US', dateStyle ? { dateStyle } : {}).format(date);\n }\n case 'boolean': {\n const format = column.booleanFormat || {};\n const truthy = value === true || String(value) === String(format.trueValue ?? 'true');\n return truthy ? (format.trueSymbol || format.trueValue || 'true') : (format.falseSymbol || format.falseValue || 'false');\n }\n case 'scientific':\n return Number.isFinite(num) ? num.toExponential(column.numberFormat?.decimals ?? 2) : this.rawValue(value);\n default:\n return this.rawValue(value);\n }\n }\n\n private escapeHTML(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table-export.js","sources":["../../../../components/table/table-export.ts"],"sourcesContent":["/**\n * Export and clipboard functionality for snice-table.\n * Handles: CSV export, print, clipboard copy.\n */\nimport type { ColumnDefinition } from './snice-table.types';\nimport { parseCellDate } from './table-date';\n\nexport interface CSVExportOptions {\n /** CSV delimiter. Default: ',' */\n delimiter?: string;\n /** Filename for download. Default: 'export.csv' */\n filename?: string;\n /** Include column headers. Default: true */\n includeHeaders?: boolean;\n /** Export only selected rows. Default: false */\n selectedOnly?: boolean;\n /** Specific column keys to export. Default: all visible */\n columns?: string[];\n /** Add UTF-8 BOM for Excel compatibility. Default: true */\n utf8BOM?: boolean;\n}\n\nexport interface PrintOptions {\n /** Hide footer in print. Default: false */\n hideFooter?: boolean;\n /** Hide toolbar in print. Default: false */\n hideToolbar?: boolean;\n /** Include selection checkboxes. Default: false */\n includeCheckboxes?: boolean;\n /** Custom page CSS. */\n pageStyles?: string;\n}\n\nexport interface ClipboardOptions {\n /** Delimiter for copied cells. Default: '\\t' */\n delimiter?: string;\n /** Use formatted values or raw. Default: true (formatted) */\n useFormatted?: boolean;\n}\n\nexport class TableExport {\n /**\n * Export data to CSV and trigger download.\n */\n exportCSV(\n data: any[],\n columns: { key: string; label: string; exportable?: boolean }[],\n options: CSVExportOptions = {}\n ) {\n const {\n delimiter = ',',\n filename = 'export.csv',\n includeHeaders = true,\n columns: columnKeys,\n utf8BOM = true,\n } = options;\n\n // Filter columns\n let exportColumns = columns.filter(c => (c as any).exportable !== false);\n if (columnKeys) {\n exportColumns = exportColumns.filter(c => columnKeys.includes(c.key));\n }\n\n const rows: string[] = [];\n\n // Header row\n if (includeHeaders) {\n rows.push(exportColumns.map(c => this.escapeCSV(c.label, delimiter)).join(delimiter));\n }\n\n // Data rows\n for (const row of data) {\n const cells = exportColumns.map(c => {\n const value = this.cellValue(row, c);\n return this.escapeCSV(value == null ? '' : String(value), delimiter);\n });\n rows.push(cells.join(delimiter));\n }\n\n const csv = rows.join('\\n');\n const bom = utf8BOM ? '\\ufeff' : '';\n const blob = new Blob([bom + csv], { type: 'text/csv;charset=utf-8;' });\n\n // Download\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.style.display = 'none';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }\n\n /**\n * Open browser print dialog with table content.\n */\n print(\n tableElement: HTMLElement,\n options: PrintOptions = {}\n ) {\n const {\n hideFooter = false,\n hideToolbar = false,\n includeCheckboxes = false,\n pageStyles = '',\n } = options;\n\n // Clone the table content\n const shadowRoot = tableElement.shadowRoot;\n if (!shadowRoot) return;\n\n const table = shadowRoot.querySelector('table');\n if (!table) return;\n\n const tableClone = table.cloneNode(true) as HTMLTableElement;\n\n // Shadow-root cell contents are not included by outerHTML. Flatten each\n // rendered custom cell into plain print text so the printout contains what\n // the user actually sees rather than empty custom-element tags.\n const originalCells = Array.from(table.querySelectorAll('snice-cell, [in-table], snice-checkbox'));\n const clonedCells = Array.from(tableClone.querySelectorAll('snice-cell, [in-table], snice-checkbox'));\n originalCells.forEach((original: any, index) => {\n const clone = clonedCells[index] as HTMLElement | undefined;\n if (!clone) return;\n if (original.localName === 'snice-checkbox') {\n clone.replaceWith(document.createTextNode(original.checked ? '☑' : '☐'));\n return;\n }\n const content = original.shadowRoot?.querySelector('[part~=\"content\"]');\n clone.replaceWith(document.createTextNode((content?.textContent || original.textContent || '').trim()));\n });\n\n if (!includeCheckboxes) {\n tableClone.querySelectorAll('.select-column').forEach((cell) => cell.remove());\n }\n\n const plainSection = (selector: string, className: string): string => {\n const source = shadowRoot.querySelector(selector);\n if (!source) return '';\n const clone = source.cloneNode(true) as HTMLElement;\n clone.querySelectorAll('style').forEach((style) => style.remove());\n clone.querySelectorAll('button').forEach((button) => {\n if (!button.textContent?.trim()) button.textContent = button.getAttribute('aria-label') || '';\n });\n const text = clone.textContent?.replace(/\\s+/g, ' ').trim();\n return text ? `<div class=\"${className}\">${this.escapeHTML(text)}</div>` : '';\n };\n const toolbarHTML = hideToolbar ? '' : plainSection('.table-controls-container', 'print-toolbar');\n const footerHTML = hideFooter ? '' : plainSection('.table-pagination-container', 'print-footer');\n\n const printWindow = window.open('', '_blank');\n if (!printWindow) return;\n\n const html = `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Print</title>\n <style>\n body { font-family: system-ui, -apple-system, sans-serif; margin: 1rem; }\n table { width: 100%; border-collapse: collapse; }\n th, td { padding: 8px 12px; border: 1px solid #ddd; text-align: left; }\n th { background: #f5f5f5; font-weight: 600; }\n .print-toolbar, .print-footer { margin: 0 0 0.75rem; color: #444; }\n .print-footer { margin: 0.75rem 0 0; }\n @media print {\n body { margin: 0; }\n table { page-break-inside: auto; }\n tr { page-break-inside: avoid; }\n }\n ${pageStyles}\n </style>\n </head>\n <body>\n ${toolbarHTML}\n ${tableClone.outerHTML}\n ${footerHTML}\n </body>\n </html>\n `;\n\n printWindow.document.write(html);\n printWindow.document.close();\n printWindow.onload = () => {\n printWindow.print();\n printWindow.close();\n };\n }\n\n /**\n * Copy cells to clipboard as tab-separated text.\n */\n async copyToClipboard(\n data: any[],\n columns: ColumnDefinition[],\n options: ClipboardOptions = {}\n ): Promise<boolean> {\n const { delimiter = '\\t', useFormatted = true } = options;\n const rows = data;\n\n if (rows.length === 0) return false;\n\n const text = rows.map(row =>\n columns.map(c => {\n const value = this.cellValue(row, c);\n return useFormatted ? this.formatValue(value, row, c) : this.rawValue(value);\n }).join(delimiter)\n ).join('\\n');\n\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch {\n // Fallback for older browsers\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.cssText = 'position:fixed;left:-9999px;';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n }\n\n private escapeCSV(value: string, delimiter: string): string {\n if (value.includes(delimiter) || value.includes('\"') || value.includes('\\n') || value.includes('\\r')) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n }\n\n /**\n * The column's working value for a row: `valueGetter` \"runs for display,\n * sort, aggregation\" — and for export, which must ship what the table shows,\n * not a row field the column may not even have.\n */\n private cellValue(row: any, column: ColumnDefinition): any {\n const raw = row?.[column.key];\n return column.valueGetter ? column.valueGetter(raw, row) : raw;\n }\n\n private rawValue(value: any): string {\n if (value == null) return '';\n if (typeof value === 'object') {\n try { return JSON.stringify(value); } catch { return String(value); }\n }\n return String(value);\n }\n\n private formatValue(value: any, row: any, column: ColumnDefinition): string {\n const override = column.formatter || column.valueFormatter;\n if (override) return String(override(value, row) ?? '');\n if (value == null) return '';\n\n const num = Number(value);\n switch (column.type) {\n case 'number':\n case 'accounting': {\n if (!Number.isFinite(num)) return this.rawValue(value);\n const format = column.numberFormat || {};\n const decimals = format.decimals ?? (column.type === 'accounting' ? 2 : 0);\n const grouped = new Intl.NumberFormat('en-US', {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n useGrouping: format.thousandsSeparator ?? false,\n }).format(Math.abs(num));\n let output = num < 0 && (format.negativeStyle === 'parentheses' || column.type === 'accounting')\n ? `(${grouped})`\n : `${num < 0 ? '-' : ''}${grouped}`;\n if (format.prefix) output = format.prefix + output;\n if (format.suffix) output += format.suffix;\n return output;\n }\n case 'currency': {\n if (!Number.isFinite(num)) return this.rawValue(value);\n const format = column.currencyFormat || {};\n const decimals = format.decimals ?? 2;\n const display = format.currencyDisplay || format.display || 'symbol';\n const output = new Intl.NumberFormat(format.locale || 'en-US', {\n style: 'currency',\n currency: format.currency || 'USD',\n currencyDisplay: display,\n useGrouping: format.thousandsSeparator ?? true,\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(Math.abs(num));\n if (num >= 0) return output;\n return format.negativeStyle === 'parentheses' ? `(${output})` : `-${output}`;\n }\n case 'percent':\n case 'percentage': {\n if (!Number.isFinite(num)) return this.rawValue(value);\n return `${num.toFixed(column.percentageFormat?.decimals ?? 2)}%`;\n }\n case 'date': {\n // Exported dates must read as the cell reads them: a calendar day is\n // local midnight, never UTC midnight (see table-date.ts).\n const date = parseCellDate(value);\n if (!date) return this.rawValue(value);\n const format = column.dateFormat || {};\n const dateStyle = format.format === 'custom' ? undefined : (format.format || 'short');\n return new Intl.DateTimeFormat(format.locale || 'en-US', dateStyle ? { dateStyle } : {}).format(date);\n }\n case 'boolean': {\n const format = column.booleanFormat || {};\n const truthy = value === true || String(value) === String(format.trueValue ?? 'true');\n return truthy ? (format.trueSymbol || format.trueValue || 'true') : (format.falseSymbol || format.falseValue || 'false');\n }\n case 'scientific':\n return Number.isFinite(num) ? num.toExponential(column.numberFormat?.decimals ?? 2) : this.rawValue(value);\n default:\n return this.rawValue(value);\n }\n }\n\n private escapeHTML(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n }\n}\n"],"names":[],"mappings":"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"}
@@ -60,6 +60,15 @@ export declare class TableFilterEngine {
60
60
  }[]): any[];
61
61
  /** Evaluate a single filter against a row */
62
62
  private evaluateFilter;
63
+ /**
64
+ * The local calendar day a value falls on, as a `YYYY-M-D` stamp, or `null`
65
+ * when the value names no day at all.
66
+ */
67
+ private calendarDay;
68
+ /** True when the raw bound named a calendar day rather than an instant. */
69
+ private isDateOnlyBound;
70
+ /** Last instant covered by a bound — the day's end when it named a day. */
71
+ private boundEnd;
63
72
  /** Serialize filter model for server-side requests */
64
73
  toServerParams(): any;
65
74
  }
@@ -1,3 +1,5 @@
1
+ import { parseCellDate } from './table-date.js';
2
+
1
3
  /**
2
4
  * Filtering engine for snice-table.
3
5
  * Handles: per-column filters, quick filter, header filters.
@@ -199,25 +201,45 @@ class TableFilterEngine {
199
201
  return Number(cellValue) < Number(filterValue);
200
202
  case 'lte':
201
203
  return Number(cellValue) <= Number(filterValue);
202
- // Date
203
- case 'is': {
204
- const d1 = new Date(cellValue).toDateString();
205
- const d2 = new Date(filterValue).toDateString();
206
- return d1 === d2;
207
- }
204
+ // Date. Both sides cross the cell layer's parser, so a `2026-03-15`
205
+ // bound means that calendar day locally — the same day the cell paints
206
+ // instead of UTC midnight, which shifted every comparison by a day
207
+ // for negative UTC offsets and split a date-only bound from a
208
+ // timestamped cell value on the same day.
209
+ case 'is':
208
210
  case 'isNot': {
209
- const d1 = new Date(cellValue).toDateString();
210
- const d2 = new Date(filterValue).toDateString();
211
- return d1 !== d2;
211
+ const cellDay = this.calendarDay(cellValue);
212
+ const filterDay = this.calendarDay(filterValue);
213
+ // An unparseable value is on no day: it never "is" the filtered day,
214
+ // and it is therefore always "isNot" it.
215
+ const sameDay = cellDay !== null && filterDay !== null && cellDay === filterDay;
216
+ return filter.operator === 'is' ? sameDay : !sameDay;
212
217
  }
213
218
  case 'before':
214
- return new Date(cellValue) < new Date(filterValue);
215
219
  case 'onOrBefore':
216
- return new Date(cellValue) <= new Date(filterValue);
217
220
  case 'after':
218
- return new Date(cellValue) > new Date(filterValue);
219
- case 'onOrAfter':
220
- return new Date(cellValue) >= new Date(filterValue);
221
+ case 'onOrAfter': {
222
+ const cell = parseCellDate(cellValue);
223
+ const bound = parseCellDate(filterValue);
224
+ if (!cell || !bound)
225
+ return false;
226
+ const a = cell.getTime();
227
+ // A date-only bound names a whole DAY, not the instant it starts at, so
228
+ // the day's two edges bracket it. Comparing everything against its
229
+ // opening instant made the four operators disagree with `is`: a cell at
230
+ // 09:00 on the bound day "is" that day, yet counted as `after` it and
231
+ // fell outside `onOrBefore` it. A bound that carries its own time is an
232
+ // instant, and both edges collapse onto it.
233
+ const dayStart = bound.getTime();
234
+ const dayEnd = this.boundEnd(filterValue, bound);
235
+ if (filter.operator === 'before')
236
+ return a < dayStart;
237
+ if (filter.operator === 'onOrBefore')
238
+ return a <= dayEnd;
239
+ if (filter.operator === 'after')
240
+ return a > dayEnd;
241
+ return a >= dayStart;
242
+ }
221
243
  // Boolean
222
244
  case 'isTrue':
223
245
  return cellValue === true || cellValue === 'true' || cellValue === 1;
@@ -227,6 +249,28 @@ class TableFilterEngine {
227
249
  return true;
228
250
  }
229
251
  }
252
+ /**
253
+ * The local calendar day a value falls on, as a `YYYY-M-D` stamp, or `null`
254
+ * when the value names no day at all.
255
+ */
256
+ calendarDay(value) {
257
+ const date = parseCellDate(value);
258
+ if (!date)
259
+ return null;
260
+ return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
261
+ }
262
+ /** True when the raw bound named a calendar day rather than an instant. */
263
+ isDateOnlyBound(raw) {
264
+ return typeof raw === 'string' && /^\d{4}(?:-\d{2}){0,2}$/.test(raw.trim());
265
+ }
266
+ /** Last instant covered by a bound — the day's end when it named a day. */
267
+ boundEnd(raw, parsed) {
268
+ if (!this.isDateOnlyBound(raw))
269
+ return parsed.getTime();
270
+ const end = new Date(parsed);
271
+ end.setDate(end.getDate() + 1);
272
+ return end.getTime() - 1;
273
+ }
230
274
  /** Serialize filter model for server-side requests */
231
275
  toServerParams() {
232
276
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"table-filter-engine.js","sources":["../../../../components/table/table-filter-engine.ts"],"sourcesContent":["/**\n * Filtering engine for snice-table.\n * Handles: per-column filters, quick filter, header filters.\n * Supports client-side and server-side modes.\n */\n\nexport type FilterOperator =\n // Text\n | 'contains' | 'notContains' | 'equals' | 'notEquals'\n | 'startsWith' | 'endsWith' | 'isEmpty' | 'isNotEmpty'\n // Number\n | 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte'\n // Date\n | 'is' | 'isNot' | 'before' | 'onOrBefore' | 'after' | 'onOrAfter'\n // Boolean\n | 'isTrue' | 'isFalse';\n\nexport type FilterLogic = 'and' | 'or';\n\nexport interface ColumnFilter {\n column: string;\n operator: FilterOperator;\n value: any;\n}\n\nexport interface FilterModel {\n filters: ColumnFilter[];\n logic: FilterLogic;\n quickFilter?: string;\n quickFilterLogic?: FilterLogic;\n}\n\nexport interface FilterOperatorDef {\n value: FilterOperator;\n label: string;\n requiresValue: boolean;\n}\n\nconst TEXT_OPERATORS: FilterOperatorDef[] = [\n { value: 'contains', label: 'contains', requiresValue: true },\n { value: 'notContains', label: 'does not contain', requiresValue: true },\n { value: 'equals', label: 'equals', requiresValue: true },\n { value: 'notEquals', label: 'does not equal', requiresValue: true },\n { value: 'startsWith', label: 'starts with', requiresValue: true },\n { value: 'endsWith', label: 'ends with', requiresValue: true },\n { value: 'isEmpty', label: 'is empty', requiresValue: false },\n { value: 'isNotEmpty', label: 'is not empty', requiresValue: false },\n];\n\nconst NUMBER_OPERATORS: FilterOperatorDef[] = [\n { value: 'eq', label: '=', requiresValue: true },\n { value: 'neq', label: '≠', requiresValue: true },\n { value: 'gt', label: '>', requiresValue: true },\n { value: 'gte', label: '≥', requiresValue: true },\n { value: 'lt', label: '<', requiresValue: true },\n { value: 'lte', label: '≤', requiresValue: true },\n { value: 'isEmpty', label: 'is empty', requiresValue: false },\n { value: 'isNotEmpty', label: 'is not empty', requiresValue: false },\n];\n\nconst DATE_OPERATORS: FilterOperatorDef[] = [\n { value: 'is', label: 'is', requiresValue: true },\n { value: 'isNot', label: 'is not', requiresValue: true },\n { value: 'before', label: 'before', requiresValue: true },\n { value: 'onOrBefore', label: 'on or before', requiresValue: true },\n { value: 'after', label: 'after', requiresValue: true },\n { value: 'onOrAfter', label: 'on or after', requiresValue: true },\n { value: 'isEmpty', label: 'is empty', requiresValue: false },\n { value: 'isNotEmpty', label: 'is not empty', requiresValue: false },\n];\n\nconst BOOLEAN_OPERATORS: FilterOperatorDef[] = [\n { value: 'isTrue', label: 'is true', requiresValue: false },\n { value: 'isFalse', label: 'is false', requiresValue: false },\n];\n\nexport class TableFilterEngine {\n private model: FilterModel = { filters: [], logic: 'and' };\n private headerFilters: Map<string, string> = new Map(); // column key → quick value\n\n /** Get operators available for a column type */\n getOperatorsForType(type: string): FilterOperatorDef[] {\n switch (type) {\n case 'number':\n case 'currency':\n case 'rating':\n case 'progress':\n case 'filesize':\n case 'duration':\n case 'percent':\n case 'percentage':\n return NUMBER_OPERATORS;\n case 'date':\n return DATE_OPERATORS;\n case 'boolean':\n return BOOLEAN_OPERATORS;\n default:\n return TEXT_OPERATORS;\n }\n }\n\n /** Set the full filter model */\n setFilterModel(model: FilterModel) {\n this.model = model;\n }\n\n /** Get the current filter model */\n getFilterModel(): FilterModel {\n return { ...this.model };\n }\n\n /** Add or update a column filter */\n setColumnFilter(column: string, operator: FilterOperator, value: any) {\n const existing = this.model.filters.findIndex(f => f.column === column);\n if (existing >= 0) {\n this.model.filters[existing] = { column, operator, value };\n } else {\n this.model.filters.push({ column, operator, value });\n }\n }\n\n /** Remove a column filter */\n removeColumnFilter(column: string) {\n this.model.filters = this.model.filters.filter(f => f.column !== column);\n }\n\n /** Clear all filters */\n clearAllFilters() {\n this.model.filters = [];\n this.model.quickFilter = undefined;\n this.model.logic = 'and';\n this.headerFilters.clear();\n }\n\n /** Set quick filter text (searches across all columns) */\n setQuickFilter(text: string) {\n this.model.quickFilter = text || undefined;\n }\n\n /** Set header filter for a column (inline quick text input) */\n setHeaderFilter(column: string, value: string) {\n if (value) {\n this.headerFilters.set(column, value);\n } else {\n this.headerFilters.delete(column);\n }\n }\n\n /** Get header filter value for a column */\n getHeaderFilter(column: string): string {\n return this.headerFilters.get(column) || '';\n }\n\n /** Set filter logic (and/or) */\n setFilterLogic(logic: FilterLogic) {\n this.model.logic = logic;\n }\n\n /** Check if any filters are active */\n hasActiveFilters(): boolean {\n return this.model.filters.length > 0 || !!this.model.quickFilter || this.headerFilters.size > 0;\n }\n\n /** Check if a specific column has an active filter */\n hasColumnFilter(column: string): boolean {\n return this.model.filters.some(f => f.column === column) || this.headerFilters.has(column);\n }\n\n /** Get active filter count */\n getActiveFilterCount(): number {\n return this.model.filters.length + this.headerFilters.size + (this.model.quickFilter ? 1 : 0);\n }\n\n /**\n * Apply filters to a dataset (client-side mode).\n * Returns filtered array (original is not modified).\n */\n applyFilters(data: any[], columns: { key: string; type?: string }[]): any[] {\n let result = data;\n\n // Apply column filters\n if (this.model.filters.length > 0) {\n result = result.filter(row => {\n const results = this.model.filters.map(f => this.evaluateFilter(f, row));\n return this.model.logic === 'and'\n ? results.every(Boolean)\n : results.some(Boolean);\n });\n }\n\n // Apply header filters (always AND)\n for (const [column, value] of this.headerFilters) {\n const lower = value.toLowerCase();\n result = result.filter(row => {\n const cellValue = row[column];\n return cellValue != null && String(cellValue).toLowerCase().includes(lower);\n });\n }\n\n // Apply quick filter\n if (this.model.quickFilter) {\n const terms = this.model.quickFilter.toLowerCase().split(/\\s+/).filter(Boolean);\n const searchCols = columns.map(c => c.key);\n const logic = this.model.quickFilterLogic || 'and';\n\n result = result.filter(row => {\n const matchesTerm = (term: string) =>\n searchCols.some(key => {\n const val = row[key];\n return val != null && String(val).toLowerCase().includes(term);\n });\n\n return logic === 'and'\n ? terms.every(matchesTerm)\n : terms.some(matchesTerm);\n });\n }\n\n return result;\n }\n\n /** Evaluate a single filter against a row */\n private evaluateFilter(filter: ColumnFilter, row: any): boolean {\n const cellValue = row[filter.column];\n const filterValue = filter.value;\n\n switch (filter.operator) {\n // Text\n case 'contains':\n return cellValue != null && String(cellValue).toLowerCase().includes(String(filterValue).toLowerCase());\n case 'notContains':\n return cellValue == null || !String(cellValue).toLowerCase().includes(String(filterValue).toLowerCase());\n case 'equals':\n return String(cellValue ?? '').toLowerCase() === String(filterValue).toLowerCase();\n case 'notEquals':\n return String(cellValue ?? '').toLowerCase() !== String(filterValue).toLowerCase();\n case 'startsWith':\n return cellValue != null && String(cellValue).toLowerCase().startsWith(String(filterValue).toLowerCase());\n case 'endsWith':\n return cellValue != null && String(cellValue).toLowerCase().endsWith(String(filterValue).toLowerCase());\n case 'isEmpty':\n return cellValue == null || cellValue === '';\n case 'isNotEmpty':\n return cellValue != null && cellValue !== '';\n\n // Number\n case 'eq':\n return Number(cellValue) === Number(filterValue);\n case 'neq':\n return Number(cellValue) !== Number(filterValue);\n case 'gt':\n return Number(cellValue) > Number(filterValue);\n case 'gte':\n return Number(cellValue) >= Number(filterValue);\n case 'lt':\n return Number(cellValue) < Number(filterValue);\n case 'lte':\n return Number(cellValue) <= Number(filterValue);\n\n // Date\n case 'is': {\n const d1 = new Date(cellValue).toDateString();\n const d2 = new Date(filterValue).toDateString();\n return d1 === d2;\n }\n case 'isNot': {\n const d1 = new Date(cellValue).toDateString();\n const d2 = new Date(filterValue).toDateString();\n return d1 !== d2;\n }\n case 'before':\n return new Date(cellValue) < new Date(filterValue);\n case 'onOrBefore':\n return new Date(cellValue) <= new Date(filterValue);\n case 'after':\n return new Date(cellValue) > new Date(filterValue);\n case 'onOrAfter':\n return new Date(cellValue) >= new Date(filterValue);\n\n // Boolean\n case 'isTrue':\n return cellValue === true || cellValue === 'true' || cellValue === 1;\n case 'isFalse':\n return cellValue === false || cellValue === 'false' || cellValue === 0 || cellValue == null;\n\n default:\n return true;\n }\n }\n\n /** Serialize filter model for server-side requests */\n toServerParams(): any {\n return {\n filters: this.model.filters,\n filterLogic: this.model.logic,\n quickFilter: this.model.quickFilter,\n headerFilters: Object.fromEntries(this.headerFilters),\n };\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table-filter-engine.js","sources":["../../../../components/table/table-filter-engine.ts"],"sourcesContent":["/**\n * Filtering engine for snice-table.\n * Handles: per-column filters, quick filter, header filters.\n * Supports client-side and server-side modes.\n */\n\nimport { parseCellDate } from './table-date';\n\nexport type FilterOperator =\n // Text\n | 'contains' | 'notContains' | 'equals' | 'notEquals'\n | 'startsWith' | 'endsWith' | 'isEmpty' | 'isNotEmpty'\n // Number\n | 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte'\n // Date\n | 'is' | 'isNot' | 'before' | 'onOrBefore' | 'after' | 'onOrAfter'\n // Boolean\n | 'isTrue' | 'isFalse';\n\nexport type FilterLogic = 'and' | 'or';\n\nexport interface ColumnFilter {\n column: string;\n operator: FilterOperator;\n value: any;\n}\n\nexport interface FilterModel {\n filters: ColumnFilter[];\n logic: FilterLogic;\n quickFilter?: string;\n quickFilterLogic?: FilterLogic;\n}\n\nexport interface FilterOperatorDef {\n value: FilterOperator;\n label: string;\n requiresValue: boolean;\n}\n\nconst TEXT_OPERATORS: FilterOperatorDef[] = [\n { value: 'contains', label: 'contains', requiresValue: true },\n { value: 'notContains', label: 'does not contain', requiresValue: true },\n { value: 'equals', label: 'equals', requiresValue: true },\n { value: 'notEquals', label: 'does not equal', requiresValue: true },\n { value: 'startsWith', label: 'starts with', requiresValue: true },\n { value: 'endsWith', label: 'ends with', requiresValue: true },\n { value: 'isEmpty', label: 'is empty', requiresValue: false },\n { value: 'isNotEmpty', label: 'is not empty', requiresValue: false },\n];\n\nconst NUMBER_OPERATORS: FilterOperatorDef[] = [\n { value: 'eq', label: '=', requiresValue: true },\n { value: 'neq', label: '≠', requiresValue: true },\n { value: 'gt', label: '>', requiresValue: true },\n { value: 'gte', label: '≥', requiresValue: true },\n { value: 'lt', label: '<', requiresValue: true },\n { value: 'lte', label: '≤', requiresValue: true },\n { value: 'isEmpty', label: 'is empty', requiresValue: false },\n { value: 'isNotEmpty', label: 'is not empty', requiresValue: false },\n];\n\nconst DATE_OPERATORS: FilterOperatorDef[] = [\n { value: 'is', label: 'is', requiresValue: true },\n { value: 'isNot', label: 'is not', requiresValue: true },\n { value: 'before', label: 'before', requiresValue: true },\n { value: 'onOrBefore', label: 'on or before', requiresValue: true },\n { value: 'after', label: 'after', requiresValue: true },\n { value: 'onOrAfter', label: 'on or after', requiresValue: true },\n { value: 'isEmpty', label: 'is empty', requiresValue: false },\n { value: 'isNotEmpty', label: 'is not empty', requiresValue: false },\n];\n\nconst BOOLEAN_OPERATORS: FilterOperatorDef[] = [\n { value: 'isTrue', label: 'is true', requiresValue: false },\n { value: 'isFalse', label: 'is false', requiresValue: false },\n];\n\nexport class TableFilterEngine {\n private model: FilterModel = { filters: [], logic: 'and' };\n private headerFilters: Map<string, string> = new Map(); // column key → quick value\n\n /** Get operators available for a column type */\n getOperatorsForType(type: string): FilterOperatorDef[] {\n switch (type) {\n case 'number':\n case 'currency':\n case 'rating':\n case 'progress':\n case 'filesize':\n case 'duration':\n case 'percent':\n case 'percentage':\n return NUMBER_OPERATORS;\n case 'date':\n return DATE_OPERATORS;\n case 'boolean':\n return BOOLEAN_OPERATORS;\n default:\n return TEXT_OPERATORS;\n }\n }\n\n /** Set the full filter model */\n setFilterModel(model: FilterModel) {\n this.model = model;\n }\n\n /** Get the current filter model */\n getFilterModel(): FilterModel {\n return { ...this.model };\n }\n\n /** Add or update a column filter */\n setColumnFilter(column: string, operator: FilterOperator, value: any) {\n const existing = this.model.filters.findIndex(f => f.column === column);\n if (existing >= 0) {\n this.model.filters[existing] = { column, operator, value };\n } else {\n this.model.filters.push({ column, operator, value });\n }\n }\n\n /** Remove a column filter */\n removeColumnFilter(column: string) {\n this.model.filters = this.model.filters.filter(f => f.column !== column);\n }\n\n /** Clear all filters */\n clearAllFilters() {\n this.model.filters = [];\n this.model.quickFilter = undefined;\n this.model.logic = 'and';\n this.headerFilters.clear();\n }\n\n /** Set quick filter text (searches across all columns) */\n setQuickFilter(text: string) {\n this.model.quickFilter = text || undefined;\n }\n\n /** Set header filter for a column (inline quick text input) */\n setHeaderFilter(column: string, value: string) {\n if (value) {\n this.headerFilters.set(column, value);\n } else {\n this.headerFilters.delete(column);\n }\n }\n\n /** Get header filter value for a column */\n getHeaderFilter(column: string): string {\n return this.headerFilters.get(column) || '';\n }\n\n /** Set filter logic (and/or) */\n setFilterLogic(logic: FilterLogic) {\n this.model.logic = logic;\n }\n\n /** Check if any filters are active */\n hasActiveFilters(): boolean {\n return this.model.filters.length > 0 || !!this.model.quickFilter || this.headerFilters.size > 0;\n }\n\n /** Check if a specific column has an active filter */\n hasColumnFilter(column: string): boolean {\n return this.model.filters.some(f => f.column === column) || this.headerFilters.has(column);\n }\n\n /** Get active filter count */\n getActiveFilterCount(): number {\n return this.model.filters.length + this.headerFilters.size + (this.model.quickFilter ? 1 : 0);\n }\n\n /**\n * Apply filters to a dataset (client-side mode).\n * Returns filtered array (original is not modified).\n */\n applyFilters(data: any[], columns: { key: string; type?: string }[]): any[] {\n let result = data;\n\n // Apply column filters\n if (this.model.filters.length > 0) {\n result = result.filter(row => {\n const results = this.model.filters.map(f => this.evaluateFilter(f, row));\n return this.model.logic === 'and'\n ? results.every(Boolean)\n : results.some(Boolean);\n });\n }\n\n // Apply header filters (always AND)\n for (const [column, value] of this.headerFilters) {\n const lower = value.toLowerCase();\n result = result.filter(row => {\n const cellValue = row[column];\n return cellValue != null && String(cellValue).toLowerCase().includes(lower);\n });\n }\n\n // Apply quick filter\n if (this.model.quickFilter) {\n const terms = this.model.quickFilter.toLowerCase().split(/\\s+/).filter(Boolean);\n const searchCols = columns.map(c => c.key);\n const logic = this.model.quickFilterLogic || 'and';\n\n result = result.filter(row => {\n const matchesTerm = (term: string) =>\n searchCols.some(key => {\n const val = row[key];\n return val != null && String(val).toLowerCase().includes(term);\n });\n\n return logic === 'and'\n ? terms.every(matchesTerm)\n : terms.some(matchesTerm);\n });\n }\n\n return result;\n }\n\n /** Evaluate a single filter against a row */\n private evaluateFilter(filter: ColumnFilter, row: any): boolean {\n const cellValue = row[filter.column];\n const filterValue = filter.value;\n\n switch (filter.operator) {\n // Text\n case 'contains':\n return cellValue != null && String(cellValue).toLowerCase().includes(String(filterValue).toLowerCase());\n case 'notContains':\n return cellValue == null || !String(cellValue).toLowerCase().includes(String(filterValue).toLowerCase());\n case 'equals':\n return String(cellValue ?? '').toLowerCase() === String(filterValue).toLowerCase();\n case 'notEquals':\n return String(cellValue ?? '').toLowerCase() !== String(filterValue).toLowerCase();\n case 'startsWith':\n return cellValue != null && String(cellValue).toLowerCase().startsWith(String(filterValue).toLowerCase());\n case 'endsWith':\n return cellValue != null && String(cellValue).toLowerCase().endsWith(String(filterValue).toLowerCase());\n case 'isEmpty':\n return cellValue == null || cellValue === '';\n case 'isNotEmpty':\n return cellValue != null && cellValue !== '';\n\n // Number\n case 'eq':\n return Number(cellValue) === Number(filterValue);\n case 'neq':\n return Number(cellValue) !== Number(filterValue);\n case 'gt':\n return Number(cellValue) > Number(filterValue);\n case 'gte':\n return Number(cellValue) >= Number(filterValue);\n case 'lt':\n return Number(cellValue) < Number(filterValue);\n case 'lte':\n return Number(cellValue) <= Number(filterValue);\n\n // Date. Both sides cross the cell layer's parser, so a `2026-03-15`\n // bound means that calendar day locally — the same day the cell paints\n // — instead of UTC midnight, which shifted every comparison by a day\n // for negative UTC offsets and split a date-only bound from a\n // timestamped cell value on the same day.\n case 'is':\n case 'isNot': {\n const cellDay = this.calendarDay(cellValue);\n const filterDay = this.calendarDay(filterValue);\n // An unparseable value is on no day: it never \"is\" the filtered day,\n // and it is therefore always \"isNot\" it.\n const sameDay = cellDay !== null && filterDay !== null && cellDay === filterDay;\n return filter.operator === 'is' ? sameDay : !sameDay;\n }\n case 'before':\n case 'onOrBefore':\n case 'after':\n case 'onOrAfter': {\n const cell = parseCellDate(cellValue);\n const bound = parseCellDate(filterValue);\n if (!cell || !bound) return false;\n const a = cell.getTime();\n // A date-only bound names a whole DAY, not the instant it starts at, so\n // the day's two edges bracket it. Comparing everything against its\n // opening instant made the four operators disagree with `is`: a cell at\n // 09:00 on the bound day \"is\" that day, yet counted as `after` it and\n // fell outside `onOrBefore` it. A bound that carries its own time is an\n // instant, and both edges collapse onto it.\n const dayStart = bound.getTime();\n const dayEnd = this.boundEnd(filterValue, bound);\n if (filter.operator === 'before') return a < dayStart;\n if (filter.operator === 'onOrBefore') return a <= dayEnd;\n if (filter.operator === 'after') return a > dayEnd;\n return a >= dayStart;\n }\n\n // Boolean\n case 'isTrue':\n return cellValue === true || cellValue === 'true' || cellValue === 1;\n case 'isFalse':\n return cellValue === false || cellValue === 'false' || cellValue === 0 || cellValue == null;\n\n default:\n return true;\n }\n }\n\n /**\n * The local calendar day a value falls on, as a `YYYY-M-D` stamp, or `null`\n * when the value names no day at all.\n */\n private calendarDay(value: any): string | null {\n const date = parseCellDate(value);\n if (!date) return null;\n return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;\n }\n\n /** True when the raw bound named a calendar day rather than an instant. */\n private isDateOnlyBound(raw: any): boolean {\n return typeof raw === 'string' && /^\\d{4}(?:-\\d{2}){0,2}$/.test(raw.trim());\n }\n\n /** Last instant covered by a bound — the day's end when it named a day. */\n private boundEnd(raw: any, parsed: Date): number {\n if (!this.isDateOnlyBound(raw)) return parsed.getTime();\n const end = new Date(parsed);\n end.setDate(end.getDate() + 1);\n return end.getTime() - 1;\n }\n\n /** Serialize filter model for server-side requests */\n toServerParams(): any {\n return {\n filters: this.model.filters,\n filterLogic: this.model.logic,\n quickFilter: this.model.quickFilter,\n headerFilters: Object.fromEntries(this.headerFilters),\n };\n }\n}\n"],"names":[],"mappings":"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"}
@@ -75,6 +75,14 @@ export declare class TableKeyboard {
75
75
  private get totalColCount();
76
76
  private get ariaRowCount();
77
77
  private get ariaRowOffset();
78
+ /**
79
+ * Rows the header contributes to `aria-rowcount`, and the offset every body
80
+ * row's `aria-rowindex` starts from. Normally 1 — but list mode paints a
81
+ * card per row and leaves the thead empty, and claiming a header row that
82
+ * does not exist would count one row too many and start the body at index 2
83
+ * with nothing at index 1.
84
+ */
85
+ private get headerRowCount();
78
86
  /** Resolve a body row by its logical data-index (not its DOM position). */
79
87
  private getBodyRow;
80
88
  /** Apply ARIA attributes to the grid */
@@ -45,7 +45,7 @@ class TableKeyboard {
45
45
  return;
46
46
  table.setAttribute('role', 'grid');
47
47
  table.setAttribute('tabindex', '0');
48
- table.setAttribute('aria-rowcount', String(this.ariaRowCount + 1)); // +1 header
48
+ table.setAttribute('aria-rowcount', String(this.ariaRowCount + this.headerRowCount));
49
49
  table.setAttribute('aria-colcount', String(this.totalColCount));
50
50
  }
51
51
  /**
@@ -79,6 +79,16 @@ class TableKeyboard {
79
79
  get ariaRowOffset() {
80
80
  return Math.max(0, this.resolveBound(this.options.ariaRowOffset));
81
81
  }
82
+ /**
83
+ * Rows the header contributes to `aria-rowcount`, and the offset every body
84
+ * row's `aria-rowindex` starts from. Normally 1 — but list mode paints a
85
+ * card per row and leaves the thead empty, and claiming a header row that
86
+ * does not exist would count one row too many and start the body at index 2
87
+ * with nothing at index 1.
88
+ */
89
+ get headerRowCount() {
90
+ return this.getTable()?.querySelector('thead tr') ? 1 : 0;
91
+ }
82
92
  /** Resolve a body row by its logical data-index (not its DOM position). */
83
93
  getBodyRow(index) {
84
94
  if (this.options.getRowElement)
@@ -93,7 +103,8 @@ class TableKeyboard {
93
103
  const table = this.getTable();
94
104
  if (!table)
95
105
  return;
96
- table.setAttribute('aria-rowcount', String(this.ariaRowCount + 1)); // +1 for header
106
+ const headerRows = this.headerRowCount;
107
+ table.setAttribute('aria-rowcount', String(this.ariaRowCount + headerRows));
97
108
  table.setAttribute('aria-colcount', String(this.totalColCount));
98
109
  // Header row
99
110
  const headerRow = table.querySelector('thead tr.column-header-row')
@@ -125,7 +136,7 @@ class TableKeyboard {
125
136
  const bodyRows = table.querySelectorAll('tbody tr:not(.virtual-spacer):not(.table-fill-row)');
126
137
  bodyRows.forEach((row, i) => {
127
138
  row.setAttribute('role', 'row');
128
- row.setAttribute('aria-rowindex', String(this.ariaRowOffset + i + 2)); // +2 for 1-based + header
139
+ row.setAttribute('aria-rowindex', String(this.ariaRowOffset + i + 1 + headerRows)); // 1-based, after the header
129
140
  const isSelected = row.getAttribute('data-selected') === 'true';
130
141
  if (isSelected)
131
142
  row.setAttribute('aria-selected', 'true');
@@ -1 +1 @@
1
- {"version":3,"file":"table-keyboard.js","sources":["../../../../components/table/table-keyboard.ts"],"sourcesContent":["/**\n * Keyboard navigation and ARIA for snice-table.\n * Full WAI-ARIA grid pattern with arrow key navigation.\n */\n\nexport interface KeyboardOptions {\n /** The shadow root containing the table */\n shadowRoot: ShadowRoot;\n /** Callback when a cell is activated (Enter) */\n onCellActivate?: (rowIndex: number, columnKey: string) => void;\n /** Callback when selection changes via keyboard */\n onSelectionToggle?: (rowIndex: number) => void;\n /** Callback for select-all (Ctrl+A) */\n onSelectAll?: () => void;\n /** Tab navigation mode */\n tabMode: 'none' | 'content' | 'header' | 'all';\n /**\n * Total data rows. A getter callback keeps bounds live as the dataset is\n * loaded / filtered / paginated; a plain number is accepted for callers that\n * re-attach on every change.\n */\n totalRows: number | (() => number);\n /** Total columns. Getter callback or plain number — see `totalRows`. */\n totalColumns: number | (() => number);\n /**\n * Total rows exposed to assistive technology. This may exceed the keyboard\n * navigation count when the body also contains non-focusable group and\n * aggregate rows. Defaults to totalRows.\n */\n ariaRows?: number | (() => number);\n /** Zero-based logical offset of the first rendered body row (virtual grids). */\n ariaRowOffset?: number | (() => number);\n /**\n * Resolve a keyboard-navigation row to its rendered <tr>. Grouping/filtering\n * can make navigation order differ from raw data-index order.\n */\n getRowElement?: (rowIndex: number) => HTMLElement | null;\n /**\n * Bring a logical (data-index) row into the rendered window before focus\n * lands on it. Under virtualization only the current window is in the DOM;\n * the host scrolls the row's range into view and renders it synchronously.\n */\n ensureRowVisible?: (rowIndex: number) => void;\n /** Whether editing is active */\n isEditing: () => boolean;\n}\n\nexport class TableKeyboard {\n private options: KeyboardOptions;\n private focusedRow = -1; // -1 = header\n /** True after the first real user keydown — no focus paint at rest. */\n private hasUserInteracted = false;\n private focusedCol = 0;\n // EventListener-typed: ShadowRoot's TS event map only knows 'slotchange',\n // so a KeyboardEvent-typed handler fails the addEventListener overloads.\n private keyHandler: EventListener;\n private attached = false;\n\n constructor() {\n this.options = {} as KeyboardOptions;\n this.keyHandler = (e: Event) => this.handleKeyDown(e as KeyboardEvent);\n }\n\n attach(options: KeyboardOptions) {\n this.options = options;\n const root = options.shadowRoot;\n if (!root) return;\n\n // Delegate keydown at the shadow root — a stable ancestor that always\n // exists (even before the first render) and survives every <table> rebuild.\n // Every keydown inside the shadow tree bubbles here, so navigation works no\n // matter when data/columns arrive or how often renderHeader/renderBody\n // replace the table structure. Binding to the inner <table> instead meant a\n // table built after @ready was never listened to, and a rebuilt table\n // silently dropped the listener.\n root.addEventListener('keydown', this.keyHandler);\n this.attached = true;\n this.syncGridRole();\n this.applyARIA();\n }\n\n detach() {\n this.options.shadowRoot?.removeEventListener('keydown', this.keyHandler);\n this.attached = false;\n }\n\n /**\n * (Re)apply the grid identity to the current <table>: role=grid, a focusable\n * tabindex, and live row/col counts. Cheap, idempotent, and safe to call\n * before the table exists (no-op). A structural rebuild that swaps the\n * <table> node drops these attributes, so they must be re-applied afterwards.\n */\n private syncGridRole() {\n const table = this.getTable();\n if (!table) return;\n\n table.setAttribute('role', 'grid');\n table.setAttribute('tabindex', '0');\n table.setAttribute('aria-rowcount', String(this.ariaRowCount + 1)); // +1 header\n table.setAttribute('aria-colcount', String(this.totalColCount));\n }\n\n /**\n * Host hook: called after renderHeader/renderBody rebuild the table. The\n * delegated keydown listener lives on the shadow root so it survives on its\n * own; this restores the grid role and the roving tabindex on the focused\n * cell, both of which a wiped thead/tbody (or a swapped <table>) would lose.\n */\n refresh() {\n if (!this.attached) return;\n\n this.syncGridRole();\n this.applyARIA();\n }\n\n private getTable(): HTMLElement | null {\n return this.options.shadowRoot?.querySelector('table') ?? null;\n }\n\n /** Resolve a bound that may be a live getter or a static number. */\n private resolveBound(v: number | (() => number) | undefined): number {\n return typeof v === 'function' ? v() : (v ?? 0);\n }\n\n private get totalRowCount(): number {\n return this.resolveBound(this.options.totalRows);\n }\n\n private get totalColCount(): number {\n return this.resolveBound(this.options.totalColumns);\n }\n\n private get ariaRowCount(): number {\n return this.resolveBound(this.options.ariaRows ?? this.options.totalRows);\n }\n\n private get ariaRowOffset(): number {\n return Math.max(0, this.resolveBound(this.options.ariaRowOffset));\n }\n\n /** Resolve a body row by its logical data-index (not its DOM position). */\n private getBodyRow(index: number): HTMLElement | null {\n if (this.options.getRowElement) return this.options.getRowElement(index);\n const table = this.getTable();\n if (!table) return null;\n return table.querySelector(`tbody tr[data-index=\"${index}\"]`);\n }\n\n /** Apply ARIA attributes to the grid */\n applyARIA() {\n const table = this.getTable();\n if (!table) return;\n\n table.setAttribute('aria-rowcount', String(this.ariaRowCount + 1)); // +1 for header\n table.setAttribute('aria-colcount', String(this.totalColCount));\n\n // Header row\n const headerRow = table.querySelector('thead tr.column-header-row')\n ?? table.querySelector('thead tr:not(.column-group-row):not(.header-filter-row)');\n if (headerRow) {\n headerRow.setAttribute('role', 'row');\n headerRow.setAttribute('aria-rowindex', '1');\n const ths = headerRow.querySelectorAll('th');\n ths.forEach((th, i) => {\n th.setAttribute('role', 'columnheader');\n th.setAttribute('aria-colindex', String(i + 1));\n\n // Sort state\n if (th.classList.contains('sortable')) {\n const sortIndicator = th.querySelector('.sort-indicator.active');\n if (sortIndicator) {\n const text = sortIndicator.textContent?.trim() || '';\n if (text.includes('▲')) th.setAttribute('aria-sort', 'ascending');\n else if (text.includes('▼')) th.setAttribute('aria-sort', 'descending');\n } else {\n th.setAttribute('aria-sort', 'none');\n }\n }\n });\n }\n\n // Body rows\n const bodyRows = table.querySelectorAll('tbody tr:not(.virtual-spacer):not(.table-fill-row)');\n bodyRows.forEach((row, i) => {\n row.setAttribute('role', 'row');\n row.setAttribute('aria-rowindex', String(this.ariaRowOffset + i + 2)); // +2 for 1-based + header\n\n const isSelected = row.getAttribute('data-selected') === 'true';\n if (isSelected) row.setAttribute('aria-selected', 'true');\n else row.removeAttribute('aria-selected');\n\n const cells = row.querySelectorAll('td');\n cells.forEach((cell, j) => {\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('aria-colindex', String(j + 1));\n });\n });\n\n // Set roving tabindex on focused cell\n this.updateFocusIndicator();\n }\n\n /** Set focus position */\n setFocus(row: number, col: number) {\n this.focusedRow = row;\n this.focusedCol = col;\n this.updateFocusIndicator();\n }\n\n /** Get current focus position */\n getFocus(): { row: number; col: number } {\n return { row: this.focusedRow, col: this.focusedCol };\n }\n\n private updateFocusIndicator() {\n // A grid that was never keyboard-focused must not wear focus styling —\n // refresh() runs after every renderBody, and painting the default header\n // position (focusedRow=-1) put a stuck blue ring on every table at rest.\n if (!this.hasUserInteracted) return;\n\n const table = this.getTable();\n if (!table) return;\n\n // Remove previous focus\n table.querySelectorAll('[data-grid-focus]').forEach(el => {\n el.removeAttribute('data-grid-focus');\n el.removeAttribute('tabindex');\n });\n\n // Find target cell\n let targetCell: HTMLElement | null = null;\n if (this.focusedRow === -1) {\n // Header\n const ths = table.querySelectorAll('thead th');\n targetCell = ths[this.focusedCol] as HTMLElement;\n } else {\n // Resolve by logical data-index, not DOM position: under virtualization\n // only the current window is in the DOM.\n let row = this.getBodyRow(this.focusedRow);\n if (!row) {\n // Outside the rendered window — ask the host to scroll it into view,\n // then look again.\n this.options.ensureRowVisible?.(this.focusedRow);\n row = this.getBodyRow(this.focusedRow);\n }\n if (row) {\n const cells = row.querySelectorAll('td');\n targetCell = cells[this.focusedCol] as HTMLElement;\n }\n }\n\n if (targetCell) {\n targetCell.setAttribute('data-grid-focus', 'true');\n targetCell.setAttribute('tabindex', '0');\n }\n }\n\n private handleKeyDown(e: KeyboardEvent) {\n // Don't handle when editing\n if (this.options.isEditing()) return;\n\n // Let controls inside the grid keep their native keyboard contract. This\n // includes group/tree toggle buttons (Enter/Space must click them), row\n // checkboxes, links, and form controls rendered by custom cells. Inspect\n // the composed path so controls nested in another component's shadow root\n // are recognized too.\n const interactiveSelector = 'button, a, input, select, textarea, [contenteditable=\"true\"], snice-button, snice-checkbox, snice-input, snice-select';\n const fromInteractiveControl = e.composedPath().some((node) =>\n node instanceof Element && node.matches(interactiveSelector)\n );\n if (fromInteractiveControl) return;\n\n this.hasUserInteracted = true;\n\n const totalRows = this.totalRowCount;\n const totalColumns = this.totalColCount;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this.focusedRow = Math.min(totalRows - 1, this.focusedRow + 1);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'ArrowUp':\n e.preventDefault();\n this.focusedRow = Math.max(-1, this.focusedRow - 1);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'ArrowRight':\n e.preventDefault();\n this.focusedCol = Math.min(totalColumns - 1, this.focusedCol + 1);\n this.updateFocusIndicator();\n break;\n\n case 'ArrowLeft':\n e.preventDefault();\n this.focusedCol = Math.max(0, this.focusedCol - 1);\n this.updateFocusIndicator();\n break;\n\n case 'Home':\n e.preventDefault();\n if (e.ctrlKey) {\n this.focusedRow = -1;\n this.focusedCol = 0;\n } else {\n this.focusedCol = 0;\n }\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'End':\n e.preventDefault();\n if (e.ctrlKey) {\n this.focusedRow = totalRows - 1;\n this.focusedCol = totalColumns - 1;\n } else {\n this.focusedCol = totalColumns - 1;\n }\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'PageDown':\n e.preventDefault();\n this.focusedRow = Math.min(totalRows - 1, this.focusedRow + 10);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'PageUp':\n e.preventDefault();\n this.focusedRow = Math.max(0, this.focusedRow - 10);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case ' ':\n if (this.focusedRow >= 0) {\n e.preventDefault();\n this.options.onSelectionToggle?.(this.focusedRow);\n }\n break;\n\n case 'Enter':\n if (this.focusedRow >= 0 && this.focusedCol >= 0) {\n e.preventDefault();\n const row = this.getBodyRow(this.focusedRow);\n const cells = row?.querySelectorAll('td');\n const cell = cells?.[this.focusedCol];\n const columnKey = cell?.getAttribute('data-key') || '';\n this.options.onCellActivate?.(this.focusedRow, columnKey);\n }\n break;\n\n case 'a':\n if (e.ctrlKey || e.metaKey) {\n e.preventDefault();\n this.options.onSelectAll?.();\n }\n break;\n\n case 'Escape':\n // Let this bubble up to cancel editing\n break;\n }\n }\n\n private scrollIntoView() {\n const table = this.getTable();\n if (!table) return;\n\n const focusedEl = table.querySelector('[data-grid-focus]') as HTMLElement;\n if (focusedEl) {\n // Guard: layout APIs are not implemented in some non-browser DOMs.\n if (typeof focusedEl.scrollIntoView === 'function') {\n focusedEl.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n if (typeof focusedEl.focus === 'function') {\n focusedEl.focus({ preventScroll: true });\n }\n }\n }\n\n isAttached(): boolean {\n return this.attached;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table-keyboard.js","sources":["../../../../components/table/table-keyboard.ts"],"sourcesContent":["/**\n * Keyboard navigation and ARIA for snice-table.\n * Full WAI-ARIA grid pattern with arrow key navigation.\n */\n\nexport interface KeyboardOptions {\n /** The shadow root containing the table */\n shadowRoot: ShadowRoot;\n /** Callback when a cell is activated (Enter) */\n onCellActivate?: (rowIndex: number, columnKey: string) => void;\n /** Callback when selection changes via keyboard */\n onSelectionToggle?: (rowIndex: number) => void;\n /** Callback for select-all (Ctrl+A) */\n onSelectAll?: () => void;\n /** Tab navigation mode */\n tabMode: 'none' | 'content' | 'header' | 'all';\n /**\n * Total data rows. A getter callback keeps bounds live as the dataset is\n * loaded / filtered / paginated; a plain number is accepted for callers that\n * re-attach on every change.\n */\n totalRows: number | (() => number);\n /** Total columns. Getter callback or plain number — see `totalRows`. */\n totalColumns: number | (() => number);\n /**\n * Total rows exposed to assistive technology. This may exceed the keyboard\n * navigation count when the body also contains non-focusable group and\n * aggregate rows. Defaults to totalRows.\n */\n ariaRows?: number | (() => number);\n /** Zero-based logical offset of the first rendered body row (virtual grids). */\n ariaRowOffset?: number | (() => number);\n /**\n * Resolve a keyboard-navigation row to its rendered <tr>. Grouping/filtering\n * can make navigation order differ from raw data-index order.\n */\n getRowElement?: (rowIndex: number) => HTMLElement | null;\n /**\n * Bring a logical (data-index) row into the rendered window before focus\n * lands on it. Under virtualization only the current window is in the DOM;\n * the host scrolls the row's range into view and renders it synchronously.\n */\n ensureRowVisible?: (rowIndex: number) => void;\n /** Whether editing is active */\n isEditing: () => boolean;\n}\n\nexport class TableKeyboard {\n private options: KeyboardOptions;\n private focusedRow = -1; // -1 = header\n /** True after the first real user keydown — no focus paint at rest. */\n private hasUserInteracted = false;\n private focusedCol = 0;\n // EventListener-typed: ShadowRoot's TS event map only knows 'slotchange',\n // so a KeyboardEvent-typed handler fails the addEventListener overloads.\n private keyHandler: EventListener;\n private attached = false;\n\n constructor() {\n this.options = {} as KeyboardOptions;\n this.keyHandler = (e: Event) => this.handleKeyDown(e as KeyboardEvent);\n }\n\n attach(options: KeyboardOptions) {\n this.options = options;\n const root = options.shadowRoot;\n if (!root) return;\n\n // Delegate keydown at the shadow root — a stable ancestor that always\n // exists (even before the first render) and survives every <table> rebuild.\n // Every keydown inside the shadow tree bubbles here, so navigation works no\n // matter when data/columns arrive or how often renderHeader/renderBody\n // replace the table structure. Binding to the inner <table> instead meant a\n // table built after @ready was never listened to, and a rebuilt table\n // silently dropped the listener.\n root.addEventListener('keydown', this.keyHandler);\n this.attached = true;\n this.syncGridRole();\n this.applyARIA();\n }\n\n detach() {\n this.options.shadowRoot?.removeEventListener('keydown', this.keyHandler);\n this.attached = false;\n }\n\n /**\n * (Re)apply the grid identity to the current <table>: role=grid, a focusable\n * tabindex, and live row/col counts. Cheap, idempotent, and safe to call\n * before the table exists (no-op). A structural rebuild that swaps the\n * <table> node drops these attributes, so they must be re-applied afterwards.\n */\n private syncGridRole() {\n const table = this.getTable();\n if (!table) return;\n\n table.setAttribute('role', 'grid');\n table.setAttribute('tabindex', '0');\n table.setAttribute('aria-rowcount', String(this.ariaRowCount + this.headerRowCount));\n table.setAttribute('aria-colcount', String(this.totalColCount));\n }\n\n /**\n * Host hook: called after renderHeader/renderBody rebuild the table. The\n * delegated keydown listener lives on the shadow root so it survives on its\n * own; this restores the grid role and the roving tabindex on the focused\n * cell, both of which a wiped thead/tbody (or a swapped <table>) would lose.\n */\n refresh() {\n if (!this.attached) return;\n\n this.syncGridRole();\n this.applyARIA();\n }\n\n private getTable(): HTMLElement | null {\n return this.options.shadowRoot?.querySelector('table') ?? null;\n }\n\n /** Resolve a bound that may be a live getter or a static number. */\n private resolveBound(v: number | (() => number) | undefined): number {\n return typeof v === 'function' ? v() : (v ?? 0);\n }\n\n private get totalRowCount(): number {\n return this.resolveBound(this.options.totalRows);\n }\n\n private get totalColCount(): number {\n return this.resolveBound(this.options.totalColumns);\n }\n\n private get ariaRowCount(): number {\n return this.resolveBound(this.options.ariaRows ?? this.options.totalRows);\n }\n\n private get ariaRowOffset(): number {\n return Math.max(0, this.resolveBound(this.options.ariaRowOffset));\n }\n\n /**\n * Rows the header contributes to `aria-rowcount`, and the offset every body\n * row's `aria-rowindex` starts from. Normally 1 — but list mode paints a\n * card per row and leaves the thead empty, and claiming a header row that\n * does not exist would count one row too many and start the body at index 2\n * with nothing at index 1.\n */\n private get headerRowCount(): number {\n return this.getTable()?.querySelector('thead tr') ? 1 : 0;\n }\n\n /** Resolve a body row by its logical data-index (not its DOM position). */\n private getBodyRow(index: number): HTMLElement | null {\n if (this.options.getRowElement) return this.options.getRowElement(index);\n const table = this.getTable();\n if (!table) return null;\n return table.querySelector(`tbody tr[data-index=\"${index}\"]`);\n }\n\n /** Apply ARIA attributes to the grid */\n applyARIA() {\n const table = this.getTable();\n if (!table) return;\n\n const headerRows = this.headerRowCount;\n table.setAttribute('aria-rowcount', String(this.ariaRowCount + headerRows));\n table.setAttribute('aria-colcount', String(this.totalColCount));\n\n // Header row\n const headerRow = table.querySelector('thead tr.column-header-row')\n ?? table.querySelector('thead tr:not(.column-group-row):not(.header-filter-row)');\n if (headerRow) {\n headerRow.setAttribute('role', 'row');\n headerRow.setAttribute('aria-rowindex', '1');\n const ths = headerRow.querySelectorAll('th');\n ths.forEach((th, i) => {\n th.setAttribute('role', 'columnheader');\n th.setAttribute('aria-colindex', String(i + 1));\n\n // Sort state\n if (th.classList.contains('sortable')) {\n const sortIndicator = th.querySelector('.sort-indicator.active');\n if (sortIndicator) {\n const text = sortIndicator.textContent?.trim() || '';\n if (text.includes('▲')) th.setAttribute('aria-sort', 'ascending');\n else if (text.includes('▼')) th.setAttribute('aria-sort', 'descending');\n } else {\n th.setAttribute('aria-sort', 'none');\n }\n }\n });\n }\n\n // Body rows\n const bodyRows = table.querySelectorAll('tbody tr:not(.virtual-spacer):not(.table-fill-row)');\n bodyRows.forEach((row, i) => {\n row.setAttribute('role', 'row');\n row.setAttribute('aria-rowindex', String(this.ariaRowOffset + i + 1 + headerRows)); // 1-based, after the header\n\n const isSelected = row.getAttribute('data-selected') === 'true';\n if (isSelected) row.setAttribute('aria-selected', 'true');\n else row.removeAttribute('aria-selected');\n\n const cells = row.querySelectorAll('td');\n cells.forEach((cell, j) => {\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('aria-colindex', String(j + 1));\n });\n });\n\n // Set roving tabindex on focused cell\n this.updateFocusIndicator();\n }\n\n /** Set focus position */\n setFocus(row: number, col: number) {\n this.focusedRow = row;\n this.focusedCol = col;\n this.updateFocusIndicator();\n }\n\n /** Get current focus position */\n getFocus(): { row: number; col: number } {\n return { row: this.focusedRow, col: this.focusedCol };\n }\n\n private updateFocusIndicator() {\n // A grid that was never keyboard-focused must not wear focus styling —\n // refresh() runs after every renderBody, and painting the default header\n // position (focusedRow=-1) put a stuck blue ring on every table at rest.\n if (!this.hasUserInteracted) return;\n\n const table = this.getTable();\n if (!table) return;\n\n // Remove previous focus\n table.querySelectorAll('[data-grid-focus]').forEach(el => {\n el.removeAttribute('data-grid-focus');\n el.removeAttribute('tabindex');\n });\n\n // Find target cell\n let targetCell: HTMLElement | null = null;\n if (this.focusedRow === -1) {\n // Header\n const ths = table.querySelectorAll('thead th');\n targetCell = ths[this.focusedCol] as HTMLElement;\n } else {\n // Resolve by logical data-index, not DOM position: under virtualization\n // only the current window is in the DOM.\n let row = this.getBodyRow(this.focusedRow);\n if (!row) {\n // Outside the rendered window — ask the host to scroll it into view,\n // then look again.\n this.options.ensureRowVisible?.(this.focusedRow);\n row = this.getBodyRow(this.focusedRow);\n }\n if (row) {\n const cells = row.querySelectorAll('td');\n targetCell = cells[this.focusedCol] as HTMLElement;\n }\n }\n\n if (targetCell) {\n targetCell.setAttribute('data-grid-focus', 'true');\n targetCell.setAttribute('tabindex', '0');\n }\n }\n\n private handleKeyDown(e: KeyboardEvent) {\n // Don't handle when editing\n if (this.options.isEditing()) return;\n\n // Let controls inside the grid keep their native keyboard contract. This\n // includes group/tree toggle buttons (Enter/Space must click them), row\n // checkboxes, links, and form controls rendered by custom cells. Inspect\n // the composed path so controls nested in another component's shadow root\n // are recognized too.\n const interactiveSelector = 'button, a, input, select, textarea, [contenteditable=\"true\"], snice-button, snice-checkbox, snice-input, snice-select';\n const fromInteractiveControl = e.composedPath().some((node) =>\n node instanceof Element && node.matches(interactiveSelector)\n );\n if (fromInteractiveControl) return;\n\n this.hasUserInteracted = true;\n\n const totalRows = this.totalRowCount;\n const totalColumns = this.totalColCount;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this.focusedRow = Math.min(totalRows - 1, this.focusedRow + 1);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'ArrowUp':\n e.preventDefault();\n this.focusedRow = Math.max(-1, this.focusedRow - 1);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'ArrowRight':\n e.preventDefault();\n this.focusedCol = Math.min(totalColumns - 1, this.focusedCol + 1);\n this.updateFocusIndicator();\n break;\n\n case 'ArrowLeft':\n e.preventDefault();\n this.focusedCol = Math.max(0, this.focusedCol - 1);\n this.updateFocusIndicator();\n break;\n\n case 'Home':\n e.preventDefault();\n if (e.ctrlKey) {\n this.focusedRow = -1;\n this.focusedCol = 0;\n } else {\n this.focusedCol = 0;\n }\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'End':\n e.preventDefault();\n if (e.ctrlKey) {\n this.focusedRow = totalRows - 1;\n this.focusedCol = totalColumns - 1;\n } else {\n this.focusedCol = totalColumns - 1;\n }\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'PageDown':\n e.preventDefault();\n this.focusedRow = Math.min(totalRows - 1, this.focusedRow + 10);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case 'PageUp':\n e.preventDefault();\n this.focusedRow = Math.max(0, this.focusedRow - 10);\n this.updateFocusIndicator();\n this.scrollIntoView();\n break;\n\n case ' ':\n if (this.focusedRow >= 0) {\n e.preventDefault();\n this.options.onSelectionToggle?.(this.focusedRow);\n }\n break;\n\n case 'Enter':\n if (this.focusedRow >= 0 && this.focusedCol >= 0) {\n e.preventDefault();\n const row = this.getBodyRow(this.focusedRow);\n const cells = row?.querySelectorAll('td');\n const cell = cells?.[this.focusedCol];\n const columnKey = cell?.getAttribute('data-key') || '';\n this.options.onCellActivate?.(this.focusedRow, columnKey);\n }\n break;\n\n case 'a':\n if (e.ctrlKey || e.metaKey) {\n e.preventDefault();\n this.options.onSelectAll?.();\n }\n break;\n\n case 'Escape':\n // Let this bubble up to cancel editing\n break;\n }\n }\n\n private scrollIntoView() {\n const table = this.getTable();\n if (!table) return;\n\n const focusedEl = table.querySelector('[data-grid-focus]') as HTMLElement;\n if (focusedEl) {\n // Guard: layout APIs are not implemented in some non-browser DOMs.\n if (typeof focusedEl.scrollIntoView === 'function') {\n focusedEl.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n if (typeof focusedEl.focus === 'function') {\n focusedEl.focus({ preventScroll: true });\n }\n }\n }\n\n isAttached(): boolean {\n return this.attached;\n }\n}\n"],"names":[],"mappings":"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"}