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
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.7.0
2
+ * snice v7.8.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -237,7 +237,7 @@ var SniceCalendar = (function (exports, snice) {
237
237
  return _classThis;
238
238
  })();
239
239
 
240
- var cssContent = ":host([hidden]){display:none}:host{display:block;font-family:var(--snice-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);contain:layout style}.calendar{width:100%;max-width:37.5rem}.calendar__header{font-family:var(--snice-font-family-display, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);display:flex;align-items:center;justify-content:space-between;padding:var(--snice-spacing-md,1rem);background:var(--snice-color-surface,rgb(255 255 255));border-bottom:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar__title{font-family:var(--snice-font-family-display, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);font-size:var(--snice-heading-md, 1.125rem);font-weight:var(--snice-font-weight-semibold,600);color:var(--snice-color-text,rgb(23 23 23))}.calendar__nav{display:flex;gap:var(--snice-spacing-xs,.5rem)}.calendar__nav-button{padding:var(--snice-spacing-xs,.5rem) var(--snice-spacing-sm,.75rem);border:1px solid var(--snice-color-border,rgb(226 226 226));background:var(--snice-color-surface,rgb(255 255 255));border-radius:var(--snice-border-radius-md,.25rem);cursor:pointer;transition:background-color var(--snice-transition-fast, 150ms) ease,border-color var(--snice-transition-fast, 150ms) ease;font-size:var(--snice-font-size-sm, .875rem);color:var(--snice-color-text,rgb(23 23 23));font-family:inherit}.calendar__nav-button:hover{background:var(--snice-color-surface-container-low,rgb(250 250 250));border-color:var(--snice-color-border-hover,rgb(204 204 204))}.calendar__nav-button:focus-visible{outline:0;box-shadow:0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__grid{display:grid;grid-template-columns:repeat(7,1fr);background:var(--snice-color-surface,rgb(255 255 255));container-type:inline-size}.calendar__weekday{padding:var(--snice-spacing-sm,.75rem);text-align:center;font-size:var(--snice-font-size-xs, .75rem);font-weight:var(--snice-font-weight-semibold,600);color:var(--snice-color-text-secondary,rgb(82 82 82));text-transform:uppercase;letter-spacing:var(--snice-tracking-widest, .1em);border-bottom:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar__day{box-sizing:border-box;min-width:0;min-height:max(calc(100cqw / 7),3rem,calc(2.125rem + var(--calendar-week-lanes,0) * 1.375rem + var(--calendar-week-more,0rem)));padding:var(--snice-spacing-xs,.5rem);border:1px solid var(--snice-color-border,rgb(226 226 226));border-top:none;border-left:none;cursor:pointer;transition:background-color var(--snice-transition-fast, 150ms) ease;position:relative;background:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text,rgb(23 23 23));overflow:hidden}.calendar__day:hover{background:var(--snice-color-surface-container-low,rgb(250 250 250))}.calendar__day:focus-visible{outline:0;box-shadow:inset 0 0 0 2px var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__day:nth-child(7n+1){border-left:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar__day--stretch{min-height:max(3rem,calc(2.125rem + var(--calendar-week-lanes,0) * 1.375rem + var(--calendar-week-more,0rem)))}.calendar__day--static{cursor:default}.calendar__day--static:hover{background:var(--snice-color-surface,rgb(255 255 255))}.calendar__day--static.calendar__day--today:hover{background:var(--snice-color-primary-subtle,rgb(239 246 255))}.calendar__day--other-month{background:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text-tertiary,rgb(115 115 115));opacity:.5}.calendar__day--other-month:hover{opacity:.7}.calendar__day--today{background:var(--snice-color-primary-subtle,rgb(239 246 255))}.calendar__day--today:hover{background:var(--snice-color-primary-subtle-hover,rgb(219 234 254))}.calendar__day--selected{background:var(--snice-color-primary,rgb(37 99 235));color:var(--snice-color-text-inverse,rgb(250 250 250))}.calendar__day--selected:hover{background:var(--snice-color-primary-hover,rgb(29 78 216))}.calendar__day--disabled{background:var(--snice-color-surface-container,rgb(241 241 241));color:var(--snice-color-text-tertiary,rgb(115 115 115));opacity:.5;cursor:not-allowed}.calendar__day--disabled:hover{background:var(--snice-color-surface-container,rgb(241 241 241))}.calendar__day-number{font-size:var(--snice-font-size-sm, .875rem);font-weight:var(--snice-font-weight-medium,500);margin-bottom:var(--snice-spacing-2xs,.25rem)}.calendar__day--selected .calendar__day-number{color:var(--snice-color-text-inverse,rgb(250 250 250))}.calendar__event-bar{display:flex;align-items:center;gap:var(--snice-spacing-3xs,.125rem);align-self:start;margin-top:calc(1.875rem + var(--calendar-event-lane,0) * 1.375rem);margin-inline:var(--snice-spacing-3xs,0.125rem);height:1.125rem;line-height:1.125rem;font-size:var(--snice-font-size-2xs, .625rem);padding:0 var(--snice-spacing-2xs,.25rem);border-radius:var(--snice-border-radius-sm,.125rem);background:var(--snice-color-primary,rgb(37 99 235));color:var(--snice-color-text-inverse,rgb(250 250 250));white-space:nowrap;overflow:hidden;cursor:pointer;z-index:1;transition:opacity var(--snice-transition-fast, 150ms) ease}.calendar__event-title{overflow:hidden;text-overflow:ellipsis;min-width:0}.calendar__event-avatar{flex:none;--avatar-size:0.875rem;font-size:.375rem;border-radius:50%;overflow:hidden}.calendar__popover{position:fixed;z-index:var(--snice-z-floating,1000);min-width:14rem;max-width:22rem;padding:var(--snice-spacing-sm,.75rem);border:1px solid var(--snice-color-border,rgb(226 226 226));border-radius:var(--snice-border-radius-md,.25rem);background:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text,rgb(23 23 23));font-size:var(--snice-font-size-sm, .875rem);line-height:1.5;box-shadow:var(--snice-shadow-lg,0 10px 15px -3px rgb(0 0 0 / .1))}.calendar__popover:focus-visible{outline:0;box-shadow:var(--snice-shadow-lg,0 10px 15px -3px rgb(0 0 0 / .1)),0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__popover-loading{color:var(--snice-color-text-secondary,rgb(82 82 82));font-size:var(--snice-font-size-xs, .75rem)}.calendar__tooltip{position:fixed;z-index:var(--snice-z-floating,1000);max-width:18rem;padding:var(--snice-spacing-2xs,.25rem) var(--snice-spacing-xs,.5rem);border-radius:var(--snice-border-radius-md,.25rem);background:var(--snice-tooltip-bg,rgb(51 51 51));color:var(--snice-color-text-inverse,rgb(250 250 250));font-size:var(--snice-font-size-xs, .75rem);line-height:1.4;box-shadow:var(--snice-shadow-md,0 4px 6px -1px rgb(0 0 0 / .1));pointer-events:none}.calendar__event-bar:hover{opacity:.8}.calendar__event-bar--continues-left{border-top-left-radius:0;border-bottom-left-radius:0;margin-inline-start:0}.calendar__event-bar--continues-right{border-top-right-radius:0;border-bottom-right-radius:0;margin-inline-end:0}.calendar__more{position:absolute;bottom:var(--snice-spacing-3xs,.125rem);left:var(--snice-spacing-2xs,.25rem);font-size:var(--snice-font-size-xs, .75rem);font-weight:var(--snice-font-weight-medium,500);color:var(--snice-color-text,rgb(23 23 23));cursor:pointer}.calendar__more:hover{text-decoration:underline}.calendar__more:focus-visible{outline:0;box-shadow:0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__week-number{padding:var(--snice-spacing-sm,.75rem) var(--snice-spacing-xs,.5rem);text-align:center;font-size:var(--snice-font-size-xs, .75rem);color:var(--snice-color-text-tertiary,rgb(115 115 115));background:var(--snice-color-surface-container-low,rgb(250 250 250));border-right:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar--week .calendar__grid{grid-template-columns:repeat(7,1fr)}.calendar--week .calendar__day{min-height:7.5rem}.calendar--day .calendar__grid{grid-template-columns:1fr}.calendar--day .calendar__day{min-height:25rem}@media (prefers-reduced-motion:reduce){*{transition-duration:0s!important;animation-duration:0s!important}}";
240
+ var cssContent = ":host([hidden]){display:none}:host{display:block;font-family:var(--snice-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);contain:layout style}:host([overlay-open]){position:relative;z-index:var(--snice-z-floating,1000)}.calendar{width:100%;max-width:37.5rem;position:relative;height:100%;display:flex;flex-direction:column}.calendar__header{font-family:var(--snice-font-family-display, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);display:flex;align-items:center;justify-content:space-between;padding:var(--snice-spacing-md,1rem);background:var(--snice-color-surface,rgb(255 255 255));border-bottom:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar__title{font-family:var(--snice-font-family-display, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);font-size:var(--snice-heading-md, 1.125rem);font-weight:var(--snice-font-weight-semibold,600);color:var(--snice-color-text,rgb(23 23 23))}.calendar__nav{display:flex;gap:var(--snice-spacing-xs,.5rem)}.calendar__nav-button{padding:var(--snice-spacing-xs,.5rem) var(--snice-spacing-sm,.75rem);border:1px solid var(--snice-color-border,rgb(226 226 226));background:var(--snice-color-surface,rgb(255 255 255));border-radius:var(--snice-border-radius-md,.25rem);cursor:pointer;transition:background-color var(--snice-transition-fast, 150ms) ease,border-color var(--snice-transition-fast, 150ms) ease;font-size:var(--snice-font-size-sm, .875rem);color:var(--snice-color-text,rgb(23 23 23));font-family:inherit}.calendar__nav-button:hover{background:var(--snice-color-surface-container-low,rgb(250 250 250));border-color:var(--snice-color-border-hover,rgb(204 204 204))}.calendar__nav-button:focus-visible{outline:0;box-shadow:0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));grid-template-rows:min-content repeat(6,var(--calendar-row-cap,auto));flex:1 1 auto;position:relative;background:var(--snice-color-surface,rgb(255 255 255));container-type:inline-size}.calendar__weekday{box-sizing:border-box;min-width:0;overflow:hidden;padding:var(--snice-spacing-sm,.75rem);text-align:center;font-size:var(--snice-font-size-xs, .75rem);font-weight:var(--snice-font-weight-semibold,600);color:var(--snice-color-text-secondary,rgb(82 82 82));text-transform:uppercase;letter-spacing:var(--snice-tracking-widest, .1em);border-bottom:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar__day{box-sizing:border-box;min-width:0;min-height:min(max(calc(100cqw / 7),3rem,calc(2.125rem + var(--calendar-week-lanes,0) * 1.375rem + var(--calendar-week-more,0rem))),var(--calendar-row-cap,100000px));padding:var(--snice-spacing-xs,.5rem);border:1px solid var(--snice-color-border,rgb(226 226 226));border-top:none;border-left:none;cursor:pointer;transition:background-color var(--snice-transition-fast, 150ms) ease;position:relative;background:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text,rgb(23 23 23));overflow:hidden}.calendar__day:hover{background:var(--snice-color-surface-container-low,rgb(250 250 250))}.calendar__day:focus-visible{outline:0;box-shadow:inset 0 0 0 2px var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__day--week-start{border-left:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar--week-numbers .calendar__day--week-start{border-left:none}.calendar__day--stretch{min-height:min(max(3rem,calc(2.125rem + var(--calendar-week-lanes,0) * 1.375rem + var(--calendar-week-more,0rem))),var(--calendar-row-cap,100000px))}.calendar__day--static{cursor:default}.calendar__day--static:hover{background:var(--snice-color-surface,rgb(255 255 255))}.calendar__day--static.calendar__day--today:hover{background:var(--snice-color-primary-subtle,rgb(239 246 255))}.calendar__day--other-month{background:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text-tertiary,rgb(115 115 115));opacity:.5}.calendar__day--other-month:hover{opacity:.7}.calendar__day--today{background:var(--snice-color-primary-subtle,rgb(239 246 255))}.calendar__day--today:hover{background:var(--snice-color-primary-subtle-hover,rgb(219 234 254))}.calendar__day--selected{background:var(--snice-color-primary,rgb(37 99 235));color:var(--snice-color-text-inverse,rgb(250 250 250))}.calendar__day--selected:hover{background:var(--snice-color-primary-hover,rgb(29 78 216))}.calendar__day--disabled{background:var(--snice-color-surface-container,rgb(241 241 241));color:var(--snice-color-text-tertiary,rgb(115 115 115));opacity:.5;cursor:not-allowed}.calendar__day--disabled:hover{background:var(--snice-color-surface-container,rgb(241 241 241))}.calendar__day-number{font-size:var(--snice-font-size-sm, .875rem);font-weight:var(--snice-font-weight-medium,500);margin-bottom:var(--snice-spacing-2xs,.25rem)}.calendar__day--selected .calendar__day-number{color:var(--snice-color-text-inverse,rgb(250 250 250))}.calendar__event-bar{display:flex;align-items:center;gap:var(--snice-spacing-3xs,.125rem);position:absolute;inset:0 0 auto 0;margin-top:calc(1.875rem + var(--calendar-event-lane,0) * 1.375rem);margin-inline:var(--snice-spacing-3xs,0.125rem);height:1.125rem;line-height:1.125rem;font-size:var(--snice-font-size-2xs, .625rem);padding:0 var(--snice-spacing-2xs,.25rem);border-radius:var(--snice-border-radius-sm,.125rem);background:var(--snice-color-primary,rgb(37 99 235));color:var(--snice-color-text-inverse,rgb(250 250 250));white-space:nowrap;overflow:hidden;cursor:pointer;z-index:1;transition:opacity var(--snice-transition-fast, 150ms) ease}.calendar__event-bar--compact{padding-inline:var(--snice-spacing-3xs,0.125rem)}.calendar__event-title{overflow:hidden;text-overflow:ellipsis;min-width:0}.calendar__event-avatar{flex:none;--avatar-size:0.875rem;font-size:.375rem;border-radius:50%;overflow:hidden}.calendar__popover{position:absolute;z-index:var(--snice-z-floating,1000);min-width:14rem;max-width:22rem;padding:var(--snice-spacing-sm,.75rem);border:1px solid var(--snice-color-border,rgb(226 226 226));border-radius:var(--snice-border-radius-md,.25rem);background:var(--snice-color-surface,rgb(255 255 255));color:var(--snice-color-text,rgb(23 23 23));font-size:var(--snice-font-size-sm, .875rem);line-height:1.5;box-shadow:var(--snice-shadow-lg,0 10px 15px -3px rgb(0 0 0 / .1))}.calendar__popover:focus-visible{outline:0;box-shadow:var(--snice-shadow-lg,0 10px 15px -3px rgb(0 0 0 / .1)),0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__popover-loading{color:var(--snice-color-text-secondary,rgb(82 82 82));font-size:var(--snice-font-size-xs, .75rem)}.calendar__more-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--snice-spacing-3xs,.125rem);max-height:15rem;overflow-y:auto}.calendar__more-item{display:flex;align-items:center;gap:var(--snice-spacing-xs,.5rem);width:100%;padding:var(--snice-spacing-2xs,.25rem) var(--snice-spacing-xs,.5rem);border:none;border-radius:var(--snice-border-radius-sm,.125rem);background:0 0;color:var(--snice-color-text,rgb(23 23 23));font-family:inherit;font-size:var(--snice-font-size-sm, .875rem);text-align:start;cursor:pointer;transition:background-color var(--snice-transition-fast, 150ms) ease}.calendar__more-item:hover{background:var(--snice-color-surface-container-low,rgb(250 250 250))}.calendar__more-item:focus-visible{outline:0;box-shadow:0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar__more-dot{flex:none;width:.5rem;height:.5rem;border-radius:50%;background:var(--snice-color-primary,rgb(37 99 235))}.calendar__more-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.calendar__more-date{margin:0 0 var(--snice-spacing-2xs,.25rem);font-size:var(--snice-font-size-xs, .75rem);font-weight:var(--snice-font-weight-semibold,600);color:var(--snice-color-text-secondary,rgb(82 82 82))}.calendar__tooltip{position:absolute;z-index:var(--snice-z-floating,1000);max-width:18rem;padding:var(--snice-spacing-2xs,.25rem) var(--snice-spacing-xs,.5rem);border-radius:var(--snice-border-radius-md,.25rem);background:var(--snice-tooltip-bg,rgb(51 51 51));color:var(--snice-tooltip-color,rgb(255 255 255));font-size:var(--snice-font-size-xs, .75rem);line-height:1.4;box-shadow:var(--snice-shadow-md,0 4px 6px -1px rgb(0 0 0 / .1));pointer-events:none}.calendar__event-bar:hover{opacity:.8}.calendar__event-bar--continues-left{border-top-left-radius:0;border-bottom-left-radius:0;margin-inline-start:0}.calendar__event-bar--continues-right{border-top-right-radius:0;border-bottom-right-radius:0;margin-inline-end:0}.calendar__more{position:absolute;bottom:var(--snice-spacing-3xs,.125rem);left:var(--snice-spacing-2xs,.25rem);font-size:var(--snice-font-size-xs, .75rem);font-weight:var(--snice-font-weight-medium,500);color:var(--snice-color-text,rgb(23 23 23));cursor:pointer}.calendar__more:hover{text-decoration:underline}.calendar__more:focus-visible{outline:0;box-shadow:0 0 0 var(--snice-focus-ring-width,2px) var(--snice-focus-ring-color,rgb(59 130 246 / .5))}.calendar--week-numbers .calendar__grid{grid-template-columns:var(--calendar-week-number-width,2.25rem) repeat(7,minmax(0,1fr))}.calendar--week-numbers .calendar__weekday{padding-inline:var(--snice-spacing-2xs,0.25rem)}.calendar--week-numbers .calendar__day{min-height:min(max(calc((100cqw - var(--calendar-week-number-width,2.25rem))/ 7),3rem,calc(2.125rem + var(--calendar-week-lanes,0) * 1.375rem + var(--calendar-week-more,0rem))),var(--calendar-row-cap,100000px))}.calendar__week-number{box-sizing:border-box;display:flex;align-items:flex-start;justify-content:center;padding:var(--snice-spacing-xs,.5rem) var(--snice-spacing-2xs,.25rem);text-align:center;font-size:var(--snice-font-size-xs, .75rem);font-variant-numeric:tabular-nums;color:var(--snice-color-text-tertiary,rgb(115 115 115));background:var(--snice-color-surface-container-low,rgb(250 250 250));border:1px solid var(--snice-color-border,rgb(226 226 226));border-top:none}.calendar__week-number--header{background:var(--snice-color-surface,rgb(255 255 255));border:none;border-bottom:1px solid var(--snice-color-border,rgb(226 226 226))}.calendar--week .calendar__grid{grid-template-columns:repeat(7,1fr)}.calendar--week .calendar__day{min-height:7.5rem}.calendar--day .calendar__grid{grid-template-columns:1fr}.calendar--day .calendar__day{min-height:25rem}@media (prefers-reduced-motion:reduce){*{transition-duration:0s!important;animation-duration:0s!important}}";
241
241
 
242
242
  let SniceCalendar = (() => {
243
243
  let _classDecorators = [snice.element('snice-calendar')];
@@ -290,18 +290,24 @@ var SniceCalendar = (function (exports, snice) {
290
290
  let _eventPopover_extraInitializers = [];
291
291
  let _dispatchChange_decorators;
292
292
  let _dispatchEventClick_decorators;
293
- let _dispatchMoreClick_decorators;
294
293
  let _styles_decorators;
295
294
  let _initialize_decorators;
296
295
  let _requestEventPopoverContent_decorators;
297
296
  let _cleanupPopover_decorators;
298
297
  let _handlePropertyChange_decorators;
298
+ let _handleCalendarResize_decorators;
299
299
  let _handleWeekdayInputsChange_decorators;
300
300
  var SniceCalendar = class extends _classSuper {
301
301
  static { _classThis = this; }
302
302
  constructor() {
303
303
  super(...arguments);
304
- this.value = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _value_initializers, new Date()));
304
+ /**
305
+ * The selected day, or `null` for "nothing selected yet" — the state a
306
+ * calendar starts in. A default of today would paint the selected
307
+ * background over today's own highlight, making `highlight-today` invisible
308
+ * and announcing `aria-selected` for a choice the user never made.
309
+ */
310
+ this.value = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _value_initializers, null));
305
311
  this.view = (__runInitializers(this, _value_extraInitializers), __runInitializers(this, _view_initializers, 'month'));
306
312
  this.events = (__runInitializers(this, _view_extraInitializers), __runInitializers(this, _events_initializers, []));
307
313
  this.minDate = (__runInitializers(this, _events_extraInitializers), __runInitializers(this, _minDate_initializers, ''));
@@ -332,6 +338,7 @@ var SniceCalendar = (function (exports, snice) {
332
338
  this.closeEventPopover({ returnFocus: false });
333
339
  };
334
340
  this.dayCells = [];
341
+ this.weekNumberCells = [];
335
342
  this.focusedDate = null;
336
343
  this.handleGridKeydown = (e) => {
337
344
  const target = e.target;
@@ -394,6 +401,12 @@ var SniceCalendar = (function (exports, snice) {
394
401
  cell?.focus();
395
402
  });
396
403
  };
404
+ /** True while `--calendar-row-cap` is holding the week rows below the size
405
+ * they would take of their own accord. */
406
+ this.rowCapped = false;
407
+ /** Box the stripes were last laid out against, so a resize that changes
408
+ * nothing (including the one the render itself causes) is a no-op. */
409
+ this.laidOutFor = '';
397
410
  }
398
411
  static {
399
412
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
@@ -413,21 +426,21 @@ var SniceCalendar = (function (exports, snice) {
413
426
  _eventPopover_decorators = [snice.property({ attribute: false })];
414
427
  _dispatchChange_decorators = [snice.dispatch('calendar-change', { bubbles: true, composed: true })];
415
428
  _dispatchEventClick_decorators = [snice.dispatch('calendar-event-click', { bubbles: true, composed: true })];
416
- _dispatchMoreClick_decorators = [snice.dispatch('calendar-more-click', { bubbles: true, composed: true })];
417
429
  _styles_decorators = [snice.styles()];
418
430
  _initialize_decorators = [snice.ready()];
419
431
  _requestEventPopoverContent_decorators = [snice.request('calendar/event-popover')];
420
432
  _cleanupPopover_decorators = [snice.dispose()];
421
- _handlePropertyChange_decorators = [snice.watch('value'), snice.watch('events'), snice.watch('eventTooltip'), snice.watch('noDaySelect'), snice.watch('cellSizing')];
433
+ _handlePropertyChange_decorators = [snice.watch('value'), snice.watch('events'), snice.watch('eventTooltip'), snice.watch('noDaySelect'), snice.watch('cellSizing'), snice.watch('showWeekNumbers'), snice.watch('highlightToday')];
434
+ _handleCalendarResize_decorators = [snice.observe('resize', { throttle: 100 })];
422
435
  _handleWeekdayInputsChange_decorators = [snice.watch('locale'), snice.watch('firstDayOfWeek')];
423
436
  __esDecorate(this, null, _dispatchChange_decorators, { kind: "method", name: "dispatchChange", static: false, private: false, access: { has: obj => "dispatchChange" in obj, get: obj => obj.dispatchChange }, metadata: _metadata }, null, _instanceExtraInitializers);
424
437
  __esDecorate(this, null, _dispatchEventClick_decorators, { kind: "method", name: "dispatchEventClick", static: false, private: false, access: { has: obj => "dispatchEventClick" in obj, get: obj => obj.dispatchEventClick }, metadata: _metadata }, null, _instanceExtraInitializers);
425
- __esDecorate(this, null, _dispatchMoreClick_decorators, { kind: "method", name: "dispatchMoreClick", static: false, private: false, access: { has: obj => "dispatchMoreClick" in obj, get: obj => obj.dispatchMoreClick }, metadata: _metadata }, null, _instanceExtraInitializers);
426
438
  __esDecorate(this, null, _styles_decorators, { kind: "method", name: "styles", static: false, private: false, access: { has: obj => "styles" in obj, get: obj => obj.styles }, metadata: _metadata }, null, _instanceExtraInitializers);
427
439
  __esDecorate(this, null, _initialize_decorators, { kind: "method", name: "initialize", static: false, private: false, access: { has: obj => "initialize" in obj, get: obj => obj.initialize }, metadata: _metadata }, null, _instanceExtraInitializers);
428
440
  __esDecorate(this, null, _requestEventPopoverContent_decorators, { kind: "method", name: "requestEventPopoverContent", static: false, private: false, access: { has: obj => "requestEventPopoverContent" in obj, get: obj => obj.requestEventPopoverContent }, metadata: _metadata }, null, _instanceExtraInitializers);
429
441
  __esDecorate(this, null, _cleanupPopover_decorators, { kind: "method", name: "cleanupPopover", static: false, private: false, access: { has: obj => "cleanupPopover" in obj, get: obj => obj.cleanupPopover }, metadata: _metadata }, null, _instanceExtraInitializers);
430
442
  __esDecorate(this, null, _handlePropertyChange_decorators, { kind: "method", name: "handlePropertyChange", static: false, private: false, access: { has: obj => "handlePropertyChange" in obj, get: obj => obj.handlePropertyChange }, metadata: _metadata }, null, _instanceExtraInitializers);
443
+ __esDecorate(this, null, _handleCalendarResize_decorators, { kind: "method", name: "handleCalendarResize", static: false, private: false, access: { has: obj => "handleCalendarResize" in obj, get: obj => obj.handleCalendarResize }, metadata: _metadata }, null, _instanceExtraInitializers);
431
444
  __esDecorate(this, null, _handleWeekdayInputsChange_decorators, { kind: "method", name: "handleWeekdayInputsChange", static: false, private: false, access: { has: obj => "handleWeekdayInputsChange" in obj, get: obj => obj.handleWeekdayInputsChange }, metadata: _metadata }, null, _instanceExtraInitializers);
432
445
  __esDecorate(null, null, _value_decorators, { kind: "field", name: "value", static: false, private: false, access: { has: obj => "value" in obj, get: obj => obj.value, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, _value_initializers, _value_extraInitializers);
433
446
  __esDecorate(null, null, _view_decorators, { kind: "field", name: "view", static: false, private: false, access: { has: obj => "view" in obj, get: obj => obj.view, set: (obj, value) => { obj.view = value; } }, metadata: _metadata }, _view_initializers, _view_extraInitializers);
@@ -447,6 +460,14 @@ var SniceCalendar = (function (exports, snice) {
447
460
  SniceCalendar = _classThis = _classDescriptor.value;
448
461
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
449
462
  }
463
+ /**
464
+ * Columns the day grid is shifted by. The week-number column occupies the
465
+ * grid's first column, so every explicitly-placed child — weekday headers,
466
+ * day cells, event stripes — moves one column right while it is shown.
467
+ */
468
+ get columnOffset() {
469
+ return this.showWeekNumbers ? 1 : 0;
470
+ }
450
471
  computeRovingIndex(days) {
451
472
  // Prefer: focused → selected → today → first in-month day
452
473
  const same = (a, b) => a.getFullYear() === b.getFullYear() &&
@@ -477,8 +498,24 @@ var SniceCalendar = (function (exports, snice) {
477
498
  dispatchEventClick(event) {
478
499
  return { event, calendar: this };
479
500
  }
501
+ /**
502
+ * `calendar-more-click` carries a default action (the built-in day panel),
503
+ * so it is cancelable and the component has to read the result of the
504
+ * dispatch. `@dispatch` accepts `cancelable` — it is EventInit passthrough —
505
+ * but the decorated method returns the detail, not `dispatchEvent`'s
506
+ * boolean, so a cancelable default action cannot be observed through it.
507
+ * Dispatched by hand for that reason, the same way `snice-link`'s `navigate`
508
+ * and `snice-stepper`'s `step-change` are.
509
+ *
510
+ * @returns `false` when a listener called `preventDefault()`.
511
+ */
480
512
  dispatchMoreClick(date, count) {
481
- return { date, count, calendar: this };
513
+ return this.dispatchEvent(new CustomEvent('calendar-more-click', {
514
+ detail: { date, count, calendar: this },
515
+ bubbles: true,
516
+ composed: true,
517
+ cancelable: true,
518
+ }));
482
519
  }
483
520
  styles() {
484
521
  return snice.css /*css*/ `${cssContent}`;
@@ -531,7 +568,7 @@ var SniceCalendar = (function (exports, snice) {
531
568
  weekdayEl.className = 'calendar__weekday';
532
569
  weekdayEl.textContent = day;
533
570
  weekdayEl.style.gridRow = '1';
534
- weekdayEl.style.gridColumn = String(i + 1);
571
+ weekdayEl.style.gridColumn = String(i + 1 + this.columnOffset);
535
572
  this.grid.appendChild(weekdayEl);
536
573
  });
537
574
  // Mark grid for a11y
@@ -548,13 +585,33 @@ var SniceCalendar = (function (exports, snice) {
548
585
  // date so today/selected gets the 0.
549
586
  dayCell.setAttribute('tabindex', i === 0 ? '0' : '-1');
550
587
  dayCell.style.gridRow = String(Math.floor(i / 7) + 2);
551
- dayCell.style.gridColumn = String((i % 7) + 1);
588
+ dayCell.style.gridColumn = String((i % 7) + 1 + this.columnOffset);
552
589
  const dayNumber = document.createElement('div');
553
590
  dayNumber.className = 'calendar__day-number';
554
591
  dayCell.appendChild(dayNumber);
555
592
  this.grid.appendChild(dayCell);
556
593
  this.dayCells.push(dayCell);
557
594
  }
595
+ // Week-number column: a header corner cell plus one row header per week
596
+ // row, all in the reserved first column. Built once here and attached only
597
+ // while `show-week-numbers` is on, so a calendar without it carries no
598
+ // extra nodes.
599
+ this.weekNumberHeader = document.createElement('div');
600
+ this.weekNumberHeader.className =
601
+ 'calendar__week-number calendar__week-number--header';
602
+ this.weekNumberHeader.setAttribute('part', 'week-number-header');
603
+ this.weekNumberHeader.setAttribute('aria-hidden', 'true');
604
+ this.weekNumberHeader.style.gridRow = '1';
605
+ this.weekNumberHeader.style.gridColumn = '1';
606
+ for (let week = 0; week < 6; week++) {
607
+ const weekCell = document.createElement('div');
608
+ weekCell.className = 'calendar__week-number';
609
+ weekCell.setAttribute('part', 'week-number');
610
+ weekCell.setAttribute('role', 'rowheader');
611
+ weekCell.style.gridRow = String(week + 2);
612
+ weekCell.style.gridColumn = '1';
613
+ this.weekNumberCells.push(weekCell);
614
+ }
558
615
  // Shared tooltip overlay for event bars — one element, repositioned per
559
616
  // bar, so rich content can load lazily without a tooltip instance per bar.
560
617
  this.tooltipEl = document.createElement('div');
@@ -586,20 +643,40 @@ var SniceCalendar = (function (exports, snice) {
586
643
  async *requestEventPopoverContent(event) {
587
644
  return await (yield { event });
588
645
  }
589
- async openEventPopover(bar, event) {
646
+ /**
647
+ * Opens the shared overlay, empty, anchored on `anchor`, and arms its
648
+ * Escape / outside-press dismissal. Both panels the calendar owns — the
649
+ * event popover and the "+N more" day list — go through here, so they share
650
+ * one dismissal contract and one focus-return target.
651
+ *
652
+ * @returns the token identifying this opening; async content must check it
653
+ * against `popoverToken` before painting.
654
+ */
655
+ beginPopover(anchor, label) {
590
656
  const token = ++this.popoverToken;
591
657
  this.hideEventTooltip();
592
- // Anchor + loading shell first, so slow content still feels responsive.
593
- this.popoverBar = bar;
658
+ // A trigger that advertises aria-haspopup owes the reader the panel's
659
+ // state too — and the previous anchor is stale the moment this one opens
660
+ // (an entry in the day panel re-anchors the overlay on the same chip).
661
+ this.setAnchorExpanded(this.popoverBar, false);
662
+ this.setAnchorExpanded(anchor, true);
663
+ this.popoverBar = anchor;
664
+ this.popoverEl.setAttribute('aria-label', label);
594
665
  this.popoverEl.replaceChildren();
666
+ this.popoverEl.hidden = false;
667
+ this.syncOverlayLift();
668
+ document.addEventListener('keydown', this.boundPopoverEscape, true);
669
+ document.addEventListener('mousedown', this.boundPopoverOutside, true);
670
+ return token;
671
+ }
672
+ async openEventPopover(bar, event) {
673
+ // Anchor + loading shell first, so slow content still feels responsive.
674
+ const token = this.beginPopover(bar, event.title || 'Event details');
595
675
  const loading = document.createElement('div');
596
676
  loading.className = 'calendar__popover-loading';
597
677
  loading.textContent = 'Loading…';
598
678
  this.popoverEl.appendChild(loading);
599
- this.popoverEl.hidden = false;
600
679
  this.positionPopover(bar);
601
- document.addEventListener('keydown', this.boundPopoverEscape, true);
602
- document.addEventListener('mousedown', this.boundPopoverOutside, true);
603
680
  let content;
604
681
  try {
605
682
  if (event.popover !== true && event.popover) {
@@ -630,24 +707,133 @@ var SniceCalendar = (function (exports, snice) {
630
707
  this.positionPopover(bar);
631
708
  this.popoverEl.focus();
632
709
  }
710
+ /**
711
+ * The "+N more" chip's built-in default action: the day's hidden events,
712
+ * listed in the shared overlay.
713
+ *
714
+ * Wiring `calendar-more-click` is an application concern, but a chip that
715
+ * visibly does nothing when clicked reads as broken, so the component owns a
716
+ * sensible default. It is only a default — the event is cancelable, and
717
+ * `preventDefault()` leaves the day to the app.
718
+ */
719
+ openMorePanel(chip, date, hidden) {
720
+ const dayLabel = date.toLocaleDateString(this.locale, { dateStyle: 'full' });
721
+ this.beginPopover(chip, `${hidden.length} more event${hidden.length === 1 ? '' : 's'} on ${dayLabel}`);
722
+ const heading = document.createElement('p');
723
+ heading.className = 'calendar__more-date';
724
+ heading.setAttribute('part', 'more-panel-date');
725
+ heading.textContent = dayLabel;
726
+ const list = document.createElement('ul');
727
+ list.className = 'calendar__more-list';
728
+ list.setAttribute('part', 'more-list');
729
+ // Explicit: `list-style: none` drops the implicit list role in WebKit.
730
+ list.setAttribute('role', 'list');
731
+ for (const event of hidden) {
732
+ const item = document.createElement('li');
733
+ const entry = document.createElement('button');
734
+ entry.type = 'button';
735
+ entry.className = 'calendar__more-item';
736
+ entry.setAttribute('part', 'more-item');
737
+ const dot = document.createElement('span');
738
+ dot.className = 'calendar__more-dot';
739
+ dot.setAttribute('part', 'more-dot');
740
+ dot.setAttribute('aria-hidden', 'true');
741
+ if (event.color)
742
+ dot.style.background = event.color;
743
+ const title = document.createElement('span');
744
+ title.className = 'calendar__more-title';
745
+ title.textContent = event.title;
746
+ entry.append(dot, title);
747
+ entry.onclick = (e) => this.handleMorePanelEntry(chip, event, e);
748
+ item.appendChild(entry);
749
+ list.appendChild(item);
750
+ }
751
+ this.popoverEl.append(heading, list);
752
+ this.positionPopover(chip);
753
+ this.popoverEl.focus();
754
+ }
755
+ /**
756
+ * A panel entry behaves like the event's own stripe: it reports the click,
757
+ * and drills into the event's popover when it has one. Without popover
758
+ * content there is nothing further to show, so the panel closes and hands
759
+ * focus back to the chip.
760
+ */
761
+ handleMorePanelEntry(chip, event, e) {
762
+ this.handleEventClick(event, e);
763
+ if (event.popover)
764
+ this.openEventPopover(chip, event);
765
+ else
766
+ this.closeEventPopover();
767
+ }
768
+ /**
769
+ * Places an overlay at a point given in viewport coordinates.
770
+ *
771
+ * `:host` declares `contain: layout`, which makes the host — not the
772
+ * viewport — the containing block for its `position: fixed` descendants, so
773
+ * viewport coordinates written straight into `left`/`top` land the overlay a
774
+ * whole host-offset down the page. The overlays are `position: absolute`
775
+ * inside `.calendar` instead, and the target is converted into that box
776
+ * here, which is correct with or without the containment.
777
+ */
778
+ placeOverlay(el, viewportX, viewportY) {
779
+ // The containing block is the container's PADDING box, while the rect is
780
+ // its border box — `clientLeft`/`clientTop` are the border widths between
781
+ // them. They are 0 by default and non-zero only when a consumer borders
782
+ // the documented `::part(base)`, which would otherwise shift both overlays
783
+ // off their bar by that width.
784
+ const origin = this.container.getBoundingClientRect();
785
+ el.style.left = `${viewportX - origin.left - this.container.clientLeft}px`;
786
+ el.style.top = `${viewportY - origin.top - this.container.clientTop}px`;
787
+ }
788
+ /** Gap between a bar and its overlay, and the minimum viewport margin. */
789
+ static { this.OVERLAY_GAP = 6; }
790
+ static { this.OVERLAY_EDGE = 4; }
791
+ /** Clamps an overlay's left edge into the viewport. */
792
+ clampOverlayX(left, width) {
793
+ const edge = SniceCalendar.OVERLAY_EDGE;
794
+ const vw = window.innerWidth || 0;
795
+ return Math.max(edge, vw > 0 ? Math.min(left, vw - width - edge) : left);
796
+ }
633
797
  positionPopover(bar) {
634
798
  const rect = bar.getBoundingClientRect();
635
- this.popoverEl.style.left = `${Math.max(4, rect.left)}px`;
636
- this.popoverEl.style.top = `${rect.bottom + 6}px`;
799
+ const width = this.popoverEl.offsetWidth;
800
+ const height = this.popoverEl.offsetHeight;
801
+ const gap = SniceCalendar.OVERLAY_GAP;
802
+ const edge = SniceCalendar.OVERLAY_EDGE;
803
+ const vh = window.innerHeight || 0;
804
+ let y = rect.bottom + gap;
805
+ if (vh > 0 && y + height > vh - edge) {
806
+ // No room under the bar — flip above it, or pin to the bottom margin
807
+ // when the panel is taller than either side.
808
+ const above = rect.top - gap - height;
809
+ y = above >= edge ? above : Math.max(edge, vh - height - edge);
810
+ }
811
+ this.placeOverlay(this.popoverEl, this.clampOverlayX(rect.left, width), y);
637
812
  }
638
813
  closeEventPopover(options = {}) {
639
814
  if (!this.popoverBar)
640
815
  return;
641
816
  this.popoverToken++;
642
817
  const bar = this.popoverBar;
818
+ this.setAnchorExpanded(bar, false);
643
819
  this.popoverBar = null;
644
820
  this.popoverEl.hidden = true;
645
821
  this.popoverEl.replaceChildren();
822
+ this.syncOverlayLift();
646
823
  document.removeEventListener('keydown', this.boundPopoverEscape, true);
647
824
  document.removeEventListener('mousedown', this.boundPopoverOutside, true);
648
825
  if (options.returnFocus !== false && bar.isConnected)
649
826
  bar.focus();
650
827
  }
828
+ /** Keeps a popup trigger's `aria-expanded` in step with the overlay. Guarded
829
+ * on `aria-haspopup` so only the anchors that actually advertise a popup —
830
+ * the "+N more" chip and an event bar carrying popover content — claim the
831
+ * state; a plain bar is not a trigger and must not report one. */
832
+ setAnchorExpanded(anchor, expanded) {
833
+ if (!anchor?.hasAttribute('aria-haspopup'))
834
+ return;
835
+ anchor.setAttribute('aria-expanded', String(expanded));
836
+ }
651
837
  cleanupPopover() {
652
838
  document.removeEventListener('keydown', this.boundPopoverEscape, true);
653
839
  document.removeEventListener('mousedown', this.boundPopoverOutside, true);
@@ -677,16 +863,34 @@ var SniceCalendar = (function (exports, snice) {
677
863
  this.tooltipEl.hidden = false;
678
864
  this.tooltipBar = bar;
679
865
  bar.setAttribute('aria-describedby', this.tooltipEl.id);
680
- // Fixed positioning above the bar, clamped to the viewport.
866
+ this.syncOverlayLift();
867
+ this.positionTooltip(bar);
868
+ }
869
+ positionTooltip(bar) {
681
870
  const rect = bar.getBoundingClientRect();
682
- this.tooltipEl.style.left = `${Math.max(4, rect.left)}px`;
683
- this.tooltipEl.style.top = `${Math.max(4, rect.top - this.tooltipEl.offsetHeight - 6)}px`;
871
+ const gap = SniceCalendar.OVERLAY_GAP;
872
+ const edge = SniceCalendar.OVERLAY_EDGE;
873
+ // Above the bar, or under it when the top of the viewport is in the way.
874
+ let y = rect.top - this.tooltipEl.offsetHeight - gap;
875
+ if (y < edge)
876
+ y = rect.bottom + gap;
877
+ this.placeOverlay(this.tooltipEl, this.clampOverlayX(rect.left, this.tooltipEl.offsetWidth), y);
684
878
  }
685
879
  hideEventTooltip() {
686
880
  this.tooltipToken++;
687
881
  this.tooltipEl.hidden = true;
688
882
  this.tooltipBar?.removeAttribute('aria-describedby');
689
883
  this.tooltipBar = null;
884
+ this.syncOverlayLift();
885
+ }
886
+ /**
887
+ * Marks the host while a tooltip or popover is showing. Layout containment
888
+ * makes the host a stacking context, so an overlay can only paint above what
889
+ * follows the calendar if the host itself is lifted — see
890
+ * `:host([overlay-open])` in the stylesheet.
891
+ */
892
+ syncOverlayLift() {
893
+ this.toggleAttribute('overlay-open', !!this.popoverBar || !!this.tooltipBar);
690
894
  }
691
895
  updateView() {
692
896
  // Update header title
@@ -697,6 +901,9 @@ var SniceCalendar = (function (exports, snice) {
697
901
  // Update day cells
698
902
  const days = this.getMonthDays();
699
903
  const currentMonth = this.displayDate.getMonth();
904
+ // The week-number column shifts every explicitly-placed grid child, so it
905
+ // is resolved before the cells are positioned.
906
+ this.syncWeekNumberColumn(days);
700
907
  // Resolve which cell should be tab-stoppable (roving tabindex).
701
908
  const rovingIndex = this.computeRovingIndex(days);
702
909
  days.forEach((date, i) => {
@@ -710,6 +917,11 @@ var SniceCalendar = (function (exports, snice) {
710
917
  const isDisabled = this.isDisabled(date);
711
918
  // Update classes (preserving role)
712
919
  cell.className = 'calendar__day';
920
+ // The week's first cell draws the grid's left edge. A class, not
921
+ // :nth-child — the week-number column adds siblings that would shift
922
+ // any positional selector.
923
+ if (i % 7 === 0)
924
+ cell.classList.add('calendar__day--week-start');
713
925
  if (isOtherMonth)
714
926
  cell.classList.add('calendar__day--other-month');
715
927
  if (isToday)
@@ -738,15 +950,221 @@ var SniceCalendar = (function (exports, snice) {
738
950
  });
739
951
  this.renderEventStripes(days);
740
952
  }
741
- /** Visible stacking depth for event stripes; deeper lanes collapse into a
742
- * per-day "+N more" chip. */
743
- static { this.MAX_EVENT_LANES = 3; }
953
+ /**
954
+ * Attaches or detaches the week-number column and re-places every
955
+ * explicitly-positioned grid child for the resulting column offset.
956
+ */
957
+ syncWeekNumberColumn(days) {
958
+ const show = this.showWeekNumbers;
959
+ this.container.classList.toggle('calendar--week-numbers', show);
960
+ if (show) {
961
+ // Inserted in reading order — the corner cell ahead of the weekday
962
+ // strip, each row header ahead of its week's day cells.
963
+ if (!this.weekNumberHeader.isConnected) {
964
+ this.grid.insertBefore(this.weekNumberHeader, this.grid.firstChild);
965
+ }
966
+ this.weekNumberCells.forEach((cell, week) => {
967
+ if (!cell.isConnected)
968
+ this.grid.insertBefore(cell, this.dayCells[week * 7]);
969
+ const weekNumber = this.getWeekNumber(days[week * 7]);
970
+ cell.textContent = String(weekNumber);
971
+ cell.setAttribute('aria-label', `Week ${weekNumber}`);
972
+ });
973
+ }
974
+ else {
975
+ this.weekNumberHeader.remove();
976
+ this.weekNumberCells.forEach(cell => cell.remove());
977
+ }
978
+ const offset = this.columnOffset;
979
+ const weekdays = this.grid.querySelectorAll('.calendar__weekday');
980
+ weekdays.forEach((el, i) => {
981
+ el.style.gridColumn = String(i + 1 + offset);
982
+ });
983
+ this.dayCells.forEach((cell, i) => {
984
+ cell.style.gridColumn = String((i % 7) + 1 + offset);
985
+ });
986
+ }
987
+ /**
988
+ * Number of the week that starts on `weekStart`.
989
+ *
990
+ * Monday-start calendars use ISO-8601 (week 1 is the week holding the year's
991
+ * first Thursday). Every other start-of-week uses the common civil rule —
992
+ * the week containing January 1 is week 1 — anchored on the year the week
993
+ * ends in, so the turn-of-year week is numbered once, not twice.
994
+ */
995
+ getWeekNumber(weekStart) {
996
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
997
+ const midnight = (y, m, d) => new Date(y, m, d).getTime();
998
+ if (this.firstDayOfWeek === 1) {
999
+ // The ISO week is named by its Thursday.
1000
+ const thursday = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 3);
1001
+ const isoYear = thursday.getFullYear();
1002
+ // January 4 is always in ISO week 1; walk back to that week's Monday.
1003
+ const jan4 = new Date(isoYear, 0, 4);
1004
+ const week1Monday = midnight(isoYear, 0, 4 - ((jan4.getDay() + 6) % 7));
1005
+ return 1 + Math.round((thursday.getTime() - week1Monday) / WEEK_MS);
1006
+ }
1007
+ const weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 6);
1008
+ const year = weekEnd.getFullYear();
1009
+ const jan1 = new Date(year, 0, 1);
1010
+ const week1Start = midnight(year, 0, 1 - ((jan1.getDay() - this.firstDayOfWeek + 7) % 7));
1011
+ return 1 + Math.round((midnight(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate())
1012
+ - week1Start) / WEEK_MS);
1013
+ }
1014
+ /** Event-lane geometry in rem — mirrors snice-calendar.css. */
1015
+ static { this.LANE_HEIGHT_REM = 1.375; }
1016
+ /** Day-number strip above the first lane (the constant in the cell's
1017
+ * min-height reservation). */
1018
+ static { this.LANE_STACK_TOP_REM = 2.125; }
1019
+ /** Strip the "+N more" chip claims when a stack overflows. */
1020
+ static { this.MORE_CHIP_REM = 1; }
1021
+ /** Intrinsic floor of a `cell-sizing="stretch"` cell. */
1022
+ static { this.STRETCH_FLOOR_REM = 3; }
1023
+ /** Lane depth used when there is no layout to measure (headless DOM,
1024
+ * detached, `display: none`). */
1025
+ static { this.DEFAULT_EVENT_LANES = 3; }
1026
+ /**
1027
+ * Height a single lane physically occupies, measured from the top of the
1028
+ * cell: the bar sits 1.875rem down and is 1.125rem tall.
1029
+ *
1030
+ * LANE_STACK_TOP_REM + LANE_HEIGHT_REM (3.5rem) is the *budgeted* cost of a
1031
+ * lane — it carries a bottom gap so the next lane has somewhere to go. This
1032
+ * is the smaller number the budget falls back to when the row cannot grow:
1033
+ * the point below which a drawn lane would be clipped by its own cell.
1034
+ */
1035
+ static { this.LANE_MIN_FIT_REM = 3; }
1036
+ /** Height the "+N more" chip itself occupies at the bottom of the cell. */
1037
+ static { this.MORE_CHIP_FIT_REM = 1.125; }
1038
+ /**
1039
+ * Narrowest segment, in rem, that still carries an event avatar.
1040
+ *
1041
+ * A one-day chip in a 400px month grid is ~57px wide; the avatar, its gap,
1042
+ * and the bar padding leave the title three characters ("Des…"). The title
1043
+ * is the payload and the avatar is an adornment, so — like every
1044
+ * professional calendar — a segment that cannot carry both keeps the title
1045
+ * and drops the adornment. 4.5rem is the width at which a default-size
1046
+ * title still reads next to the avatar.
1047
+ */
1048
+ static { this.AVATAR_MIN_SEGMENT_REM = 4.5; }
1049
+ /**
1050
+ * How many event lanes fit in a day cell, and how many still fit once the
1051
+ * "+N more" chip claims its strip.
1052
+ *
1053
+ * Measured from the cell's own height with every lane reservation cleared,
1054
+ * so the budget follows whatever sizes the cell — the square baseline
1055
+ * (`100cqw / 7`), a height the host imposes on the calendar, or the stretch
1056
+ * floor — and never feeds back on itself. A `cell-sizing="stretch"` cell the
1057
+ * host does not constrain has no budget at all: its row grows to fit every
1058
+ * lane, which is what "collapse to content" means.
1059
+ */
1060
+ laneBudget() {
1061
+ const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
1062
+ const height = this.dayCells[0]?.getBoundingClientRect().height ?? 0;
1063
+ if (!height) {
1064
+ const depth = SniceCalendar.DEFAULT_EVENT_LANES;
1065
+ return { fit: depth, withMore: depth, height: 0, rem };
1066
+ }
1067
+ if (this.cellSizing === 'stretch' && !this.rowCapped
1068
+ && height <= SniceCalendar.STRETCH_FLOOR_REM * rem + 1) {
1069
+ return { fit: Infinity, withMore: Infinity, height, rem };
1070
+ }
1071
+ const lanesIn = (reserve) => {
1072
+ const budgeted = Math.floor((height / rem - SniceCalendar.LANE_STACK_TOP_REM - reserve)
1073
+ / SniceCalendar.LANE_HEIGHT_REM);
1074
+ if (budgeted >= 1)
1075
+ return budgeted;
1076
+ // Below the budgeted floor a row that can still grow always draws one
1077
+ // lane — the reservation at the end of renderEventStripes buys it the
1078
+ // height. A capped row has no such recourse, so the lane is drawn only
1079
+ // when it already fits; otherwise the day collapses entirely into its
1080
+ // chip rather than painting a stripe its cell will clip.
1081
+ if (!this.rowCapped)
1082
+ return 1;
1083
+ const needed = SniceCalendar.LANE_MIN_FIT_REM
1084
+ + (reserve > 0 ? SniceCalendar.MORE_CHIP_FIT_REM : 0);
1085
+ return height / rem >= needed ? 1 : 0;
1086
+ };
1087
+ return {
1088
+ fit: lanesIn(0),
1089
+ withMore: lanesIn(SniceCalendar.MORE_CHIP_REM),
1090
+ height,
1091
+ rem,
1092
+ };
1093
+ }
1094
+ /**
1095
+ * Fits the six week rows inside a host that constrains the calendar's
1096
+ * height.
1097
+ *
1098
+ * The square baseline sizes a week row from the *column width*
1099
+ * (`100cqw / 7`), which says nothing about how much height the host has
1100
+ * given the grid. A host that hands down a definite height — the components
1101
+ * page's `aspect-ratio: 1` card is the reported case — therefore gets a grid
1102
+ * whose min-content height exceeds its box: the grid overflows, and whatever
1103
+ * clips it (that card's `overflow: hidden`) takes the bottom weeks of the
1104
+ * month with it.
1105
+ *
1106
+ * The room the grid has and the room it wants are both measurable, so the
1107
+ * fix is to compare them and publish the per-row ceiling when they disagree.
1108
+ * With no constraint the container's height IS its content height, the two
1109
+ * agree, and nothing is published — square cells and the growable lane
1110
+ * reservation are untouched. Measured with the cap cleared, so it never
1111
+ * feeds back on itself.
1112
+ *
1113
+ * Month view only. The cap divides the room into WEEK_ROWS equal shares and
1114
+ * lands as a definite `grid-template-rows` track; the week and day views
1115
+ * carry their own taller `min-height` at higher specificity, so a definite
1116
+ * track there would leave every cell overflowing its own row and painting
1117
+ * over the next one. They keep the pre-cap behaviour — the var stays unset,
1118
+ * the tracks stay `auto`.
1119
+ */
1120
+ syncRowCap() {
1121
+ this.grid.style.removeProperty('--calendar-row-cap');
1122
+ this.rowCapped = false;
1123
+ if (this.view !== 'month')
1124
+ return;
1125
+ // The host's own content box, not just the container's: `.calendar`'s
1126
+ // `height: 100%` resolves to auto against a host sized by `max-height`,
1127
+ // so the container reports the height it WANTED and only the host box is
1128
+ // actually clamped. Whichever is smaller is the room there really is.
1129
+ const hostStyle = getComputedStyle(this);
1130
+ const hostRoom = this.clientHeight
1131
+ - parseFloat(hostStyle.paddingTop) - parseFloat(hostStyle.paddingBottom);
1132
+ const room = Math.min(this.container.clientHeight, hostRoom)
1133
+ - this.header.getBoundingClientRect().height;
1134
+ const wanted = this.grid.getBoundingClientRect().height;
1135
+ // No layout to measure (headless, detached, display:none) — or the rows
1136
+ // already fit, which is every unconstrained calendar.
1137
+ if (!(room > 0) || !(wanted > 0) || wanted <= room + 0.5)
1138
+ return;
1139
+ const weekdayHeight = this.grid.querySelector('.calendar__weekday')?.getBoundingClientRect().height ?? 0;
1140
+ // A cell cannot be shorter than its own padding and rules, so a cap below
1141
+ // that floor buys no height — it only detaches the rows from their tracks
1142
+ // and stacks each week on top of the next. Below the floor the calendar
1143
+ // has compressed as far as it physically can: it overflows a host that
1144
+ // short, the way it always did, rather than rendering weeks over weeks.
1145
+ const cell = this.dayCells[0];
1146
+ const cellStyle = cell ? getComputedStyle(cell) : null;
1147
+ const cellFloor = cellStyle
1148
+ ? parseFloat(cellStyle.paddingTop) + parseFloat(cellStyle.paddingBottom)
1149
+ + parseFloat(cellStyle.borderTopWidth)
1150
+ + parseFloat(cellStyle.borderBottomWidth)
1151
+ : 0;
1152
+ const cap = Math.max((room - weekdayHeight) / SniceCalendar.WEEK_ROWS, cellFloor);
1153
+ if (!(cap > 0))
1154
+ return;
1155
+ this.rowCapped = true;
1156
+ this.grid.style.setProperty('--calendar-row-cap', `${cap}px`);
1157
+ }
1158
+ /** Week rows in a month grid. */
1159
+ static { this.WEEK_ROWS = 6; }
744
1160
  /**
745
1161
  * Render events as continuous stripes: one bar per (event × week row),
746
1162
  * spanning the event's days in that week and chopped at week boundaries
747
1163
  * with continues-left/right styling. Overlapping events stack into lanes
748
1164
  * per week — earlier start first, longer event first on ties — like
749
- * professional calendars.
1165
+ * professional calendars. How many of those lanes are drawn is the week's
1166
+ * share of the day cell's height (see laneBudget); the rest collapse into
1167
+ * the day's "+N more" chip.
750
1168
  */
751
1169
  renderEventStripes(days) {
752
1170
  this.closeEventPopover({ returnFocus: false }); // anchors are re-created
@@ -756,6 +1174,9 @@ var SniceCalendar = (function (exports, snice) {
756
1174
  cell.style.removeProperty('--calendar-week-lanes');
757
1175
  cell.style.removeProperty('--calendar-week-more');
758
1176
  });
1177
+ // Before anything is measured and regardless of whether this month has
1178
+ // events: an empty calendar in a short host overflows too.
1179
+ this.syncRowCap();
759
1180
  if (!this.events || this.events.length === 0)
760
1181
  return;
761
1182
  // Local-midnight timestamps for the visible grid; events normalize to the
@@ -788,14 +1209,28 @@ var SniceCalendar = (function (exports, snice) {
788
1209
  });
789
1210
  });
790
1211
  resolved.sort((a, b) => a.startTime - b.startTime || b.duration - a.duration || a.order - b.order);
791
- const hiddenPerDay = new Array(dayTimes.length).fill(0);
1212
+ // The events each day dropped, in the order they stack — the chip reports
1213
+ // the count, and its built-in panel lists them.
1214
+ const hiddenPerDay = Array.from({ length: dayTimes.length }, () => []);
792
1215
  const weekCount = Math.floor(dayTimes.length / 7);
793
1216
  const weekLaneCounts = new Array(weekCount).fill(0);
1217
+ // Measured before a single bar or reservation goes back in, so the budget
1218
+ // reflects the room the row has of its own.
1219
+ const budget = this.laneBudget();
1220
+ // Segment width comes from the same measured cell the lane budget uses, so
1221
+ // the avatar decision follows the calendar's real box and is re-taken by
1222
+ // the resize observer. No layout to measure → nothing is dropped.
1223
+ const cellWidth = this.dayCells[0]?.getBoundingClientRect().width ?? 0;
1224
+ const avatarMinWidth = SniceCalendar.AVATAR_MIN_SEGMENT_REM * budget.rem;
794
1225
  for (let week = 0; week < weekCount; week++) {
795
1226
  const weekStart = week * 7;
796
1227
  const weekEnd = weekStart + 6;
797
1228
  // lanes[lane] holds the occupied [startCol, endCol] ranges in this week.
798
1229
  const lanes = [];
1230
+ const placed = [];
1231
+ let stackDepth = 0;
1232
+ // Lane assignment first: how deep this week stacks decides whether the
1233
+ // "+N more" chip is needed at all, and the chip costs a strip of height.
799
1234
  for (const item of resolved) {
800
1235
  if (item.endIdx < weekStart || item.startIdx > weekEnd)
801
1236
  continue;
@@ -807,22 +1242,35 @@ var SniceCalendar = (function (exports, snice) {
807
1242
  while (lanes[lane]?.some(([s, e]) => startCol <= e && endCol >= s))
808
1243
  lane++;
809
1244
  (lanes[lane] ??= []).push([startCol, endCol]);
810
- weekLaneCounts[week] = Math.min(Math.max(weekLaneCounts[week], lane + 1), SniceCalendar.MAX_EVENT_LANES);
811
- if (lane >= SniceCalendar.MAX_EVENT_LANES) {
1245
+ placed.push({ item, lane, segStart, segEnd });
1246
+ stackDepth = Math.max(stackDepth, lane + 1);
1247
+ }
1248
+ const visibleLanes = stackDepth <= budget.fit ? stackDepth : budget.withMore;
1249
+ weekLaneCounts[week] = Math.min(stackDepth, visibleLanes);
1250
+ for (const { item, lane, segStart, segEnd } of placed) {
1251
+ if (lane >= visibleLanes) {
812
1252
  for (let i = segStart; i <= segEnd; i++)
813
- hiddenPerDay[i]++;
1253
+ hiddenPerDay[i].push(item.event);
814
1254
  continue;
815
1255
  }
1256
+ const startCol = segStart - weekStart;
1257
+ const endCol = segEnd - weekStart;
816
1258
  const continuesLeft = segStart > item.startIdx
817
1259
  || (segStart === item.startIdx && item.startsBeforeGrid);
818
1260
  const continuesRight = segEnd < item.endIdx
819
1261
  || (segEnd === item.endIdx && item.endsAfterGrid);
1262
+ // A segment too narrow for an avatar plus a legible title runs
1263
+ // compact: title only, tighter inline padding.
1264
+ const compact = cellWidth > 0
1265
+ && cellWidth * (endCol - startCol + 1) < avatarMinWidth;
820
1266
  const bar = document.createElement('div');
821
1267
  bar.className = 'calendar__event-bar';
822
1268
  if (continuesLeft)
823
1269
  bar.classList.add('calendar__event-bar--continues-left');
824
1270
  if (continuesRight)
825
1271
  bar.classList.add('calendar__event-bar--continues-right');
1272
+ if (compact)
1273
+ bar.classList.add('calendar__event-bar--compact');
826
1274
  // A custom className rides along on both the class list and the part
827
1275
  // attribute, so events are styleable from outside via
828
1276
  // ::part(<className>).
@@ -834,11 +1282,12 @@ var SniceCalendar = (function (exports, snice) {
834
1282
  bar.setAttribute('data-event-id', String(item.event.id));
835
1283
  bar.setAttribute('data-lane', String(lane));
836
1284
  bar.style.gridRow = String(week + 2); // row 1 is the weekday header
837
- bar.style.gridColumn = `${startCol + 1} / ${endCol + 2}`;
1285
+ bar.style.gridColumn =
1286
+ `${startCol + 1 + this.columnOffset} / ${endCol + 2 + this.columnOffset}`;
838
1287
  bar.style.setProperty('--calendar-event-lane', String(lane));
839
1288
  if (item.event.color)
840
1289
  bar.style.background = item.event.color;
841
- if (item.event.avatar) {
1290
+ if (item.event.avatar && !compact) {
842
1291
  const spec = typeof item.event.avatar === 'string'
843
1292
  ? { src: item.event.avatar }
844
1293
  : item.event.avatar;
@@ -897,7 +1346,8 @@ var SniceCalendar = (function (exports, snice) {
897
1346
  this.grid.appendChild(bar);
898
1347
  }
899
1348
  }
900
- hiddenPerDay.forEach((count, i) => {
1349
+ hiddenPerDay.forEach((hidden, i) => {
1350
+ const count = hidden.length;
901
1351
  if (count === 0)
902
1352
  return;
903
1353
  const cell = this.dayCells[i];
@@ -908,35 +1358,49 @@ var SniceCalendar = (function (exports, snice) {
908
1358
  more.className = 'calendar__more';
909
1359
  more.setAttribute('part', 'more-chip');
910
1360
  more.textContent = `+${count} more`;
911
- // The chip is its own control: it reports its day and hidden count, and
912
- // its clicks never fall through to the day cell's selection handler.
1361
+ // The chip is its own control: it reports its day and hidden count,
1362
+ // opens the day panel unless the app cancels it, and its clicks never
1363
+ // fall through to the day cell's selection handler.
913
1364
  more.setAttribute('role', 'button');
914
1365
  more.setAttribute('tabindex', '0');
1366
+ more.setAttribute('aria-haspopup', 'dialog');
1367
+ more.setAttribute('aria-expanded', 'false');
915
1368
  more.setAttribute('aria-label', `${count} more event${count === 1 ? '' : 's'} on ${date.toLocaleDateString(this.locale, { dateStyle: 'full' })}`);
916
- more.onclick = (e) => this.handleMoreClick(date, count, e);
1369
+ more.onclick = (e) => this.handleMoreClick(date, more, hidden, e);
917
1370
  more.onkeydown = (e) => {
918
1371
  if (e.key !== 'Enter' && e.key !== ' ')
919
1372
  return;
920
1373
  e.preventDefault();
921
- this.handleMoreClick(date, count, e);
1374
+ this.handleMoreClick(date, more, hidden, e);
922
1375
  };
923
1376
  cell.appendChild(more);
924
1377
  });
925
1378
  // Reserve row height for the lane stack (and the "+N more" chip) so
926
1379
  // stripes never spill past their week row — every cell in a week shares
927
1380
  // the reservation, since the grid row is as tall as its tallest cell.
1381
+ // The reservation is only a floor: when the row already has the room it is
1382
+ // left off, so the busy week's row stays level with the rest. That covers
1383
+ // every cell tall enough for the stack it shows; the floor still bites
1384
+ // below 3.5rem (the one lane always drawn) or 4.5rem once the chip claims
1385
+ // its strip — square cells are that short on calendars under ~500px wide.
928
1386
  for (let week = 0; week < weekCount; week++) {
929
1387
  if (weekLaneCounts[week] === 0)
930
1388
  continue;
931
1389
  const weekHasMore = hiddenPerDay
932
- .slice(week * 7, week * 7 + 7).some(count => count > 0);
1390
+ .slice(week * 7, week * 7 + 7).some(hidden => hidden.length > 0);
1391
+ const needed = (SniceCalendar.LANE_STACK_TOP_REM
1392
+ + weekLaneCounts[week] * SniceCalendar.LANE_HEIGHT_REM
1393
+ + (weekHasMore ? SniceCalendar.MORE_CHIP_REM : 0)) * budget.rem;
1394
+ if (budget.height && needed <= budget.height + 0.5)
1395
+ continue;
933
1396
  for (let i = week * 7; i < week * 7 + 7; i++) {
934
1397
  const cell = this.dayCells[i];
935
1398
  if (!cell)
936
1399
  continue;
937
1400
  cell.style.setProperty('--calendar-week-lanes', String(weekLaneCounts[week]));
938
- if (weekHasMore)
939
- cell.style.setProperty('--calendar-week-more', '1rem');
1401
+ if (weekHasMore) {
1402
+ cell.style.setProperty('--calendar-week-more', `${SniceCalendar.MORE_CHIP_REM}rem`);
1403
+ }
940
1404
  }
941
1405
  }
942
1406
  }
@@ -1010,7 +1474,13 @@ var SniceCalendar = (function (exports, snice) {
1010
1474
  return this.isSameDay(date, new Date());
1011
1475
  }
1012
1476
  isSelected(date) {
1477
+ // No value means no selection at all — and an unparseable one selects
1478
+ // nothing rather than throwing out of the render.
1479
+ if (!this.value)
1480
+ return false;
1013
1481
  const valueDate = typeof this.value === 'string' ? new Date(this.value) : this.value;
1482
+ if (!(valueDate instanceof Date) || isNaN(valueDate.getTime()))
1483
+ return false;
1014
1484
  return this.isSameDay(date, valueDate);
1015
1485
  }
1016
1486
  isDisabled(date) {
@@ -1043,9 +1513,13 @@ var SniceCalendar = (function (exports, snice) {
1043
1513
  e.stopPropagation();
1044
1514
  this.dispatchEventClick(event);
1045
1515
  }
1046
- handleMoreClick(date, count, e) {
1516
+ handleMoreClick(date, chip, hidden, e) {
1047
1517
  e.stopPropagation();
1048
- this.dispatchMoreClick(date, count);
1518
+ // The app gets first refusal: preventDefault() means it is opening its own
1519
+ // day view and the built-in panel must stay out of the way.
1520
+ if (!this.dispatchMoreClick(date, hidden.length))
1521
+ return;
1522
+ this.openMorePanel(chip, date, hidden);
1049
1523
  }
1050
1524
  getMonthDays() {
1051
1525
  const year = this.displayDate.getFullYear();
@@ -1078,6 +1552,20 @@ var SniceCalendar = (function (exports, snice) {
1078
1552
  this.updateView();
1079
1553
  }
1080
1554
  }
1555
+ /**
1556
+ * The lane budget comes from the cell's height, and that height follows the
1557
+ * calendar's own box — column width in square mode, the host's height when
1558
+ * it has one. Re-stripe when the box actually changes.
1559
+ */
1560
+ handleCalendarResize(entry) {
1561
+ if (!this.grid)
1562
+ return;
1563
+ const box = `${Math.round(entry.contentRect.width)}x${Math.round(entry.contentRect.height)}`;
1564
+ if (box === this.laidOutFor)
1565
+ return;
1566
+ this.laidOutFor = box;
1567
+ this.updateView();
1568
+ }
1081
1569
  handleWeekdayInputsChange() {
1082
1570
  if (!this.grid)
1083
1571
  return;
@@ -1085,14 +1573,19 @@ var SniceCalendar = (function (exports, snice) {
1085
1573
  const existing = this.grid.querySelectorAll('.calendar__weekday');
1086
1574
  existing.forEach(el => el.remove());
1087
1575
  const weekdays = this.getWeekdays();
1576
+ // Anchor on the first day cell rather than a child index: the week-number
1577
+ // column adds siblings ahead of it, so a positional index would interleave
1578
+ // the strip with the grid's other children.
1579
+ const firstRow = this.weekNumberCells[0]?.isConnected
1580
+ ? this.weekNumberCells[0] : this.dayCells[0];
1581
+ const anchor = firstRow ?? null;
1088
1582
  weekdays.forEach((day, i) => {
1089
1583
  const weekdayEl = document.createElement('div');
1090
1584
  weekdayEl.className = 'calendar__weekday';
1091
1585
  weekdayEl.textContent = day;
1092
1586
  weekdayEl.style.gridRow = '1';
1093
- weekdayEl.style.gridColumn = String(i + 1);
1094
- // Insert before the first day cell so header row stays at the top
1095
- this.grid.insertBefore(weekdayEl, this.grid.children[i] || null);
1587
+ weekdayEl.style.gridColumn = String(i + 1 + this.columnOffset);
1588
+ this.grid.insertBefore(weekdayEl, anchor);
1096
1589
  });
1097
1590
  this.updateView();
1098
1591
  }