decantr 0.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 (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,534 @@
1
+ /**
2
+ * Shared rendering primitives for Decantr components.
3
+ * Eliminates duplication across calendar, time, menu, and field components.
4
+ *
5
+ * @module decantr/components/_primitives
6
+ */
7
+ import { createEffect } from '../state/index.js';
8
+ import { tags } from '../tags/index.js';
9
+ import { cx } from './_base.js';
10
+ import { caret, createOverlay, positionPanel } from './_behaviors.js';
11
+
12
+ const { div, button, span } = tags;
13
+
14
+ // ─── CALENDAR RENDERING ──────────────────────────────────────
15
+ // Used by: DatePicker, DateRangePicker
16
+
17
+ const DAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
18
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
19
+
20
+ export { MONTHS };
21
+
22
+ function sameDay(a, b) {
23
+ return a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
24
+ }
25
+
26
+ /**
27
+ * Render a calendar month grid with navigation header.
28
+ *
29
+ * @param {Object} opts
30
+ * @param {Date} opts.viewDate - Month/year to display
31
+ * @param {Date} [opts.selected] - Selected date (highlight)
32
+ * @param {Date} [opts.rangeStart] - Range start for range pickers
33
+ * @param {Date} [opts.rangeEnd] - Range end for range pickers
34
+ * @param {Date} [opts.hoverDate] - Hover date for range preview
35
+ * @param {Function} [opts.isDisabled] - (date) => boolean
36
+ * @param {Function} opts.onSelect - (date) => void
37
+ * @param {Function} [opts.onHover] - (date) => void
38
+ * @param {Function} opts.onNav - (delta) => void — called with -1/+1 for month nav
39
+ * @param {Function} [opts.onTitleClick] - () => void — switch to month view
40
+ * @returns {HTMLElement}
41
+ */
42
+ export function renderCalendar(opts) {
43
+ const { viewDate, selected, rangeStart, rangeEnd, hoverDate, isDisabled, onSelect, onHover, onNav, onTitleClick } = opts;
44
+ const container = div({ class: 'd-datepicker-calendar' });
45
+ const year = viewDate.getFullYear();
46
+ const month = viewDate.getMonth();
47
+
48
+ // Header
49
+ const prevBtn = button({ type: 'button', class: 'd-datepicker-nav-btn', 'aria-label': 'Previous month' }, caret('left'));
50
+ const nextBtn = button({ type: 'button', class: 'd-datepicker-nav-btn', 'aria-label': 'Next month' }, caret('right'));
51
+ const titleBtn = onTitleClick
52
+ ? button({ type: 'button', class: 'd-datepicker-title' }, `${MONTHS[month]} ${year}`)
53
+ : span({ class: 'd-datepicker-title' }, `${MONTHS[month]} ${year}`);
54
+
55
+ prevBtn.addEventListener('click', () => onNav(-1));
56
+ nextBtn.addEventListener('click', () => onNav(1));
57
+ if (onTitleClick) titleBtn.addEventListener('click', onTitleClick);
58
+
59
+ container.appendChild(div({ class: 'd-datepicker-header' },
60
+ div({ class: 'd-datepicker-nav' }, prevBtn),
61
+ titleBtn,
62
+ div({ class: 'd-datepicker-nav' }, nextBtn)
63
+ ));
64
+
65
+ // Grid
66
+ const grid = div({ class: 'd-datepicker-grid', role: 'grid' });
67
+ DAYS.forEach(d => grid.appendChild(div({ class: 'd-datepicker-weekday' }, d)));
68
+
69
+ const firstDay = new Date(year, month, 1).getDay();
70
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
71
+ const daysInPrev = new Date(year, month, 0).getDate();
72
+ const today = new Date();
73
+
74
+ // Effective range for highlighting
75
+ const effStart = rangeStart;
76
+ const effEnd = hoverDate && rangeStart ? (hoverDate >= rangeStart ? hoverDate : rangeStart) : rangeEnd;
77
+ const effMin = effStart && effEnd && effEnd < effStart ? effEnd : effStart;
78
+ const effMax = effStart && effEnd && effEnd < effStart ? effStart : effEnd;
79
+
80
+ function dayClasses(d, outside) {
81
+ const c = ['d-datepicker-day'];
82
+ if (outside) c.push('d-datepicker-day-outside');
83
+ if (sameDay(d, today)) c.push('d-datepicker-day-today');
84
+ if (isDisabled && isDisabled(d)) c.push('d-datepicker-day-disabled');
85
+ if (sameDay(d, selected)) c.push('d-datepicker-day-selected');
86
+ if (effMin && effMax) {
87
+ if (sameDay(d, effMin)) c.push('d-datepicker-day-selected', 'd-datepicker-day-range-start');
88
+ if (sameDay(d, effMax)) c.push('d-datepicker-day-selected', 'd-datepicker-day-range-end');
89
+ const t = d.getTime();
90
+ if (t > effMin.getTime() && t < effMax.getTime()) c.push('d-datepicker-day-in-range');
91
+ }
92
+ return c.join(' ');
93
+ }
94
+
95
+ // Previous month filler
96
+ for (let i = firstDay - 1; i >= 0; i--) {
97
+ const d = new Date(year, month - 1, daysInPrev - i);
98
+ const btn = button({ type: 'button', class: dayClasses(d, true), tabindex: '-1' }, String(daysInPrev - i));
99
+ btn.addEventListener('click', () => onSelect(d));
100
+ grid.appendChild(btn);
101
+ }
102
+
103
+ // Current month
104
+ for (let i = 1; i <= daysInMonth; i++) {
105
+ const d = new Date(year, month, i);
106
+ const dis = isDisabled && isDisabled(d);
107
+ const btn = button({
108
+ type: 'button',
109
+ class: dayClasses(d, false),
110
+ tabindex: '-1',
111
+ disabled: dis ? '' : undefined
112
+ }, String(i));
113
+ if (!dis) {
114
+ btn.addEventListener('click', () => onSelect(d));
115
+ if (onHover) btn.addEventListener('mouseenter', () => onHover(d));
116
+ }
117
+ grid.appendChild(btn);
118
+ }
119
+
120
+ // Next month filler
121
+ const totalCells = firstDay + daysInMonth;
122
+ const remaining = (7 - (totalCells % 7)) % 7;
123
+ for (let i = 1; i <= remaining; i++) {
124
+ const d = new Date(year, month + 1, i);
125
+ const btn = button({ type: 'button', class: dayClasses(d, true), tabindex: '-1' }, String(i));
126
+ btn.addEventListener('click', () => onSelect(d));
127
+ grid.appendChild(btn);
128
+ }
129
+
130
+ container.appendChild(grid);
131
+ return container;
132
+ }
133
+
134
+
135
+ // ─── TIME COLUMNS ────────────────────────────────────────────
136
+ // Used by: TimePicker, TimeRangePicker
137
+
138
+ /**
139
+ * Render scrollable time selection columns.
140
+ *
141
+ * @param {Object} opts
142
+ * @param {number} opts.hours - Selected hour
143
+ * @param {number} opts.minutes - Selected minute
144
+ * @param {number} [opts.seconds] - Selected second (omit to hide seconds column)
145
+ * @param {number} [opts.hourStep=1]
146
+ * @param {number} [opts.minuteStep=1]
147
+ * @param {number} [opts.secondStep=1]
148
+ * @param {boolean} [opts.use12h=false]
149
+ * @param {string} [opts.period='AM'] - AM/PM for 12h mode
150
+ * @param {Function} opts.onChange - ({ hours, minutes, seconds?, period? }) => void
151
+ * @returns {HTMLElement}
152
+ */
153
+ export function renderTimeColumns(opts) {
154
+ const { hours, minutes, seconds, hourStep = 1, minuteStep = 1, secondStep = 1, use12h = false, period = 'AM', onChange } = opts;
155
+
156
+ const container = div({ class: 'd-timepicker-columns' });
157
+
158
+ function createColumn(count, step, selected, startAt, onSelect) {
159
+ const col = div({ class: 'd-timepicker-column' });
160
+ for (let i = startAt; i < count; i += step) {
161
+ const cell = button({
162
+ type: 'button',
163
+ class: cx('d-timepicker-cell', i === selected && 'd-timepicker-cell-selected'),
164
+ tabindex: '-1'
165
+ }, String(i).padStart(2, '0'));
166
+ cell.addEventListener('click', () => {
167
+ onSelect(i);
168
+ col.querySelectorAll('.d-timepicker-cell').forEach(c => c.classList.remove('d-timepicker-cell-selected'));
169
+ cell.classList.add('d-timepicker-cell-selected');
170
+ });
171
+ col.appendChild(cell);
172
+ }
173
+ requestAnimationFrame(() => {
174
+ const sel = col.querySelector('.d-timepicker-cell-selected');
175
+ if (sel && sel.scrollIntoView) sel.scrollIntoView({ block: 'center' });
176
+ });
177
+ return col;
178
+ }
179
+
180
+ // Hour column
181
+ const maxH = use12h ? 13 : 24;
182
+ const startH = use12h ? 1 : 0;
183
+ container.appendChild(createColumn(maxH, hourStep, hours, startH, (v) => {
184
+ onChange({ hours: v, minutes, seconds, period });
185
+ }));
186
+
187
+ // Minute column
188
+ container.appendChild(createColumn(60, minuteStep, minutes, 0, (v) => {
189
+ onChange({ hours, minutes: v, seconds, period });
190
+ }));
191
+
192
+ // Second column (optional)
193
+ if (seconds !== undefined) {
194
+ container.appendChild(createColumn(60, secondStep, seconds, 0, (v) => {
195
+ onChange({ hours, minutes, seconds: v, period });
196
+ }));
197
+ }
198
+
199
+ // AM/PM column (12h mode)
200
+ if (use12h) {
201
+ const periodCol = div({ class: 'd-timepicker-column' });
202
+ ['AM', 'PM'].forEach(p => {
203
+ const cell = button({
204
+ type: 'button',
205
+ class: cx('d-timepicker-cell', p === period && 'd-timepicker-cell-selected'),
206
+ tabindex: '-1'
207
+ }, p);
208
+ cell.addEventListener('click', () => {
209
+ onChange({ hours, minutes, seconds, period: p });
210
+ periodCol.querySelectorAll('.d-timepicker-cell').forEach(c => c.classList.remove('d-timepicker-cell-selected'));
211
+ cell.classList.add('d-timepicker-cell-selected');
212
+ });
213
+ periodCol.appendChild(cell);
214
+ });
215
+ container.appendChild(periodCol);
216
+ }
217
+
218
+ return container;
219
+ }
220
+
221
+
222
+ // ─── MENU ITEMS ──────────────────────────────────────────────
223
+ // Used by: ContextMenu, Dropdown
224
+
225
+ /**
226
+ * Render menu items into a container.
227
+ *
228
+ * @param {HTMLElement} container
229
+ * @param {{ label: string, value?: string, icon?: string|Node, shortcut?: string, disabled?: boolean, separator?: boolean, onclick?: Function }[]} items
230
+ * @param {Object} [opts]
231
+ * @param {Function} [opts.onSelect] - (value|label) => void
232
+ * @param {Function} [opts.onClose] - () => void — close menu after selection
233
+ * @returns {void}
234
+ */
235
+ export function renderMenuItems(container, items, opts = {}) {
236
+ const { onSelect, onClose } = opts;
237
+ container.replaceChildren();
238
+
239
+ items.forEach(item => {
240
+ if (item.separator) {
241
+ container.appendChild(div({ class: 'd-dropdown-separator', role: 'separator' }));
242
+ return;
243
+ }
244
+ const children = [];
245
+ if (item.icon) {
246
+ children.push(typeof item.icon === 'string'
247
+ ? span({ class: 'd-dropdown-item-icon', 'aria-hidden': 'true' }, item.icon)
248
+ : item.icon);
249
+ }
250
+ children.push(span({ class: 'd-dropdown-item-label' }, item.label));
251
+ if (item.shortcut) {
252
+ children.push(span({ class: 'd-dropdown-item-shortcut' }, item.shortcut));
253
+ }
254
+
255
+ const el = div({
256
+ class: cx('d-dropdown-item', item.disabled && 'd-dropdown-item-disabled'),
257
+ role: 'menuitem',
258
+ tabindex: '-1'
259
+ }, ...children);
260
+
261
+ if (!item.disabled) {
262
+ el.addEventListener('click', (e) => {
263
+ e.stopPropagation();
264
+ if (onClose) onClose();
265
+ if (item.onclick) item.onclick(item.value || item.label);
266
+ if (onSelect) onSelect(item.value || item.label);
267
+ });
268
+ }
269
+ container.appendChild(el);
270
+ });
271
+ }
272
+
273
+
274
+ // ─── FIELD STATE ─────────────────────────────────────────────
275
+ // Used by: ALL 14 field components
276
+
277
+ /**
278
+ * Apply reactive field state (error, success, disabled, readonly, variant, size, loading)
279
+ * to a .d-field element. Single source of truth for all field components.
280
+ *
281
+ * @param {HTMLElement} el - The .d-field container element
282
+ * @param {Object} props
283
+ * @param {boolean|string|Function} [props.error]
284
+ * @param {boolean|string|Function} [props.success]
285
+ * @param {boolean|Function} [props.disabled]
286
+ * @param {boolean|Function} [props.readonly]
287
+ * @param {boolean|Function} [props.loading]
288
+ * @param {string} [props.variant='outlined'] - 'outlined'|'filled'|'ghost'
289
+ * @param {string} [props.size] - 'xs'|'sm'|'lg'
290
+ * @returns {{ destroy: Function }}
291
+ */
292
+ export function applyFieldState(el, props = {}) {
293
+ const { error, success, disabled, readonly, loading, variant, size } = props;
294
+
295
+ // Static class setup
296
+ el.classList.add('d-field');
297
+ if (variant && variant !== 'outlined') el.classList.add(`d-field-${variant}`);
298
+ if (size) el.classList.add(`d-field-${size}`);
299
+
300
+ // Reactive error
301
+ if (typeof error === 'function') {
302
+ createEffect(() => {
303
+ const v = error();
304
+ if (v) el.setAttribute('data-error', typeof v === 'string' ? v : '');
305
+ else el.removeAttribute('data-error');
306
+ });
307
+ } else if (error) {
308
+ el.setAttribute('data-error', typeof error === 'string' ? error : '');
309
+ }
310
+
311
+ // Reactive success
312
+ if (typeof success === 'function') {
313
+ createEffect(() => {
314
+ const v = success();
315
+ if (v) el.setAttribute('data-success', typeof v === 'string' ? v : '');
316
+ else el.removeAttribute('data-success');
317
+ });
318
+ } else if (success) {
319
+ el.setAttribute('data-success', typeof success === 'string' ? success : '');
320
+ }
321
+
322
+ // Reactive disabled
323
+ if (typeof disabled === 'function') {
324
+ createEffect(() => {
325
+ const v = disabled();
326
+ if (v) el.setAttribute('data-disabled', '');
327
+ else el.removeAttribute('data-disabled');
328
+ });
329
+ } else if (disabled) {
330
+ el.setAttribute('data-disabled', '');
331
+ }
332
+
333
+ // Reactive readonly
334
+ if (typeof readonly === 'function') {
335
+ createEffect(() => {
336
+ const v = readonly();
337
+ if (v) el.setAttribute('readonly', '');
338
+ else el.removeAttribute('readonly');
339
+ });
340
+ } else if (readonly) {
341
+ el.setAttribute('readonly', '');
342
+ }
343
+
344
+ return { destroy() {} };
345
+ }
346
+
347
+
348
+ // ─── FIELD OVERLAY ───────────────────────────────────────────
349
+ // Used by: Select, Combobox, DatePicker, TimePicker, Cascader, TreeSelect, ColorPicker, Mentions
350
+
351
+ /**
352
+ * Create an overlay with field-standard defaults (matchWidth, portal, placement).
353
+ * Thin wrapper around createOverlay + positionPanel.
354
+ *
355
+ * @param {HTMLElement} triggerEl
356
+ * @param {HTMLElement} panelEl
357
+ * @param {Object} [opts] - Passed to createOverlay, with these defaults:
358
+ * trigger: 'manual', portal: true, matchWidth: true, closeOnEscape: true, closeOnOutside: true
359
+ * @returns {{ open, close, toggle, isOpen, destroy }}
360
+ */
361
+ export function createFieldOverlay(triggerEl, panelEl, opts = {}) {
362
+ return createOverlay(triggerEl, panelEl, {
363
+ trigger: 'manual',
364
+ portal: true,
365
+ matchWidth: true,
366
+ closeOnEscape: true,
367
+ closeOnOutside: true,
368
+ ...opts
369
+ });
370
+ }
371
+
372
+
373
+ // ─── OKLCH COLOR MATH ────────────────────────────────────────
374
+ // Used by: ColorPicker
375
+
376
+ /**
377
+ * Convert hex color to OKLCH components.
378
+ * @param {string} hex - 3, 4, 6, or 8 digit hex color
379
+ * @returns {{ l: number, c: number, h: number, a: number }} l: 0-1, c: 0-0.4, h: 0-360, a: 0-1
380
+ */
381
+ export function hexToOklch(hex) {
382
+ hex = hex.replace('#', '');
383
+ if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
384
+ if (hex.length === 4) hex = hex.split('').map(c => c + c).join('');
385
+ const r = parseInt(hex.substr(0, 2), 16) / 255;
386
+ const g = parseInt(hex.substr(2, 2), 16) / 255;
387
+ const b = parseInt(hex.substr(4, 2), 16) / 255;
388
+ const alpha = hex.length === 8 ? parseInt(hex.substr(6, 2), 16) / 255 : 1;
389
+
390
+ // sRGB → linear RGB
391
+ const toLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
392
+ const lr = toLinear(r), lg = toLinear(g), lb = toLinear(b);
393
+
394
+ // Linear RGB → OKLab
395
+ const l_ = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;
396
+ const m_ = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;
397
+ const s_ = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;
398
+
399
+ const l_c = Math.cbrt(l_), m_c = Math.cbrt(m_), s_c = Math.cbrt(s_);
400
+
401
+ const L = 0.2104542553 * l_c + 0.7936177850 * m_c - 0.0040720468 * s_c;
402
+ const a = 1.9779984951 * l_c - 2.4285922050 * m_c + 0.4505937099 * s_c;
403
+ const bk = 0.0259040371 * l_c + 0.7827717662 * m_c - 0.8086757660 * s_c;
404
+
405
+ const C = Math.sqrt(a * a + bk * bk);
406
+ let H = Math.atan2(bk, a) * (180 / Math.PI);
407
+ if (H < 0) H += 360;
408
+
409
+ return { l: L, c: C, h: H, a: alpha };
410
+ }
411
+
412
+ /**
413
+ * Convert OKLCH to hex color.
414
+ * @param {number} L - Lightness 0-1
415
+ * @param {number} C - Chroma 0-0.4
416
+ * @param {number} H - Hue 0-360
417
+ * @param {number} [A=1] - Alpha 0-1
418
+ * @returns {string} hex color (6-digit or 8-digit when A < 1)
419
+ */
420
+ export function oklchToHex(L, C, H, A = 1) {
421
+ const hRad = H * (Math.PI / 180);
422
+ const a = C * Math.cos(hRad);
423
+ const b = C * Math.sin(hRad);
424
+
425
+ // OKLab → linear RGB via LMS
426
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
427
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
428
+ const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
429
+
430
+ const l_c = l_ * l_ * l_, m_c = m_ * m_ * m_, s_c = s_ * s_ * s_;
431
+
432
+ let lr = +4.0767416621 * l_c - 3.3077115913 * m_c + 0.2309699292 * s_c;
433
+ let lg = -1.2684380046 * l_c + 2.6097574011 * m_c - 0.3413193965 * s_c;
434
+ let lb = -0.0041960863 * l_c - 0.7034186147 * m_c + 1.7076147010 * s_c;
435
+
436
+ // Clamp
437
+ lr = Math.max(0, Math.min(1, lr));
438
+ lg = Math.max(0, Math.min(1, lg));
439
+ lb = Math.max(0, Math.min(1, lb));
440
+
441
+ // Linear → sRGB
442
+ const toSrgb = (c) => c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
443
+ const toHex = (c) => Math.round(Math.max(0, Math.min(255, toSrgb(c) * 255))).toString(16).padStart(2, '0');
444
+
445
+ const rgb = `#${toHex(lr)}${toHex(lg)}${toHex(lb)}`;
446
+ if (A < 1) {
447
+ const aHex = Math.round(Math.max(0, Math.min(255, A * 255))).toString(16).padStart(2, '0');
448
+ return `${rgb}${aHex}`;
449
+ }
450
+ return rgb;
451
+ }
452
+
453
+
454
+ // ─── COLOR HARMONY GENERATION ───────────────────────────────
455
+ // Used by: ColorPalette, Theme Studio
456
+
457
+ /**
458
+ * Generate harmonious colors from a base hex using OKLCH color math.
459
+ * @param {string} baseHex - Base color as hex string
460
+ * @param {string} type - Harmony type
461
+ * @param {number} count - Desired number of colors (2-12)
462
+ * @returns {string[]} Array of hex colors
463
+ */
464
+ export function generateHarmony(baseHex, type, count) {
465
+ if (type === 'custom' || !baseHex) return [];
466
+ const base = hexToOklch(baseHex);
467
+ const H = base.h, L = base.l, C = base.c;
468
+
469
+ // Anchor hues per harmony type
470
+ const anchors = {
471
+ monochromatic: [0],
472
+ analogous: [0, 30, -30],
473
+ complementary: [0, 180],
474
+ 'split-complementary': [0, 150, 210],
475
+ triadic: [0, 120, 240],
476
+ tetradic: [0, 90, 180, 270],
477
+ square: [0, 90, 180, 270],
478
+ };
479
+
480
+ const hueOffsets = anchors[type] || anchors.complementary;
481
+
482
+ // Distribute count across anchor hues with L/C variation
483
+ const colors = [];
484
+ for (let i = 0; i < count; i++) {
485
+ const anchorIdx = i % hueOffsets.length;
486
+ const hue = (H + hueOffsets[anchorIdx] + 360) % 360;
487
+ // Vary lightness and chroma for visual interest
488
+ const variation = Math.floor(i / hueOffsets.length);
489
+ let lShift, cScale;
490
+ if (type === 'monochromatic') {
491
+ // Spread L from 0.2 to 0.85 evenly
492
+ const t = count > 1 ? i / (count - 1) : 0.5;
493
+ lShift = 0.2 + t * 0.65 - L;
494
+ cScale = 0.7 + 0.6 * Math.sin(t * Math.PI); // bell curve chroma
495
+ } else {
496
+ lShift = variation * 0.12 * (variation % 2 === 0 ? 1 : -1);
497
+ cScale = 1 - variation * 0.15;
498
+ }
499
+ const newL = Math.max(0.15, Math.min(0.85, L + lShift));
500
+ const newC = Math.max(0.01, Math.min(0.37, C * Math.max(0.3, cScale)));
501
+ colors.push(oklchToHex(newL, newC, hue));
502
+ }
503
+ return colors;
504
+ }
505
+
506
+ /**
507
+ * Generate shade strip for a color.
508
+ * @param {string} hex - Base hex color
509
+ * @param {number} [steps=5] - Number of shades
510
+ * @returns {string[]} Array of hex shade strings
511
+ */
512
+ export function generateShades(hex, steps = 5) {
513
+ const base = hexToOklch(hex);
514
+ const shades = [];
515
+ for (let i = 0; i < steps; i++) {
516
+ const t = steps > 1 ? i / (steps - 1) : 0.5;
517
+ const L = 0.15 + t * 0.70;
518
+ // Bell curve chroma modulation — max chroma at midpoint
519
+ const cMod = 0.5 + 0.5 * Math.sin(t * Math.PI);
520
+ const C = Math.max(0.01, base.c * cMod);
521
+ shades.push(oklchToHex(L, C, base.h));
522
+ }
523
+ return shades;
524
+ }
525
+
526
+ /**
527
+ * Pick accessible foreground color for a swatch.
528
+ * @param {string} hex - Background hex color
529
+ * @returns {string} Black or white hex
530
+ */
531
+ export function pickSwatchForeground(hex) {
532
+ const { l } = hexToOklch(hex);
533
+ return l > 0.6 ? '#09090b' : '#ffffff';
534
+ }