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
@@ -236,6 +236,22 @@ export type PaginationMode = 'client' | 'server';
236
236
  * convention used across this file (attribute-reflectable, ergonomic to assign).
237
237
  */
238
238
  export type SelectionMode = 'none' | 'single' | 'multiple';
239
+ /**
240
+ * How the columns relate to the width of the frame they are painted in.
241
+ *
242
+ * `scroll` (the default, and the historical behavior): a column never goes
243
+ * below its `minWidth`. A frame too narrow for the minimums overflows into the
244
+ * frame's own horizontal scroller, so every column keeps a readable width and
245
+ * the reader scrolls to the ones that did not fit.
246
+ *
247
+ * `squish`: the frame's width is the hard constraint and `minWidth` is relaxed
248
+ * to a bare legibility floor. Every visible column shares the frame, cell
249
+ * content ellipsises, and the table NEVER scrolls horizontally — the rightmost
250
+ * column edge sits on the frame's inner edge at any width. Resizing is still
251
+ * allowed; the columns beside the dragged one give up (or take back) the
252
+ * difference so the total stays inside the frame.
253
+ */
254
+ export type ColumnFit = 'scroll' | 'squish';
239
255
  /**
240
256
  * The real public surface of `SniceTable` (components/table/snice-table.ts),
241
257
  * enumerated directly from the class — every `@property`, every plain public
@@ -298,6 +314,7 @@ export interface SniceTableElement extends HTMLElement {
298
314
  editable: boolean;
299
315
  editMode: 'cell' | 'row';
300
316
  density: 'compact' | 'standard' | 'comfortable';
317
+ columnFit: ColumnFit;
301
318
  columnResize: boolean;
302
319
  headerFilters: boolean;
303
320
  quickFilter: boolean;
@@ -1,4 +1,18 @@
1
1
  import type { ColumnDefinition } from './snice-table.types';
2
+ /** The alignment a column type renders with when the column declares none. */
3
+ export declare function defaultCellAlign(type?: string): 'left' | 'center' | 'right';
4
+ /**
5
+ * The value a typed cell holds when the row has nothing to show — the row field
6
+ * is null, or the row never carried the field at all.
7
+ *
8
+ * Each typed cell owns its value semantics: `false` for boolean, `0` for
9
+ * rating/progress/duration/filesize, `null` for JSON, `''` for the text-shaped
10
+ * rest. Handing every empty value the same `''` instead lets the cell's typed
11
+ * attribute converter re-read that empty string as a value of the wrong
12
+ * meaning — `''` on a Boolean property is an attribute that is *present*, i.e.
13
+ * `true`, and `''` on an Object property parses to `{}`.
14
+ */
15
+ export declare function emptyCellValue(type?: string): any;
2
16
  /**
3
17
  * Give every standalone specialized table cell the common ColumnDefinition
4
18
  * presentation contract. The observer reapplies after Snice's differential
@@ -10,10 +24,9 @@ import type { ColumnDefinition } from './snice-table.types';
10
24
  * fallback, so `formatter` wins. `formatterOverride` says the cell does NOT
11
25
  * apply `column.formatter` itself, so this module owns both formatters for it.
12
26
  *
13
- * A host marked `formatted` was handed its display text as `value` by the
14
- * table, which already resolved the pipeline. Its content is that text
15
- * verbatim the marker exists so a rating/progress/link/tag cell, which
16
- * renders structure rather than text, still shows the formatted string.
27
+ * Table-rendered cells never arrive here with a formatter: `createCellElement`
28
+ * resolves the pipeline once and hands the text cell the formatted string. This
29
+ * path serves the declarative and standalone cells, which own a full column.
17
30
  */
18
31
  export declare function installCellPresentation(host: HTMLElement & {
19
32
  column?: ColumnDefinition | null;
@@ -6,6 +6,37 @@ const presentationProperties = [
6
6
  'fontSize',
7
7
  'textDecoration',
8
8
  ];
9
+ /** The alignment a column type renders with when the column declares none. */
10
+ function defaultCellAlign(type) {
11
+ if (['number', 'currency', 'percent', 'percentage', 'duration', 'filesize',
12
+ 'accounting', 'scientific', 'fraction'].includes(type || ''))
13
+ return 'right';
14
+ if (['boolean', 'rating', 'image'].includes(type || ''))
15
+ return 'center';
16
+ return 'left';
17
+ }
18
+ /**
19
+ * The value a typed cell holds when the row has nothing to show — the row field
20
+ * is null, or the row never carried the field at all.
21
+ *
22
+ * Each typed cell owns its value semantics: `false` for boolean, `0` for
23
+ * rating/progress/duration/filesize, `null` for JSON, `''` for the text-shaped
24
+ * rest. Handing every empty value the same `''` instead lets the cell's typed
25
+ * attribute converter re-read that empty string as a value of the wrong
26
+ * meaning — `''` on a Boolean property is an attribute that is *present*, i.e.
27
+ * `true`, and `''` on an Object property parses to `{}`.
28
+ */
29
+ function emptyCellValue(type) {
30
+ switch (type) {
31
+ case 'boolean': return false;
32
+ case 'rating':
33
+ case 'progress':
34
+ case 'duration':
35
+ case 'filesize': return 0;
36
+ case 'json': return null;
37
+ default: return '';
38
+ }
39
+ }
9
40
  const installed = new WeakMap();
10
41
  const appliedClasses = new WeakMap();
11
42
  /**
@@ -19,10 +50,9 @@ const appliedClasses = new WeakMap();
19
50
  * fallback, so `formatter` wins. `formatterOverride` says the cell does NOT
20
51
  * apply `column.formatter` itself, so this module owns both formatters for it.
21
52
  *
22
- * A host marked `formatted` was handed its display text as `value` by the
23
- * table, which already resolved the pipeline. Its content is that text
24
- * verbatim the marker exists so a rating/progress/link/tag cell, which
25
- * renders structure rather than text, still shows the formatted string.
53
+ * Table-rendered cells never arrive here with a formatter: `createCellElement`
54
+ * resolves the pipeline once and hands the text cell the formatted string. This
55
+ * path serves the declarative and standalone cells, which own a full column.
26
56
  */
27
57
  function installCellPresentation(host, formatterOverride = false) {
28
58
  if (installed.has(host))
@@ -75,12 +105,8 @@ function installCellPresentation(host, formatterOverride = false) {
75
105
  const formatter = column.formatter
76
106
  ? (formatterOverride ? column.formatter : null)
77
107
  : column.valueFormatter;
78
- // Pre-formatted hosts read the attribute, not the property: a cell whose
79
- // value property is typed (boolean, number) coerces the display string.
80
- const display = host.hasAttribute('formatted')
81
- ? (host.getAttribute('value') ?? '')
82
- : (formatter ? String(formatter(host.value, host.rowData) ?? '') : null);
83
- if (display !== null) {
108
+ if (formatter) {
109
+ const display = String(formatter(host.value, host.rowData) ?? '');
84
110
  const content = host.shadowRoot?.querySelector('[part~="content"]');
85
111
  if (content && (content.textContent !== display || content.childElementCount > 0)) {
86
112
  content.textContent = display;
@@ -101,5 +127,5 @@ function installCellPresentation(host, formatterOverride = false) {
101
127
  queueMicrotask(sync);
102
128
  }
103
129
 
104
- export { installCellPresentation };
130
+ export { defaultCellAlign, emptyCellValue, installCellPresentation };
105
131
  //# sourceMappingURL=table-cell-presentation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"table-cell-presentation.js","sources":["../../../../components/table/table-cell-presentation.ts"],"sourcesContent":["import type { ColumnDefinition, CellStyle } from './snice-table.types';\n\nconst presentationProperties: Array<keyof CellStyle> = [\n 'backgroundColor',\n 'color',\n 'fontWeight',\n 'fontStyle',\n 'fontSize',\n 'textDecoration',\n];\n\nconst installed = new WeakMap<HTMLElement, MutationObserver>();\nconst appliedClasses = new WeakMap<HTMLElement, string[]>();\n\n/**\n * Give every standalone specialized table cell the common ColumnDefinition\n * presentation contract. The observer reapplies after Snice's differential\n * renderer changes shadow content; equality guards prevent mutation loops.\n *\n * Display formatters\n * ------------------\n * `formatter` is the column's display formatter and `valueFormatter` its\n * fallback, so `formatter` wins. `formatterOverride` says the cell does NOT\n * apply `column.formatter` itself, so this module owns both formatters for it.\n *\n * A host marked `formatted` was handed its display text as `value` by the\n * table, which already resolved the pipeline. Its content is that text\n * verbatim — the marker exists so a rating/progress/link/tag cell, which\n * renders structure rather than text, still shows the formatted string.\n */\nexport function installCellPresentation(\n host: HTMLElement & { column?: ColumnDefinition | null; value?: any; rowData?: any },\n formatterOverride = false,\n) {\n if (installed.has(host)) return;\n let observer: MutationObserver | null = null;\n let syncing = false;\n\n const sync = () => {\n if (syncing) return;\n syncing = true;\n observer?.disconnect();\n try {\n const column = host.column;\n for (const className of appliedClasses.get(host) || []) host.classList.remove(className);\n appliedClasses.set(host, []);\n for (const property of presentationProperties) (host.style as any)[property] = '';\n host.removeAttribute('title');\n if (!column) return;\n\n const applyStyle = (style?: CellStyle) => {\n if (!style) return;\n for (const property of presentationProperties) {\n const value = style[property];\n if (value) (host.style as any)[property] = value;\n }\n };\n\n applyStyle(column.style);\n for (const rule of column.conditionalFormats || []) {\n if (!rule.condition(host.value, host.rowData)) continue;\n applyStyle(rule.style);\n if (rule.className) {\n host.classList.add(rule.className);\n appliedClasses.set(host, [rule.className]);\n }\n break;\n }\n\n if (column.tooltip) {\n host.title = typeof column.tooltip === 'function'\n ? column.tooltip(host.value, host.rowData)\n : (host.value == null ? '' : String(host.value));\n }\n\n // `formatter` beats `valueFormatter`. A cell that applies `formatter`\n // itself has already painted it, so the fallback must not overwrite that\n // content — only a formatter-less column falls through to valueFormatter.\n const formatter = column.formatter\n ? (formatterOverride ? column.formatter : null)\n : column.valueFormatter;\n // Pre-formatted hosts read the attribute, not the property: a cell whose\n // value property is typed (boolean, number) coerces the display string.\n const display = host.hasAttribute('formatted')\n ? (host.getAttribute('value') ?? '')\n : (formatter ? String(formatter(host.value, host.rowData) ?? '') : null);\n if (display !== null) {\n const content = host.shadowRoot?.querySelector('[part~=\"content\"]') as HTMLElement | null;\n if (content && (content.textContent !== display || content.childElementCount > 0)) {\n content.textContent = display;\n }\n }\n } finally {\n syncing = false;\n if (observer && host.shadowRoot) {\n observer.observe(host.shadowRoot, { childList: true, subtree: true, characterData: true });\n }\n }\n };\n\n observer = new MutationObserver(sync);\n if (host.shadowRoot) observer.observe(host.shadowRoot, { childList: true, subtree: true, characterData: true });\n installed.set(host, observer);\n queueMicrotask(sync);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table-cell-presentation.js","sources":["../../../../components/table/table-cell-presentation.ts"],"sourcesContent":["import type { ColumnDefinition, CellStyle } from './snice-table.types';\n\nconst presentationProperties: Array<keyof CellStyle> = [\n 'backgroundColor',\n 'color',\n 'fontWeight',\n 'fontStyle',\n 'fontSize',\n 'textDecoration',\n];\n\n/** The alignment a column type renders with when the column declares none. */\nexport function defaultCellAlign(type?: string): 'left' | 'center' | 'right' {\n if (['number', 'currency', 'percent', 'percentage', 'duration', 'filesize',\n 'accounting', 'scientific', 'fraction'].includes(type || '')) return 'right';\n if (['boolean', 'rating', 'image'].includes(type || '')) return 'center';\n return 'left';\n}\n\n/**\n * The value a typed cell holds when the row has nothing to show — the row field\n * is null, or the row never carried the field at all.\n *\n * Each typed cell owns its value semantics: `false` for boolean, `0` for\n * rating/progress/duration/filesize, `null` for JSON, `''` for the text-shaped\n * rest. Handing every empty value the same `''` instead lets the cell's typed\n * attribute converter re-read that empty string as a value of the wrong\n * meaning — `''` on a Boolean property is an attribute that is *present*, i.e.\n * `true`, and `''` on an Object property parses to `{}`.\n */\nexport function emptyCellValue(type?: string): any {\n switch (type) {\n case 'boolean': return false;\n case 'rating':\n case 'progress':\n case 'duration':\n case 'filesize': return 0;\n case 'json': return null;\n default: return '';\n }\n}\n\nconst installed = new WeakMap<HTMLElement, MutationObserver>();\nconst appliedClasses = new WeakMap<HTMLElement, string[]>();\n\n/**\n * Give every standalone specialized table cell the common ColumnDefinition\n * presentation contract. The observer reapplies after Snice's differential\n * renderer changes shadow content; equality guards prevent mutation loops.\n *\n * Display formatters\n * ------------------\n * `formatter` is the column's display formatter and `valueFormatter` its\n * fallback, so `formatter` wins. `formatterOverride` says the cell does NOT\n * apply `column.formatter` itself, so this module owns both formatters for it.\n *\n * Table-rendered cells never arrive here with a formatter: `createCellElement`\n * resolves the pipeline once and hands the text cell the formatted string. This\n * path serves the declarative and standalone cells, which own a full column.\n */\nexport function installCellPresentation(\n host: HTMLElement & { column?: ColumnDefinition | null; value?: any; rowData?: any },\n formatterOverride = false,\n) {\n if (installed.has(host)) return;\n let observer: MutationObserver | null = null;\n let syncing = false;\n\n const sync = () => {\n if (syncing) return;\n syncing = true;\n observer?.disconnect();\n try {\n const column = host.column;\n for (const className of appliedClasses.get(host) || []) host.classList.remove(className);\n appliedClasses.set(host, []);\n for (const property of presentationProperties) (host.style as any)[property] = '';\n host.removeAttribute('title');\n if (!column) return;\n\n const applyStyle = (style?: CellStyle) => {\n if (!style) return;\n for (const property of presentationProperties) {\n const value = style[property];\n if (value) (host.style as any)[property] = value;\n }\n };\n\n applyStyle(column.style);\n for (const rule of column.conditionalFormats || []) {\n if (!rule.condition(host.value, host.rowData)) continue;\n applyStyle(rule.style);\n if (rule.className) {\n host.classList.add(rule.className);\n appliedClasses.set(host, [rule.className]);\n }\n break;\n }\n\n if (column.tooltip) {\n host.title = typeof column.tooltip === 'function'\n ? column.tooltip(host.value, host.rowData)\n : (host.value == null ? '' : String(host.value));\n }\n\n // `formatter` beats `valueFormatter`. A cell that applies `formatter`\n // itself has already painted it, so the fallback must not overwrite that\n // content — only a formatter-less column falls through to valueFormatter.\n const formatter = column.formatter\n ? (formatterOverride ? column.formatter : null)\n : column.valueFormatter;\n if (formatter) {\n const display = String(formatter(host.value, host.rowData) ?? '');\n const content = host.shadowRoot?.querySelector('[part~=\"content\"]') as HTMLElement | null;\n if (content && (content.textContent !== display || content.childElementCount > 0)) {\n content.textContent = display;\n }\n }\n } finally {\n syncing = false;\n if (observer && host.shadowRoot) {\n observer.observe(host.shadowRoot, { childList: true, subtree: true, characterData: true });\n }\n }\n };\n\n observer = new MutationObserver(sync);\n if (host.shadowRoot) observer.observe(host.shadowRoot, { childList: true, subtree: true, characterData: true });\n installed.set(host, observer);\n queueMicrotask(sync);\n}\n"],"names":[],"mappings":"AAEA,MAAM,sBAAsB,GAA2B;IACrD,iBAAiB;IACjB,OAAO;IACP,YAAY;IACZ,WAAW;IACX,UAAU;IACV,gBAAgB;CACjB;AAED;AACM,SAAU,gBAAgB,CAAC,IAAa,EAAA;IAC5C,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU;QACrE,YAAY,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;AAAE,QAAA,OAAO,OAAO;AACjF,IAAA,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;AAAE,QAAA,OAAO,QAAQ;AACxE,IAAA,OAAO,MAAM;AACf;AAEA;;;;;;;;;;AAUG;AACG,SAAU,cAAc,CAAC,IAAa,EAAA;IAC1C,QAAQ,IAAI;AACV,QAAA,KAAK,SAAS,EAAE,OAAO,KAAK;AAC5B,QAAA,KAAK,QAAQ;AACb,QAAA,KAAK,UAAU;AACf,QAAA,KAAK,UAAU;AACf,QAAA,KAAK,UAAU,EAAE,OAAO,CAAC;AACzB,QAAA,KAAK,MAAM,EAAE,OAAO,IAAI;AACxB,QAAA,SAAS,OAAO,EAAE;;AAEtB;AAEA,MAAM,SAAS,GAAG,IAAI,OAAO,EAAiC;AAC9D,MAAM,cAAc,GAAG,IAAI,OAAO,EAAyB;AAE3D;;;;;;;;;;;;;;AAcG;SACa,uBAAuB,CACrC,IAAoF,EACpF,iBAAiB,GAAG,KAAK,EAAA;AAEzB,IAAA,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE;IACzB,IAAI,QAAQ,GAA4B,IAAI;IAC5C,IAAI,OAAO,GAAG,KAAK;IAEnB,MAAM,IAAI,GAAG,MAAK;AAChB,QAAA,IAAI,OAAO;YAAE;QACb,OAAO,GAAG,IAAI;QACd,QAAQ,EAAE,UAAU,EAAE;AACtB,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM;YAC1B,KAAK,MAAM,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE;AAAE,gBAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC;AACxF,YAAA,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5B,KAAK,MAAM,QAAQ,IAAI,sBAAsB;AAAG,gBAAA,IAAI,CAAC,KAAa,CAAC,QAAQ,CAAC,GAAG,EAAE;AACjF,YAAA,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;AAC7B,YAAA,IAAI,CAAC,MAAM;gBAAE;AAEb,YAAA,MAAM,UAAU,GAAG,CAAC,KAAiB,KAAI;AACvC,gBAAA,IAAI,CAAC,KAAK;oBAAE;AACZ,gBAAA,KAAK,MAAM,QAAQ,IAAI,sBAAsB,EAAE;AAC7C,oBAAA,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC;AAC7B,oBAAA,IAAI,KAAK;AAAG,wBAAA,IAAI,CAAC,KAAa,CAAC,QAAQ,CAAC,GAAG,KAAK;gBAClD;AACF,YAAA,CAAC;AAED,YAAA,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC;YACxB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,kBAAkB,IAAI,EAAE,EAAE;AAClD,gBAAA,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;oBAAE;AAC/C,gBAAA,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;AACtB,gBAAA,IAAI,IAAI,CAAC,SAAS,EAAE;oBAClB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;oBAClC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC5C;gBACA;YACF;AAEA,YAAA,IAAI,MAAM,CAAC,OAAO,EAAE;gBAClB,IAAI,CAAC,KAAK,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK;AACrC,sBAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO;uBACtC,IAAI,CAAC,KAAK,IAAI,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpD;;;;AAKA,YAAA,MAAM,SAAS,GAAG,MAAM,CAAC;AACvB,mBAAG,iBAAiB,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI;AAC9C,kBAAE,MAAM,CAAC,cAAc;YACzB,IAAI,SAAS,EAAE;AACb,gBAAA,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBACjE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,mBAAmB,CAAuB;AACzF,gBAAA,IAAI,OAAO,KAAK,OAAO,CAAC,WAAW,KAAK,OAAO,IAAI,OAAO,CAAC,iBAAiB,GAAG,CAAC,CAAC,EAAE;AACjF,oBAAA,OAAO,CAAC,WAAW,GAAG,OAAO;gBAC/B;YACF;QACF;gBAAU;YACR,OAAO,GAAG,KAAK;AACf,YAAA,IAAI,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE;gBAC/B,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;YAC5F;QACF;AACF,IAAA,CAAC;AAED,IAAA,QAAQ,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC;IACrC,IAAI,IAAI,CAAC,UAAU;QAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;AAC/G,IAAA,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC;IAC7B,cAAc,CAAC,IAAI,CAAC;AACtB;;;;"}
@@ -2,7 +2,9 @@
2
2
  * Column management for snice-table.
3
3
  * Handles: flex width, resize, auto-size, visibility, ordering, pinning, groups.
4
4
  */
5
- import type { ColumnDefinition } from './snice-table.types';
5
+ import type { ColumnDefinition, ColumnFit } from './snice-table.types';
6
+ /** Padding + border a cell spends before any content fits inside it. */
7
+ export declare function horizontalCellChrome(cell: HTMLElement): number;
6
8
  export interface ColumnState {
7
9
  key: string;
8
10
  width: number;
@@ -16,6 +18,13 @@ export interface ColumnState {
16
18
  reorderable: boolean;
17
19
  hideable: boolean;
18
20
  pinnable: boolean;
21
+ /**
22
+ * Someone chose this width on purpose — the column definition declared one,
23
+ * the user dragged the edge, or auto-size measured the content. Only columns
24
+ * that never did participate in `fitVisibleColumns`, so a deliberate width is
25
+ * never quietly overwritten by the container's arithmetic.
26
+ */
27
+ authored: boolean;
19
28
  }
20
29
  export interface ColumnGroup {
21
30
  label: string;
@@ -30,8 +39,71 @@ export declare class TableColumnManager {
30
39
  private resizingKey;
31
40
  private resizeOverlay;
32
41
  private tableElement;
33
- /** Initialize column states from column definitions */
42
+ private fitMode;
43
+ /**
44
+ * The content budget the last `squish` fit shared out. A drag-resize needs it
45
+ * to know how much width the columns beside the dragged one have to give up
46
+ * (or take back) for the total to stay on the frame.
47
+ */
48
+ private squishBudget;
49
+ /** Fallback content width for a column nobody sized. */
50
+ static readonly DEFAULT_WIDTH = 150;
51
+ /**
52
+ * The floor `squish` relaxes `minWidth` down to. Not zero: a column narrower
53
+ * than this shows no glyph at all, only its ellipsis, and stops being a
54
+ * column the reader can identify. A frame too narrow even for this is past
55
+ * what the mode can honour — see `fitVisibleColumns`.
56
+ */
57
+ static readonly SQUISH_MIN_WIDTH = 24;
58
+ /**
59
+ * Which fitting policy `fitVisibleColumns` (and a drag-resize) follows.
60
+ * Owned by the table's `columnFit` property; see the `ColumnFit` docs.
61
+ */
62
+ setFitMode(mode: ColumnFit): void;
63
+ getFitMode(): ColumnFit;
64
+ /**
65
+ * Routine resync, run on every render: make sure a state exists for each
66
+ * declared column and refresh the flags that are pure declaration (min/max,
67
+ * resizable, reorderable, hideable, pinnable, flex).
68
+ *
69
+ * Deliberately PRESERVES the interaction-owned members of an existing state —
70
+ * width, visible, pinned, order — because renderBody() calls this on every
71
+ * paint, and re-applying the declaration here would undo a drag-resize, a
72
+ * hidden column or a user reorder on the very next render. Re-applying the
73
+ * declaration is applyConfiguration()'s job, and only a `columns` assignment
74
+ * calls that.
75
+ */
34
76
  initialize(columns: ColumnDefinition[], tableEl: HTMLElement): void;
77
+ /**
78
+ * A `columns` assignment (or setColumns()) delivered a NEW CONFIGURATION.
79
+ *
80
+ * The declaration is the source of truth here: painted order, `pinned`,
81
+ * `width` and visibility all come from the array just assigned, and states for
82
+ * columns the new array no longer declares are DROPPED. Without this the first
83
+ * configuration the table ever saw won permanently — a re-ordered array kept
84
+ * the original order, a newly declared pin or width was ignored, and a column
85
+ * hidden through setColumnVisible() could never be brought back
86
+ * (MATRIX-columns-2/3/4/6).
87
+ *
88
+ * The cost is that the assignment also discards user interaction state (a
89
+ * drag-resize, a manual reorder, a hidden column) for the columns it
90
+ * re-declares. That is the intended reading of the contract, and it applies to
91
+ * EVERY assignment: `columns` declares `hasChanged: () => true`, so handing
92
+ * back the array the table already holds — after mutating it, or unchanged —
93
+ * re-applies the declaration exactly like a fresh array would. Mutating the
94
+ * published definitions is therefore NOT an escape hatch; the only way to keep
95
+ * user column state is to not assign `columns` at all and drive the change
96
+ * through the column APIs instead (setColumnVisible / moveColumn / pinColumn /
97
+ * unpinColumn), which mutate the state map in place. Renders triggered by this
98
+ * go through the table's coalescing queue, so re-applying costs no extra paint.
99
+ */
100
+ applyConfiguration(columns: ColumnDefinition[], tableEl: HTMLElement): void;
101
+ /**
102
+ * One place that turns a column definition into a state. `existing` supplies
103
+ * the interaction-owned members on the resync path; omitting it yields the
104
+ * pure declared state applyConfiguration() wants.
105
+ */
106
+ private buildState;
35
107
  private parseWidth;
36
108
  /** Get ordered, visible columns */
37
109
  getVisibleColumns(): ColumnState[];
@@ -45,6 +117,63 @@ export declare class TableColumnManager {
45
117
  getUnpinned(): ColumnState[];
46
118
  /** Compute flex widths based on available space */
47
119
  computeFlexWidths(availableWidth: number): Map<string, number>;
120
+ /**
121
+ * Fit the columns nobody sized into `availableWidth` (the frame's content
122
+ * box), reserving `chromePerColumn` for each column's own padding + border.
123
+ *
124
+ * A column width is a CONTENT width — the rendered column is that plus its
125
+ * chrome — so the budget has to come off the top or the fitted table lands a
126
+ * few pixels wider than the frame it was fitted to.
127
+ *
128
+ * Declared and user-chosen widths are spent first and never rewritten: a
129
+ * table that asked for `width: '80'` keeps 80 whatever the frame does. What
130
+ * is left is split evenly and clamped to each column's [minWidth, maxWidth],
131
+ * so a frame too narrow for the minimums overflows into the frame's own
132
+ * scroller instead of collapsing the columns to nothing.
133
+ *
134
+ * Returns whether any width actually moved, so callers can skip a repaint.
135
+ *
136
+ * Under `column-fit="squish"` the frame stops being a suggestion: see
137
+ * `squishVisibleColumns`.
138
+ */
139
+ fitVisibleColumns(availableWidth: number, chromePerColumn?: number): boolean;
140
+ /**
141
+ * The `squish` policy: the columns add up to the frame, whatever that costs.
142
+ *
143
+ * `minWidth` is a readability preference, and this mode has already decided
144
+ * the frame outranks it, so the only floor left is SQUISH_MIN_WIDTH. Two
145
+ * cases:
146
+ *
147
+ * - There is an unsized column left to absorb the difference, and the
148
+ * authored widths still leave room for it. Those are honoured exactly (a
149
+ * column that asked for 80, or that the user dragged to 80, keeps 80) and
150
+ * the rest is split evenly between the unsized ones.
151
+ *
152
+ * - Every column is authored, or the authored widths alone already overflow
153
+ * the frame. There is nothing left to give, so `scroll`'s escape hatch
154
+ * (overflow into the frame's scroller) is exactly what this mode exists to
155
+ * prevent: every column is scaled by the same factor instead. Scaling is a
156
+ * fixed point — the second pass computes a factor of 1 — so this settles
157
+ * on the first render rather than creeping on every repaint.
158
+ *
159
+ * Either way the remainder from integer division is handed back to the widest
160
+ * columns that are free to take it, so the widths sum to the budget EXACTLY
161
+ * and the rightmost column edge lands on the frame's inner edge instead of a
162
+ * few pixels short of it.
163
+ */
164
+ private squishVisibleColumns;
165
+ /**
166
+ * A drag-resize under `squish` moved one column; the others pay for it.
167
+ *
168
+ * The dragged column is the anchor — it keeps the width the pointer asked
169
+ * for, capped so the remaining columns can still sit on their floor — and the
170
+ * rest are scaled to fill exactly what is left of the frame. Without this a
171
+ * drag would simply push the trailing column out of a frame that is not
172
+ * allowed to scroll.
173
+ */
174
+ private rebalanceSquish;
175
+ /** Push the current widths onto the painted header and body cells. */
176
+ private writeWidths;
48
177
  /** Apply computed widths to <col> or <th> elements */
49
178
  applyWidths(headerRow: HTMLElement, widths: Map<string, number>): void;
50
179
  /** Start column resize. Call from mousedown on resize handle. */
@@ -53,6 +182,28 @@ export declare class TableColumnManager {
53
182
  autoSizeColumn(columnKey: string, tbody: HTMLElement): void;
54
183
  /** Auto-size all columns */
55
184
  autoSizeAll(tbody: HTMLElement): void;
185
+ /** Slack added to a measured content width so the last glyph never sits on
186
+ * the clip edge (and a sortable header keeps room for its chevron). */
187
+ private static readonly AUTO_SIZE_SLACK;
188
+ /**
189
+ * Measure the columns' content and give each the width it actually needs.
190
+ *
191
+ * A table cell's `scrollWidth` is NOT its content width: for content that
192
+ * fits, it reports the padding box — the width the column already has. The
193
+ * previous implementation took that maximum and added 16px, so every
194
+ * "auto-size" pass could only GROW the column, by a chrome's worth each
195
+ * time, until the table outgrew its own frame. Once it did, the right-pinned
196
+ * column (`position: sticky`) parked over the columns beside it, and the
197
+ * columns behind it read as deleted.
198
+ *
199
+ * The width a column NEEDS is a layout question, so ask layout: drop the
200
+ * table into content-driven sizing (`table-layout: auto` at `max-content`)
201
+ * with the measured columns' imposed widths removed, and read back the width
202
+ * each header settles at. `max-content` also means no wrapping, so a long
203
+ * cell reports its full single-line extent even though the painted column
204
+ * ellipsises it. Everything is restored in the same task, before paint.
205
+ */
206
+ private autoSizeColumns;
56
207
  setColumnVisible(key: string, visible: boolean): void;
57
208
  showAllColumns(): void;
58
209
  hideAllColumns(): void;