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,8 +1,8 @@
1
1
  import { __runInitializers, __esDecorate } from 'tslib';
2
- import { element, property, dispatch, styles, ready, request, dispose, watch, css } from 'snice';
2
+ import { element, property, dispatch, styles, ready, request, dispose, watch, observe, css } from 'snice';
3
3
  import '../avatar/snice-avatar.js';
4
4
 
5
- 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}}";
5
+ 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}}";
6
6
 
7
7
  let SniceCalendar = (() => {
8
8
  let _classDecorators = [element('snice-calendar')];
@@ -55,18 +55,24 @@ let SniceCalendar = (() => {
55
55
  let _eventPopover_extraInitializers = [];
56
56
  let _dispatchChange_decorators;
57
57
  let _dispatchEventClick_decorators;
58
- let _dispatchMoreClick_decorators;
59
58
  let _styles_decorators;
60
59
  let _initialize_decorators;
61
60
  let _requestEventPopoverContent_decorators;
62
61
  let _cleanupPopover_decorators;
63
62
  let _handlePropertyChange_decorators;
63
+ let _handleCalendarResize_decorators;
64
64
  let _handleWeekdayInputsChange_decorators;
65
65
  var SniceCalendar = class extends _classSuper {
66
66
  static { _classThis = this; }
67
67
  constructor() {
68
68
  super(...arguments);
69
- this.value = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _value_initializers, new Date()));
69
+ /**
70
+ * The selected day, or `null` for "nothing selected yet" — the state a
71
+ * calendar starts in. A default of today would paint the selected
72
+ * background over today's own highlight, making `highlight-today` invisible
73
+ * and announcing `aria-selected` for a choice the user never made.
74
+ */
75
+ this.value = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _value_initializers, null));
70
76
  this.view = (__runInitializers(this, _value_extraInitializers), __runInitializers(this, _view_initializers, 'month'));
71
77
  this.events = (__runInitializers(this, _view_extraInitializers), __runInitializers(this, _events_initializers, []));
72
78
  this.minDate = (__runInitializers(this, _events_extraInitializers), __runInitializers(this, _minDate_initializers, ''));
@@ -97,6 +103,7 @@ let SniceCalendar = (() => {
97
103
  this.closeEventPopover({ returnFocus: false });
98
104
  };
99
105
  this.dayCells = [];
106
+ this.weekNumberCells = [];
100
107
  this.focusedDate = null;
101
108
  this.handleGridKeydown = (e) => {
102
109
  const target = e.target;
@@ -159,6 +166,12 @@ let SniceCalendar = (() => {
159
166
  cell?.focus();
160
167
  });
161
168
  };
169
+ /** True while `--calendar-row-cap` is holding the week rows below the size
170
+ * they would take of their own accord. */
171
+ this.rowCapped = false;
172
+ /** Box the stripes were last laid out against, so a resize that changes
173
+ * nothing (including the one the render itself causes) is a no-op. */
174
+ this.laidOutFor = '';
162
175
  }
163
176
  static {
164
177
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
@@ -178,21 +191,21 @@ let SniceCalendar = (() => {
178
191
  _eventPopover_decorators = [property({ attribute: false })];
179
192
  _dispatchChange_decorators = [dispatch('calendar-change', { bubbles: true, composed: true })];
180
193
  _dispatchEventClick_decorators = [dispatch('calendar-event-click', { bubbles: true, composed: true })];
181
- _dispatchMoreClick_decorators = [dispatch('calendar-more-click', { bubbles: true, composed: true })];
182
194
  _styles_decorators = [styles()];
183
195
  _initialize_decorators = [ready()];
184
196
  _requestEventPopoverContent_decorators = [request('calendar/event-popover')];
185
197
  _cleanupPopover_decorators = [dispose()];
186
- _handlePropertyChange_decorators = [watch('value'), watch('events'), watch('eventTooltip'), watch('noDaySelect'), watch('cellSizing')];
198
+ _handlePropertyChange_decorators = [watch('value'), watch('events'), watch('eventTooltip'), watch('noDaySelect'), watch('cellSizing'), watch('showWeekNumbers'), watch('highlightToday')];
199
+ _handleCalendarResize_decorators = [observe('resize', { throttle: 100 })];
187
200
  _handleWeekdayInputsChange_decorators = [watch('locale'), watch('firstDayOfWeek')];
188
201
  __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);
189
202
  __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);
190
- __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);
191
203
  __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);
192
204
  __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);
193
205
  __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);
194
206
  __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);
195
207
  __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);
208
+ __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);
196
209
  __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);
197
210
  __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);
198
211
  __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);
@@ -212,6 +225,14 @@ let SniceCalendar = (() => {
212
225
  SniceCalendar = _classThis = _classDescriptor.value;
213
226
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
214
227
  }
228
+ /**
229
+ * Columns the day grid is shifted by. The week-number column occupies the
230
+ * grid's first column, so every explicitly-placed child — weekday headers,
231
+ * day cells, event stripes — moves one column right while it is shown.
232
+ */
233
+ get columnOffset() {
234
+ return this.showWeekNumbers ? 1 : 0;
235
+ }
215
236
  computeRovingIndex(days) {
216
237
  // Prefer: focused → selected → today → first in-month day
217
238
  const same = (a, b) => a.getFullYear() === b.getFullYear() &&
@@ -242,8 +263,24 @@ let SniceCalendar = (() => {
242
263
  dispatchEventClick(event) {
243
264
  return { event, calendar: this };
244
265
  }
266
+ /**
267
+ * `calendar-more-click` carries a default action (the built-in day panel),
268
+ * so it is cancelable and the component has to read the result of the
269
+ * dispatch. `@dispatch` accepts `cancelable` — it is EventInit passthrough —
270
+ * but the decorated method returns the detail, not `dispatchEvent`'s
271
+ * boolean, so a cancelable default action cannot be observed through it.
272
+ * Dispatched by hand for that reason, the same way `snice-link`'s `navigate`
273
+ * and `snice-stepper`'s `step-change` are.
274
+ *
275
+ * @returns `false` when a listener called `preventDefault()`.
276
+ */
245
277
  dispatchMoreClick(date, count) {
246
- return { date, count, calendar: this };
278
+ return this.dispatchEvent(new CustomEvent('calendar-more-click', {
279
+ detail: { date, count, calendar: this },
280
+ bubbles: true,
281
+ composed: true,
282
+ cancelable: true,
283
+ }));
247
284
  }
248
285
  styles() {
249
286
  return css /*css*/ `${cssContent}`;
@@ -296,7 +333,7 @@ let SniceCalendar = (() => {
296
333
  weekdayEl.className = 'calendar__weekday';
297
334
  weekdayEl.textContent = day;
298
335
  weekdayEl.style.gridRow = '1';
299
- weekdayEl.style.gridColumn = String(i + 1);
336
+ weekdayEl.style.gridColumn = String(i + 1 + this.columnOffset);
300
337
  this.grid.appendChild(weekdayEl);
301
338
  });
302
339
  // Mark grid for a11y
@@ -313,13 +350,33 @@ let SniceCalendar = (() => {
313
350
  // date so today/selected gets the 0.
314
351
  dayCell.setAttribute('tabindex', i === 0 ? '0' : '-1');
315
352
  dayCell.style.gridRow = String(Math.floor(i / 7) + 2);
316
- dayCell.style.gridColumn = String((i % 7) + 1);
353
+ dayCell.style.gridColumn = String((i % 7) + 1 + this.columnOffset);
317
354
  const dayNumber = document.createElement('div');
318
355
  dayNumber.className = 'calendar__day-number';
319
356
  dayCell.appendChild(dayNumber);
320
357
  this.grid.appendChild(dayCell);
321
358
  this.dayCells.push(dayCell);
322
359
  }
360
+ // Week-number column: a header corner cell plus one row header per week
361
+ // row, all in the reserved first column. Built once here and attached only
362
+ // while `show-week-numbers` is on, so a calendar without it carries no
363
+ // extra nodes.
364
+ this.weekNumberHeader = document.createElement('div');
365
+ this.weekNumberHeader.className =
366
+ 'calendar__week-number calendar__week-number--header';
367
+ this.weekNumberHeader.setAttribute('part', 'week-number-header');
368
+ this.weekNumberHeader.setAttribute('aria-hidden', 'true');
369
+ this.weekNumberHeader.style.gridRow = '1';
370
+ this.weekNumberHeader.style.gridColumn = '1';
371
+ for (let week = 0; week < 6; week++) {
372
+ const weekCell = document.createElement('div');
373
+ weekCell.className = 'calendar__week-number';
374
+ weekCell.setAttribute('part', 'week-number');
375
+ weekCell.setAttribute('role', 'rowheader');
376
+ weekCell.style.gridRow = String(week + 2);
377
+ weekCell.style.gridColumn = '1';
378
+ this.weekNumberCells.push(weekCell);
379
+ }
323
380
  // Shared tooltip overlay for event bars — one element, repositioned per
324
381
  // bar, so rich content can load lazily without a tooltip instance per bar.
325
382
  this.tooltipEl = document.createElement('div');
@@ -351,20 +408,40 @@ let SniceCalendar = (() => {
351
408
  async *requestEventPopoverContent(event) {
352
409
  return await (yield { event });
353
410
  }
354
- async openEventPopover(bar, event) {
411
+ /**
412
+ * Opens the shared overlay, empty, anchored on `anchor`, and arms its
413
+ * Escape / outside-press dismissal. Both panels the calendar owns — the
414
+ * event popover and the "+N more" day list — go through here, so they share
415
+ * one dismissal contract and one focus-return target.
416
+ *
417
+ * @returns the token identifying this opening; async content must check it
418
+ * against `popoverToken` before painting.
419
+ */
420
+ beginPopover(anchor, label) {
355
421
  const token = ++this.popoverToken;
356
422
  this.hideEventTooltip();
357
- // Anchor + loading shell first, so slow content still feels responsive.
358
- this.popoverBar = bar;
423
+ // A trigger that advertises aria-haspopup owes the reader the panel's
424
+ // state too — and the previous anchor is stale the moment this one opens
425
+ // (an entry in the day panel re-anchors the overlay on the same chip).
426
+ this.setAnchorExpanded(this.popoverBar, false);
427
+ this.setAnchorExpanded(anchor, true);
428
+ this.popoverBar = anchor;
429
+ this.popoverEl.setAttribute('aria-label', label);
359
430
  this.popoverEl.replaceChildren();
431
+ this.popoverEl.hidden = false;
432
+ this.syncOverlayLift();
433
+ document.addEventListener('keydown', this.boundPopoverEscape, true);
434
+ document.addEventListener('mousedown', this.boundPopoverOutside, true);
435
+ return token;
436
+ }
437
+ async openEventPopover(bar, event) {
438
+ // Anchor + loading shell first, so slow content still feels responsive.
439
+ const token = this.beginPopover(bar, event.title || 'Event details');
360
440
  const loading = document.createElement('div');
361
441
  loading.className = 'calendar__popover-loading';
362
442
  loading.textContent = 'Loading…';
363
443
  this.popoverEl.appendChild(loading);
364
- this.popoverEl.hidden = false;
365
444
  this.positionPopover(bar);
366
- document.addEventListener('keydown', this.boundPopoverEscape, true);
367
- document.addEventListener('mousedown', this.boundPopoverOutside, true);
368
445
  let content;
369
446
  try {
370
447
  if (event.popover !== true && event.popover) {
@@ -395,24 +472,133 @@ let SniceCalendar = (() => {
395
472
  this.positionPopover(bar);
396
473
  this.popoverEl.focus();
397
474
  }
475
+ /**
476
+ * The "+N more" chip's built-in default action: the day's hidden events,
477
+ * listed in the shared overlay.
478
+ *
479
+ * Wiring `calendar-more-click` is an application concern, but a chip that
480
+ * visibly does nothing when clicked reads as broken, so the component owns a
481
+ * sensible default. It is only a default — the event is cancelable, and
482
+ * `preventDefault()` leaves the day to the app.
483
+ */
484
+ openMorePanel(chip, date, hidden) {
485
+ const dayLabel = date.toLocaleDateString(this.locale, { dateStyle: 'full' });
486
+ this.beginPopover(chip, `${hidden.length} more event${hidden.length === 1 ? '' : 's'} on ${dayLabel}`);
487
+ const heading = document.createElement('p');
488
+ heading.className = 'calendar__more-date';
489
+ heading.setAttribute('part', 'more-panel-date');
490
+ heading.textContent = dayLabel;
491
+ const list = document.createElement('ul');
492
+ list.className = 'calendar__more-list';
493
+ list.setAttribute('part', 'more-list');
494
+ // Explicit: `list-style: none` drops the implicit list role in WebKit.
495
+ list.setAttribute('role', 'list');
496
+ for (const event of hidden) {
497
+ const item = document.createElement('li');
498
+ const entry = document.createElement('button');
499
+ entry.type = 'button';
500
+ entry.className = 'calendar__more-item';
501
+ entry.setAttribute('part', 'more-item');
502
+ const dot = document.createElement('span');
503
+ dot.className = 'calendar__more-dot';
504
+ dot.setAttribute('part', 'more-dot');
505
+ dot.setAttribute('aria-hidden', 'true');
506
+ if (event.color)
507
+ dot.style.background = event.color;
508
+ const title = document.createElement('span');
509
+ title.className = 'calendar__more-title';
510
+ title.textContent = event.title;
511
+ entry.append(dot, title);
512
+ entry.onclick = (e) => this.handleMorePanelEntry(chip, event, e);
513
+ item.appendChild(entry);
514
+ list.appendChild(item);
515
+ }
516
+ this.popoverEl.append(heading, list);
517
+ this.positionPopover(chip);
518
+ this.popoverEl.focus();
519
+ }
520
+ /**
521
+ * A panel entry behaves like the event's own stripe: it reports the click,
522
+ * and drills into the event's popover when it has one. Without popover
523
+ * content there is nothing further to show, so the panel closes and hands
524
+ * focus back to the chip.
525
+ */
526
+ handleMorePanelEntry(chip, event, e) {
527
+ this.handleEventClick(event, e);
528
+ if (event.popover)
529
+ this.openEventPopover(chip, event);
530
+ else
531
+ this.closeEventPopover();
532
+ }
533
+ /**
534
+ * Places an overlay at a point given in viewport coordinates.
535
+ *
536
+ * `:host` declares `contain: layout`, which makes the host — not the
537
+ * viewport — the containing block for its `position: fixed` descendants, so
538
+ * viewport coordinates written straight into `left`/`top` land the overlay a
539
+ * whole host-offset down the page. The overlays are `position: absolute`
540
+ * inside `.calendar` instead, and the target is converted into that box
541
+ * here, which is correct with or without the containment.
542
+ */
543
+ placeOverlay(el, viewportX, viewportY) {
544
+ // The containing block is the container's PADDING box, while the rect is
545
+ // its border box — `clientLeft`/`clientTop` are the border widths between
546
+ // them. They are 0 by default and non-zero only when a consumer borders
547
+ // the documented `::part(base)`, which would otherwise shift both overlays
548
+ // off their bar by that width.
549
+ const origin = this.container.getBoundingClientRect();
550
+ el.style.left = `${viewportX - origin.left - this.container.clientLeft}px`;
551
+ el.style.top = `${viewportY - origin.top - this.container.clientTop}px`;
552
+ }
553
+ /** Gap between a bar and its overlay, and the minimum viewport margin. */
554
+ static { this.OVERLAY_GAP = 6; }
555
+ static { this.OVERLAY_EDGE = 4; }
556
+ /** Clamps an overlay's left edge into the viewport. */
557
+ clampOverlayX(left, width) {
558
+ const edge = SniceCalendar.OVERLAY_EDGE;
559
+ const vw = window.innerWidth || 0;
560
+ return Math.max(edge, vw > 0 ? Math.min(left, vw - width - edge) : left);
561
+ }
398
562
  positionPopover(bar) {
399
563
  const rect = bar.getBoundingClientRect();
400
- this.popoverEl.style.left = `${Math.max(4, rect.left)}px`;
401
- this.popoverEl.style.top = `${rect.bottom + 6}px`;
564
+ const width = this.popoverEl.offsetWidth;
565
+ const height = this.popoverEl.offsetHeight;
566
+ const gap = SniceCalendar.OVERLAY_GAP;
567
+ const edge = SniceCalendar.OVERLAY_EDGE;
568
+ const vh = window.innerHeight || 0;
569
+ let y = rect.bottom + gap;
570
+ if (vh > 0 && y + height > vh - edge) {
571
+ // No room under the bar — flip above it, or pin to the bottom margin
572
+ // when the panel is taller than either side.
573
+ const above = rect.top - gap - height;
574
+ y = above >= edge ? above : Math.max(edge, vh - height - edge);
575
+ }
576
+ this.placeOverlay(this.popoverEl, this.clampOverlayX(rect.left, width), y);
402
577
  }
403
578
  closeEventPopover(options = {}) {
404
579
  if (!this.popoverBar)
405
580
  return;
406
581
  this.popoverToken++;
407
582
  const bar = this.popoverBar;
583
+ this.setAnchorExpanded(bar, false);
408
584
  this.popoverBar = null;
409
585
  this.popoverEl.hidden = true;
410
586
  this.popoverEl.replaceChildren();
587
+ this.syncOverlayLift();
411
588
  document.removeEventListener('keydown', this.boundPopoverEscape, true);
412
589
  document.removeEventListener('mousedown', this.boundPopoverOutside, true);
413
590
  if (options.returnFocus !== false && bar.isConnected)
414
591
  bar.focus();
415
592
  }
593
+ /** Keeps a popup trigger's `aria-expanded` in step with the overlay. Guarded
594
+ * on `aria-haspopup` so only the anchors that actually advertise a popup —
595
+ * the "+N more" chip and an event bar carrying popover content — claim the
596
+ * state; a plain bar is not a trigger and must not report one. */
597
+ setAnchorExpanded(anchor, expanded) {
598
+ if (!anchor?.hasAttribute('aria-haspopup'))
599
+ return;
600
+ anchor.setAttribute('aria-expanded', String(expanded));
601
+ }
416
602
  cleanupPopover() {
417
603
  document.removeEventListener('keydown', this.boundPopoverEscape, true);
418
604
  document.removeEventListener('mousedown', this.boundPopoverOutside, true);
@@ -442,16 +628,34 @@ let SniceCalendar = (() => {
442
628
  this.tooltipEl.hidden = false;
443
629
  this.tooltipBar = bar;
444
630
  bar.setAttribute('aria-describedby', this.tooltipEl.id);
445
- // Fixed positioning above the bar, clamped to the viewport.
631
+ this.syncOverlayLift();
632
+ this.positionTooltip(bar);
633
+ }
634
+ positionTooltip(bar) {
446
635
  const rect = bar.getBoundingClientRect();
447
- this.tooltipEl.style.left = `${Math.max(4, rect.left)}px`;
448
- this.tooltipEl.style.top = `${Math.max(4, rect.top - this.tooltipEl.offsetHeight - 6)}px`;
636
+ const gap = SniceCalendar.OVERLAY_GAP;
637
+ const edge = SniceCalendar.OVERLAY_EDGE;
638
+ // Above the bar, or under it when the top of the viewport is in the way.
639
+ let y = rect.top - this.tooltipEl.offsetHeight - gap;
640
+ if (y < edge)
641
+ y = rect.bottom + gap;
642
+ this.placeOverlay(this.tooltipEl, this.clampOverlayX(rect.left, this.tooltipEl.offsetWidth), y);
449
643
  }
450
644
  hideEventTooltip() {
451
645
  this.tooltipToken++;
452
646
  this.tooltipEl.hidden = true;
453
647
  this.tooltipBar?.removeAttribute('aria-describedby');
454
648
  this.tooltipBar = null;
649
+ this.syncOverlayLift();
650
+ }
651
+ /**
652
+ * Marks the host while a tooltip or popover is showing. Layout containment
653
+ * makes the host a stacking context, so an overlay can only paint above what
654
+ * follows the calendar if the host itself is lifted — see
655
+ * `:host([overlay-open])` in the stylesheet.
656
+ */
657
+ syncOverlayLift() {
658
+ this.toggleAttribute('overlay-open', !!this.popoverBar || !!this.tooltipBar);
455
659
  }
456
660
  updateView() {
457
661
  // Update header title
@@ -462,6 +666,9 @@ let SniceCalendar = (() => {
462
666
  // Update day cells
463
667
  const days = this.getMonthDays();
464
668
  const currentMonth = this.displayDate.getMonth();
669
+ // The week-number column shifts every explicitly-placed grid child, so it
670
+ // is resolved before the cells are positioned.
671
+ this.syncWeekNumberColumn(days);
465
672
  // Resolve which cell should be tab-stoppable (roving tabindex).
466
673
  const rovingIndex = this.computeRovingIndex(days);
467
674
  days.forEach((date, i) => {
@@ -475,6 +682,11 @@ let SniceCalendar = (() => {
475
682
  const isDisabled = this.isDisabled(date);
476
683
  // Update classes (preserving role)
477
684
  cell.className = 'calendar__day';
685
+ // The week's first cell draws the grid's left edge. A class, not
686
+ // :nth-child — the week-number column adds siblings that would shift
687
+ // any positional selector.
688
+ if (i % 7 === 0)
689
+ cell.classList.add('calendar__day--week-start');
478
690
  if (isOtherMonth)
479
691
  cell.classList.add('calendar__day--other-month');
480
692
  if (isToday)
@@ -503,15 +715,221 @@ let SniceCalendar = (() => {
503
715
  });
504
716
  this.renderEventStripes(days);
505
717
  }
506
- /** Visible stacking depth for event stripes; deeper lanes collapse into a
507
- * per-day "+N more" chip. */
508
- static { this.MAX_EVENT_LANES = 3; }
718
+ /**
719
+ * Attaches or detaches the week-number column and re-places every
720
+ * explicitly-positioned grid child for the resulting column offset.
721
+ */
722
+ syncWeekNumberColumn(days) {
723
+ const show = this.showWeekNumbers;
724
+ this.container.classList.toggle('calendar--week-numbers', show);
725
+ if (show) {
726
+ // Inserted in reading order — the corner cell ahead of the weekday
727
+ // strip, each row header ahead of its week's day cells.
728
+ if (!this.weekNumberHeader.isConnected) {
729
+ this.grid.insertBefore(this.weekNumberHeader, this.grid.firstChild);
730
+ }
731
+ this.weekNumberCells.forEach((cell, week) => {
732
+ if (!cell.isConnected)
733
+ this.grid.insertBefore(cell, this.dayCells[week * 7]);
734
+ const weekNumber = this.getWeekNumber(days[week * 7]);
735
+ cell.textContent = String(weekNumber);
736
+ cell.setAttribute('aria-label', `Week ${weekNumber}`);
737
+ });
738
+ }
739
+ else {
740
+ this.weekNumberHeader.remove();
741
+ this.weekNumberCells.forEach(cell => cell.remove());
742
+ }
743
+ const offset = this.columnOffset;
744
+ const weekdays = this.grid.querySelectorAll('.calendar__weekday');
745
+ weekdays.forEach((el, i) => {
746
+ el.style.gridColumn = String(i + 1 + offset);
747
+ });
748
+ this.dayCells.forEach((cell, i) => {
749
+ cell.style.gridColumn = String((i % 7) + 1 + offset);
750
+ });
751
+ }
752
+ /**
753
+ * Number of the week that starts on `weekStart`.
754
+ *
755
+ * Monday-start calendars use ISO-8601 (week 1 is the week holding the year's
756
+ * first Thursday). Every other start-of-week uses the common civil rule —
757
+ * the week containing January 1 is week 1 — anchored on the year the week
758
+ * ends in, so the turn-of-year week is numbered once, not twice.
759
+ */
760
+ getWeekNumber(weekStart) {
761
+ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
762
+ const midnight = (y, m, d) => new Date(y, m, d).getTime();
763
+ if (this.firstDayOfWeek === 1) {
764
+ // The ISO week is named by its Thursday.
765
+ const thursday = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 3);
766
+ const isoYear = thursday.getFullYear();
767
+ // January 4 is always in ISO week 1; walk back to that week's Monday.
768
+ const jan4 = new Date(isoYear, 0, 4);
769
+ const week1Monday = midnight(isoYear, 0, 4 - ((jan4.getDay() + 6) % 7));
770
+ return 1 + Math.round((thursday.getTime() - week1Monday) / WEEK_MS);
771
+ }
772
+ const weekEnd = new Date(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate() + 6);
773
+ const year = weekEnd.getFullYear();
774
+ const jan1 = new Date(year, 0, 1);
775
+ const week1Start = midnight(year, 0, 1 - ((jan1.getDay() - this.firstDayOfWeek + 7) % 7));
776
+ return 1 + Math.round((midnight(weekStart.getFullYear(), weekStart.getMonth(), weekStart.getDate())
777
+ - week1Start) / WEEK_MS);
778
+ }
779
+ /** Event-lane geometry in rem — mirrors snice-calendar.css. */
780
+ static { this.LANE_HEIGHT_REM = 1.375; }
781
+ /** Day-number strip above the first lane (the constant in the cell's
782
+ * min-height reservation). */
783
+ static { this.LANE_STACK_TOP_REM = 2.125; }
784
+ /** Strip the "+N more" chip claims when a stack overflows. */
785
+ static { this.MORE_CHIP_REM = 1; }
786
+ /** Intrinsic floor of a `cell-sizing="stretch"` cell. */
787
+ static { this.STRETCH_FLOOR_REM = 3; }
788
+ /** Lane depth used when there is no layout to measure (headless DOM,
789
+ * detached, `display: none`). */
790
+ static { this.DEFAULT_EVENT_LANES = 3; }
791
+ /**
792
+ * Height a single lane physically occupies, measured from the top of the
793
+ * cell: the bar sits 1.875rem down and is 1.125rem tall.
794
+ *
795
+ * LANE_STACK_TOP_REM + LANE_HEIGHT_REM (3.5rem) is the *budgeted* cost of a
796
+ * lane — it carries a bottom gap so the next lane has somewhere to go. This
797
+ * is the smaller number the budget falls back to when the row cannot grow:
798
+ * the point below which a drawn lane would be clipped by its own cell.
799
+ */
800
+ static { this.LANE_MIN_FIT_REM = 3; }
801
+ /** Height the "+N more" chip itself occupies at the bottom of the cell. */
802
+ static { this.MORE_CHIP_FIT_REM = 1.125; }
803
+ /**
804
+ * Narrowest segment, in rem, that still carries an event avatar.
805
+ *
806
+ * A one-day chip in a 400px month grid is ~57px wide; the avatar, its gap,
807
+ * and the bar padding leave the title three characters ("Des…"). The title
808
+ * is the payload and the avatar is an adornment, so — like every
809
+ * professional calendar — a segment that cannot carry both keeps the title
810
+ * and drops the adornment. 4.5rem is the width at which a default-size
811
+ * title still reads next to the avatar.
812
+ */
813
+ static { this.AVATAR_MIN_SEGMENT_REM = 4.5; }
814
+ /**
815
+ * How many event lanes fit in a day cell, and how many still fit once the
816
+ * "+N more" chip claims its strip.
817
+ *
818
+ * Measured from the cell's own height with every lane reservation cleared,
819
+ * so the budget follows whatever sizes the cell — the square baseline
820
+ * (`100cqw / 7`), a height the host imposes on the calendar, or the stretch
821
+ * floor — and never feeds back on itself. A `cell-sizing="stretch"` cell the
822
+ * host does not constrain has no budget at all: its row grows to fit every
823
+ * lane, which is what "collapse to content" means.
824
+ */
825
+ laneBudget() {
826
+ const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
827
+ const height = this.dayCells[0]?.getBoundingClientRect().height ?? 0;
828
+ if (!height) {
829
+ const depth = SniceCalendar.DEFAULT_EVENT_LANES;
830
+ return { fit: depth, withMore: depth, height: 0, rem };
831
+ }
832
+ if (this.cellSizing === 'stretch' && !this.rowCapped
833
+ && height <= SniceCalendar.STRETCH_FLOOR_REM * rem + 1) {
834
+ return { fit: Infinity, withMore: Infinity, height, rem };
835
+ }
836
+ const lanesIn = (reserve) => {
837
+ const budgeted = Math.floor((height / rem - SniceCalendar.LANE_STACK_TOP_REM - reserve)
838
+ / SniceCalendar.LANE_HEIGHT_REM);
839
+ if (budgeted >= 1)
840
+ return budgeted;
841
+ // Below the budgeted floor a row that can still grow always draws one
842
+ // lane — the reservation at the end of renderEventStripes buys it the
843
+ // height. A capped row has no such recourse, so the lane is drawn only
844
+ // when it already fits; otherwise the day collapses entirely into its
845
+ // chip rather than painting a stripe its cell will clip.
846
+ if (!this.rowCapped)
847
+ return 1;
848
+ const needed = SniceCalendar.LANE_MIN_FIT_REM
849
+ + (reserve > 0 ? SniceCalendar.MORE_CHIP_FIT_REM : 0);
850
+ return height / rem >= needed ? 1 : 0;
851
+ };
852
+ return {
853
+ fit: lanesIn(0),
854
+ withMore: lanesIn(SniceCalendar.MORE_CHIP_REM),
855
+ height,
856
+ rem,
857
+ };
858
+ }
859
+ /**
860
+ * Fits the six week rows inside a host that constrains the calendar's
861
+ * height.
862
+ *
863
+ * The square baseline sizes a week row from the *column width*
864
+ * (`100cqw / 7`), which says nothing about how much height the host has
865
+ * given the grid. A host that hands down a definite height — the components
866
+ * page's `aspect-ratio: 1` card is the reported case — therefore gets a grid
867
+ * whose min-content height exceeds its box: the grid overflows, and whatever
868
+ * clips it (that card's `overflow: hidden`) takes the bottom weeks of the
869
+ * month with it.
870
+ *
871
+ * The room the grid has and the room it wants are both measurable, so the
872
+ * fix is to compare them and publish the per-row ceiling when they disagree.
873
+ * With no constraint the container's height IS its content height, the two
874
+ * agree, and nothing is published — square cells and the growable lane
875
+ * reservation are untouched. Measured with the cap cleared, so it never
876
+ * feeds back on itself.
877
+ *
878
+ * Month view only. The cap divides the room into WEEK_ROWS equal shares and
879
+ * lands as a definite `grid-template-rows` track; the week and day views
880
+ * carry their own taller `min-height` at higher specificity, so a definite
881
+ * track there would leave every cell overflowing its own row and painting
882
+ * over the next one. They keep the pre-cap behaviour — the var stays unset,
883
+ * the tracks stay `auto`.
884
+ */
885
+ syncRowCap() {
886
+ this.grid.style.removeProperty('--calendar-row-cap');
887
+ this.rowCapped = false;
888
+ if (this.view !== 'month')
889
+ return;
890
+ // The host's own content box, not just the container's: `.calendar`'s
891
+ // `height: 100%` resolves to auto against a host sized by `max-height`,
892
+ // so the container reports the height it WANTED and only the host box is
893
+ // actually clamped. Whichever is smaller is the room there really is.
894
+ const hostStyle = getComputedStyle(this);
895
+ const hostRoom = this.clientHeight
896
+ - parseFloat(hostStyle.paddingTop) - parseFloat(hostStyle.paddingBottom);
897
+ const room = Math.min(this.container.clientHeight, hostRoom)
898
+ - this.header.getBoundingClientRect().height;
899
+ const wanted = this.grid.getBoundingClientRect().height;
900
+ // No layout to measure (headless, detached, display:none) — or the rows
901
+ // already fit, which is every unconstrained calendar.
902
+ if (!(room > 0) || !(wanted > 0) || wanted <= room + 0.5)
903
+ return;
904
+ const weekdayHeight = this.grid.querySelector('.calendar__weekday')?.getBoundingClientRect().height ?? 0;
905
+ // A cell cannot be shorter than its own padding and rules, so a cap below
906
+ // that floor buys no height — it only detaches the rows from their tracks
907
+ // and stacks each week on top of the next. Below the floor the calendar
908
+ // has compressed as far as it physically can: it overflows a host that
909
+ // short, the way it always did, rather than rendering weeks over weeks.
910
+ const cell = this.dayCells[0];
911
+ const cellStyle = cell ? getComputedStyle(cell) : null;
912
+ const cellFloor = cellStyle
913
+ ? parseFloat(cellStyle.paddingTop) + parseFloat(cellStyle.paddingBottom)
914
+ + parseFloat(cellStyle.borderTopWidth)
915
+ + parseFloat(cellStyle.borderBottomWidth)
916
+ : 0;
917
+ const cap = Math.max((room - weekdayHeight) / SniceCalendar.WEEK_ROWS, cellFloor);
918
+ if (!(cap > 0))
919
+ return;
920
+ this.rowCapped = true;
921
+ this.grid.style.setProperty('--calendar-row-cap', `${cap}px`);
922
+ }
923
+ /** Week rows in a month grid. */
924
+ static { this.WEEK_ROWS = 6; }
509
925
  /**
510
926
  * Render events as continuous stripes: one bar per (event × week row),
511
927
  * spanning the event's days in that week and chopped at week boundaries
512
928
  * with continues-left/right styling. Overlapping events stack into lanes
513
929
  * per week — earlier start first, longer event first on ties — like
514
- * professional calendars.
930
+ * professional calendars. How many of those lanes are drawn is the week's
931
+ * share of the day cell's height (see laneBudget); the rest collapse into
932
+ * the day's "+N more" chip.
515
933
  */
516
934
  renderEventStripes(days) {
517
935
  this.closeEventPopover({ returnFocus: false }); // anchors are re-created
@@ -521,6 +939,9 @@ let SniceCalendar = (() => {
521
939
  cell.style.removeProperty('--calendar-week-lanes');
522
940
  cell.style.removeProperty('--calendar-week-more');
523
941
  });
942
+ // Before anything is measured and regardless of whether this month has
943
+ // events: an empty calendar in a short host overflows too.
944
+ this.syncRowCap();
524
945
  if (!this.events || this.events.length === 0)
525
946
  return;
526
947
  // Local-midnight timestamps for the visible grid; events normalize to the
@@ -553,14 +974,28 @@ let SniceCalendar = (() => {
553
974
  });
554
975
  });
555
976
  resolved.sort((a, b) => a.startTime - b.startTime || b.duration - a.duration || a.order - b.order);
556
- const hiddenPerDay = new Array(dayTimes.length).fill(0);
977
+ // The events each day dropped, in the order they stack — the chip reports
978
+ // the count, and its built-in panel lists them.
979
+ const hiddenPerDay = Array.from({ length: dayTimes.length }, () => []);
557
980
  const weekCount = Math.floor(dayTimes.length / 7);
558
981
  const weekLaneCounts = new Array(weekCount).fill(0);
982
+ // Measured before a single bar or reservation goes back in, so the budget
983
+ // reflects the room the row has of its own.
984
+ const budget = this.laneBudget();
985
+ // Segment width comes from the same measured cell the lane budget uses, so
986
+ // the avatar decision follows the calendar's real box and is re-taken by
987
+ // the resize observer. No layout to measure → nothing is dropped.
988
+ const cellWidth = this.dayCells[0]?.getBoundingClientRect().width ?? 0;
989
+ const avatarMinWidth = SniceCalendar.AVATAR_MIN_SEGMENT_REM * budget.rem;
559
990
  for (let week = 0; week < weekCount; week++) {
560
991
  const weekStart = week * 7;
561
992
  const weekEnd = weekStart + 6;
562
993
  // lanes[lane] holds the occupied [startCol, endCol] ranges in this week.
563
994
  const lanes = [];
995
+ const placed = [];
996
+ let stackDepth = 0;
997
+ // Lane assignment first: how deep this week stacks decides whether the
998
+ // "+N more" chip is needed at all, and the chip costs a strip of height.
564
999
  for (const item of resolved) {
565
1000
  if (item.endIdx < weekStart || item.startIdx > weekEnd)
566
1001
  continue;
@@ -572,22 +1007,35 @@ let SniceCalendar = (() => {
572
1007
  while (lanes[lane]?.some(([s, e]) => startCol <= e && endCol >= s))
573
1008
  lane++;
574
1009
  (lanes[lane] ??= []).push([startCol, endCol]);
575
- weekLaneCounts[week] = Math.min(Math.max(weekLaneCounts[week], lane + 1), SniceCalendar.MAX_EVENT_LANES);
576
- if (lane >= SniceCalendar.MAX_EVENT_LANES) {
1010
+ placed.push({ item, lane, segStart, segEnd });
1011
+ stackDepth = Math.max(stackDepth, lane + 1);
1012
+ }
1013
+ const visibleLanes = stackDepth <= budget.fit ? stackDepth : budget.withMore;
1014
+ weekLaneCounts[week] = Math.min(stackDepth, visibleLanes);
1015
+ for (const { item, lane, segStart, segEnd } of placed) {
1016
+ if (lane >= visibleLanes) {
577
1017
  for (let i = segStart; i <= segEnd; i++)
578
- hiddenPerDay[i]++;
1018
+ hiddenPerDay[i].push(item.event);
579
1019
  continue;
580
1020
  }
1021
+ const startCol = segStart - weekStart;
1022
+ const endCol = segEnd - weekStart;
581
1023
  const continuesLeft = segStart > item.startIdx
582
1024
  || (segStart === item.startIdx && item.startsBeforeGrid);
583
1025
  const continuesRight = segEnd < item.endIdx
584
1026
  || (segEnd === item.endIdx && item.endsAfterGrid);
1027
+ // A segment too narrow for an avatar plus a legible title runs
1028
+ // compact: title only, tighter inline padding.
1029
+ const compact = cellWidth > 0
1030
+ && cellWidth * (endCol - startCol + 1) < avatarMinWidth;
585
1031
  const bar = document.createElement('div');
586
1032
  bar.className = 'calendar__event-bar';
587
1033
  if (continuesLeft)
588
1034
  bar.classList.add('calendar__event-bar--continues-left');
589
1035
  if (continuesRight)
590
1036
  bar.classList.add('calendar__event-bar--continues-right');
1037
+ if (compact)
1038
+ bar.classList.add('calendar__event-bar--compact');
591
1039
  // A custom className rides along on both the class list and the part
592
1040
  // attribute, so events are styleable from outside via
593
1041
  // ::part(<className>).
@@ -599,11 +1047,12 @@ let SniceCalendar = (() => {
599
1047
  bar.setAttribute('data-event-id', String(item.event.id));
600
1048
  bar.setAttribute('data-lane', String(lane));
601
1049
  bar.style.gridRow = String(week + 2); // row 1 is the weekday header
602
- bar.style.gridColumn = `${startCol + 1} / ${endCol + 2}`;
1050
+ bar.style.gridColumn =
1051
+ `${startCol + 1 + this.columnOffset} / ${endCol + 2 + this.columnOffset}`;
603
1052
  bar.style.setProperty('--calendar-event-lane', String(lane));
604
1053
  if (item.event.color)
605
1054
  bar.style.background = item.event.color;
606
- if (item.event.avatar) {
1055
+ if (item.event.avatar && !compact) {
607
1056
  const spec = typeof item.event.avatar === 'string'
608
1057
  ? { src: item.event.avatar }
609
1058
  : item.event.avatar;
@@ -662,7 +1111,8 @@ let SniceCalendar = (() => {
662
1111
  this.grid.appendChild(bar);
663
1112
  }
664
1113
  }
665
- hiddenPerDay.forEach((count, i) => {
1114
+ hiddenPerDay.forEach((hidden, i) => {
1115
+ const count = hidden.length;
666
1116
  if (count === 0)
667
1117
  return;
668
1118
  const cell = this.dayCells[i];
@@ -673,35 +1123,49 @@ let SniceCalendar = (() => {
673
1123
  more.className = 'calendar__more';
674
1124
  more.setAttribute('part', 'more-chip');
675
1125
  more.textContent = `+${count} more`;
676
- // The chip is its own control: it reports its day and hidden count, and
677
- // its clicks never fall through to the day cell's selection handler.
1126
+ // The chip is its own control: it reports its day and hidden count,
1127
+ // opens the day panel unless the app cancels it, and its clicks never
1128
+ // fall through to the day cell's selection handler.
678
1129
  more.setAttribute('role', 'button');
679
1130
  more.setAttribute('tabindex', '0');
1131
+ more.setAttribute('aria-haspopup', 'dialog');
1132
+ more.setAttribute('aria-expanded', 'false');
680
1133
  more.setAttribute('aria-label', `${count} more event${count === 1 ? '' : 's'} on ${date.toLocaleDateString(this.locale, { dateStyle: 'full' })}`);
681
- more.onclick = (e) => this.handleMoreClick(date, count, e);
1134
+ more.onclick = (e) => this.handleMoreClick(date, more, hidden, e);
682
1135
  more.onkeydown = (e) => {
683
1136
  if (e.key !== 'Enter' && e.key !== ' ')
684
1137
  return;
685
1138
  e.preventDefault();
686
- this.handleMoreClick(date, count, e);
1139
+ this.handleMoreClick(date, more, hidden, e);
687
1140
  };
688
1141
  cell.appendChild(more);
689
1142
  });
690
1143
  // Reserve row height for the lane stack (and the "+N more" chip) so
691
1144
  // stripes never spill past their week row — every cell in a week shares
692
1145
  // the reservation, since the grid row is as tall as its tallest cell.
1146
+ // The reservation is only a floor: when the row already has the room it is
1147
+ // left off, so the busy week's row stays level with the rest. That covers
1148
+ // every cell tall enough for the stack it shows; the floor still bites
1149
+ // below 3.5rem (the one lane always drawn) or 4.5rem once the chip claims
1150
+ // its strip — square cells are that short on calendars under ~500px wide.
693
1151
  for (let week = 0; week < weekCount; week++) {
694
1152
  if (weekLaneCounts[week] === 0)
695
1153
  continue;
696
1154
  const weekHasMore = hiddenPerDay
697
- .slice(week * 7, week * 7 + 7).some(count => count > 0);
1155
+ .slice(week * 7, week * 7 + 7).some(hidden => hidden.length > 0);
1156
+ const needed = (SniceCalendar.LANE_STACK_TOP_REM
1157
+ + weekLaneCounts[week] * SniceCalendar.LANE_HEIGHT_REM
1158
+ + (weekHasMore ? SniceCalendar.MORE_CHIP_REM : 0)) * budget.rem;
1159
+ if (budget.height && needed <= budget.height + 0.5)
1160
+ continue;
698
1161
  for (let i = week * 7; i < week * 7 + 7; i++) {
699
1162
  const cell = this.dayCells[i];
700
1163
  if (!cell)
701
1164
  continue;
702
1165
  cell.style.setProperty('--calendar-week-lanes', String(weekLaneCounts[week]));
703
- if (weekHasMore)
704
- cell.style.setProperty('--calendar-week-more', '1rem');
1166
+ if (weekHasMore) {
1167
+ cell.style.setProperty('--calendar-week-more', `${SniceCalendar.MORE_CHIP_REM}rem`);
1168
+ }
705
1169
  }
706
1170
  }
707
1171
  }
@@ -775,7 +1239,13 @@ let SniceCalendar = (() => {
775
1239
  return this.isSameDay(date, new Date());
776
1240
  }
777
1241
  isSelected(date) {
1242
+ // No value means no selection at all — and an unparseable one selects
1243
+ // nothing rather than throwing out of the render.
1244
+ if (!this.value)
1245
+ return false;
778
1246
  const valueDate = typeof this.value === 'string' ? new Date(this.value) : this.value;
1247
+ if (!(valueDate instanceof Date) || isNaN(valueDate.getTime()))
1248
+ return false;
779
1249
  return this.isSameDay(date, valueDate);
780
1250
  }
781
1251
  isDisabled(date) {
@@ -808,9 +1278,13 @@ let SniceCalendar = (() => {
808
1278
  e.stopPropagation();
809
1279
  this.dispatchEventClick(event);
810
1280
  }
811
- handleMoreClick(date, count, e) {
1281
+ handleMoreClick(date, chip, hidden, e) {
812
1282
  e.stopPropagation();
813
- this.dispatchMoreClick(date, count);
1283
+ // The app gets first refusal: preventDefault() means it is opening its own
1284
+ // day view and the built-in panel must stay out of the way.
1285
+ if (!this.dispatchMoreClick(date, hidden.length))
1286
+ return;
1287
+ this.openMorePanel(chip, date, hidden);
814
1288
  }
815
1289
  getMonthDays() {
816
1290
  const year = this.displayDate.getFullYear();
@@ -843,6 +1317,20 @@ let SniceCalendar = (() => {
843
1317
  this.updateView();
844
1318
  }
845
1319
  }
1320
+ /**
1321
+ * The lane budget comes from the cell's height, and that height follows the
1322
+ * calendar's own box — column width in square mode, the host's height when
1323
+ * it has one. Re-stripe when the box actually changes.
1324
+ */
1325
+ handleCalendarResize(entry) {
1326
+ if (!this.grid)
1327
+ return;
1328
+ const box = `${Math.round(entry.contentRect.width)}x${Math.round(entry.contentRect.height)}`;
1329
+ if (box === this.laidOutFor)
1330
+ return;
1331
+ this.laidOutFor = box;
1332
+ this.updateView();
1333
+ }
846
1334
  handleWeekdayInputsChange() {
847
1335
  if (!this.grid)
848
1336
  return;
@@ -850,14 +1338,19 @@ let SniceCalendar = (() => {
850
1338
  const existing = this.grid.querySelectorAll('.calendar__weekday');
851
1339
  existing.forEach(el => el.remove());
852
1340
  const weekdays = this.getWeekdays();
1341
+ // Anchor on the first day cell rather than a child index: the week-number
1342
+ // column adds siblings ahead of it, so a positional index would interleave
1343
+ // the strip with the grid's other children.
1344
+ const firstRow = this.weekNumberCells[0]?.isConnected
1345
+ ? this.weekNumberCells[0] : this.dayCells[0];
1346
+ const anchor = firstRow ?? null;
853
1347
  weekdays.forEach((day, i) => {
854
1348
  const weekdayEl = document.createElement('div');
855
1349
  weekdayEl.className = 'calendar__weekday';
856
1350
  weekdayEl.textContent = day;
857
1351
  weekdayEl.style.gridRow = '1';
858
- weekdayEl.style.gridColumn = String(i + 1);
859
- // Insert before the first day cell so header row stays at the top
860
- this.grid.insertBefore(weekdayEl, this.grid.children[i] || null);
1352
+ weekdayEl.style.gridColumn = String(i + 1 + this.columnOffset);
1353
+ this.grid.insertBefore(weekdayEl, anchor);
861
1354
  });
862
1355
  this.updateView();
863
1356
  }