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,82 @@
1
+ import { createEffect } from '../state/index.js';
2
+ import { tags } from '../tags/index.js';
3
+ import { injectBase, cx } from './_base.js';
4
+ import { createFormField } from './_behaviors.js';
5
+ import { applyFieldState } from './_primitives.js';
6
+
7
+ const { div, textarea: textareaTag } = tags;
8
+
9
+ /**
10
+ * @param {Object} [props]
11
+ * @param {string} [props.placeholder]
12
+ * @param {string|Function} [props.value]
13
+ * @param {boolean|Function} [props.disabled]
14
+ * @param {boolean|Function} [props.readonly]
15
+ * @param {boolean|string|Function} [props.error]
16
+ * @param {boolean|string|Function} [props.success]
17
+ * @param {string} [props.variant='outlined'] - 'outlined'|'filled'|'ghost'
18
+ * @param {string} [props.size] - xs|sm|lg
19
+ * @param {number} [props.rows=3]
20
+ * @param {string} [props.resize='vertical'] - 'none'|'vertical'|'horizontal'|'both'
21
+ * @param {string} [props.label]
22
+ * @param {string} [props.help]
23
+ * @param {boolean} [props.required]
24
+ * @param {Function} [props.oninput]
25
+ * @param {Function} [props.ref]
26
+ * @param {string} [props['aria-label']]
27
+ * @param {string} [props.class]
28
+ * @returns {HTMLElement}
29
+ */
30
+ export function Textarea(props = {}) {
31
+ injectBase();
32
+
33
+ const {
34
+ placeholder, value, disabled, readonly, error, success,
35
+ variant, size, rows = 3, resize = 'vertical',
36
+ label, help, required, oninput, ref,
37
+ 'aria-label': ariaLabel, class: cls
38
+ } = props;
39
+
40
+ const textareaProps = { class: 'd-textarea', rows };
41
+ if (placeholder) textareaProps.placeholder = placeholder;
42
+ if (readonly && typeof readonly !== 'function') textareaProps.readonly = '';
43
+ if (oninput) textareaProps.oninput = oninput;
44
+ if (ariaLabel) textareaProps['aria-label'] = ariaLabel;
45
+ if (required) textareaProps.required = '';
46
+
47
+ const textareaEl = textareaTag(textareaProps);
48
+ // resize is runtime-derived from prop
49
+ textareaEl.style.resize = resize;
50
+
51
+ if (ref) ref(textareaEl);
52
+
53
+ // Reactive value
54
+ if (typeof value === 'function') {
55
+ createEffect(() => { textareaEl.value = value(); });
56
+ } else if (value !== undefined) {
57
+ textareaEl.value = value;
58
+ }
59
+
60
+ // Reactive disabled
61
+ if (typeof disabled === 'function') {
62
+ createEffect(() => { textareaEl.disabled = disabled(); });
63
+ } else if (disabled) {
64
+ textareaEl.disabled = true;
65
+ }
66
+
67
+ // Reactive readonly
68
+ if (typeof readonly === 'function') {
69
+ createEffect(() => { textareaEl.readOnly = readonly(); });
70
+ }
71
+
72
+ const wrap = div({ class: cx('d-textarea-wrap', cls) }, textareaEl);
73
+
74
+ applyFieldState(wrap, { error, success, disabled, readonly, variant, size });
75
+
76
+ if (label || help) {
77
+ const { wrapper } = createFormField(wrap, { label, error, help, required, success, variant, size });
78
+ return wrapper;
79
+ }
80
+
81
+ return wrap;
82
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * TimePicker — Time selection with scrollable hour/minute/second columns.
3
+ * Uses renderTimeColumns primitive, createFieldOverlay behavior.
4
+ *
5
+ * @module decantr/components/time-picker
6
+ */
7
+ import { onDestroy } from '../core/index.js';
8
+ import { createEffect } from '../state/index.js';
9
+ import { tags } from '../tags/index.js';
10
+ import { injectBase, cx } from './_base.js';
11
+ import { createFormField } from './_behaviors.js';
12
+ import { icon } from './icon.js';
13
+ import { applyFieldState, createFieldOverlay, renderTimeColumns } from './_primitives.js';
14
+
15
+ const { div, button: buttonTag, span } = tags;
16
+
17
+ /**
18
+ * @param {Object} [props]
19
+ * @param {string|Function} [props.value]
20
+ * @param {string} [props.placeholder='Select time']
21
+ * @param {boolean} [props.seconds=false]
22
+ * @param {boolean} [props.use12h=false]
23
+ * @param {number} [props.hourStep=1]
24
+ * @param {number} [props.minuteStep=1]
25
+ * @param {number} [props.secondStep=1]
26
+ * @param {boolean|Function} [props.disabled]
27
+ * @param {boolean|string|Function} [props.error]
28
+ * @param {boolean|string|Function} [props.success]
29
+ * @param {string} [props.variant='outlined'] - 'outlined'|'filled'|'ghost'
30
+ * @param {string} [props.size] - xs|sm|lg
31
+ * @param {string} [props.label]
32
+ * @param {string} [props.help]
33
+ * @param {boolean} [props.required]
34
+ * @param {Function} [props.onchange]
35
+ * @param {string} [props.class]
36
+ * @returns {HTMLElement}
37
+ */
38
+ export function TimePicker(props = {}) {
39
+ injectBase();
40
+ const {
41
+ value, placeholder = 'Select time', seconds = false, use12h = false,
42
+ hourStep = 1, minuteStep = 1, secondStep = 1, disabled,
43
+ error, success, variant, size, label, help, required, onchange, class: cls
44
+ } = props;
45
+
46
+ let _h = 0, _m = 0, _s = 0, _period = 'AM';
47
+
48
+ function parseTime(v) {
49
+ if (!v) return;
50
+ const parts = v.split(':').map(Number);
51
+ _h = parts[0] || 0;
52
+ _m = parts[1] || 0;
53
+ _s = parts[2] || 0;
54
+ if (use12h) {
55
+ _period = _h >= 12 ? 'PM' : 'AM';
56
+ _h = _h % 12 || 12;
57
+ }
58
+ }
59
+
60
+ function formatTime() {
61
+ let hour = _h;
62
+ if (use12h) hour = _period === 'PM' ? (_h === 12 ? 12 : _h + 12) : (_h === 12 ? 0 : _h);
63
+ const hh = String(hour).padStart(2, '0');
64
+ const mm = String(_m).padStart(2, '0');
65
+ if (seconds) return `${hh}:${mm}:${String(_s).padStart(2, '0')}`;
66
+ return `${hh}:${mm}`;
67
+ }
68
+
69
+ function displayTime() {
70
+ const hh = String(_h).padStart(2, '0');
71
+ const mm = String(_m).padStart(2, '0');
72
+ let t = `${hh}:${mm}`;
73
+ if (seconds) t += `:${String(_s).padStart(2, '0')}`;
74
+ if (use12h) t += ` ${_period}`;
75
+ return t;
76
+ }
77
+
78
+ parseTime(typeof value === 'function' ? value() : value);
79
+
80
+ const displayEl = span({ class: 'd-select-display' });
81
+ const trigger = buttonTag({
82
+ type: 'button',
83
+ class: 'd-select',
84
+ 'aria-haspopup': 'dialog',
85
+ 'aria-expanded': 'false'
86
+ }, displayEl, icon('clock', { size: '1em', class: 'd-select-arrow' }));
87
+
88
+ const panel = div({ class: 'd-timepicker-panel' });
89
+ const wrap = div({ class: cx('d-timepicker', cls) }, trigger, panel);
90
+
91
+ applyFieldState(wrap, { error, success, disabled, variant, size });
92
+
93
+ function updateDisplay() {
94
+ const initVal = typeof value === 'function' ? value() : value;
95
+ displayEl.textContent = initVal || _h || _m || _s ? displayTime() : placeholder;
96
+ if (!initVal && !_h && !_m && !_s) displayEl.classList.add('d-select-placeholder');
97
+ else displayEl.classList.remove('d-select-placeholder');
98
+ }
99
+
100
+ function emitChange() {
101
+ updateDisplay();
102
+ if (onchange) onchange(formatTime());
103
+ }
104
+
105
+ function renderPanel() {
106
+ panel.replaceChildren();
107
+ panel.appendChild(renderTimeColumns({
108
+ hours: _h,
109
+ minutes: _m,
110
+ seconds: seconds ? _s : undefined,
111
+ hourStep,
112
+ minuteStep,
113
+ secondStep,
114
+ use12h,
115
+ period: _period,
116
+ onChange: (vals) => {
117
+ _h = vals.hours;
118
+ _m = vals.minutes;
119
+ if (vals.seconds !== undefined) _s = vals.seconds;
120
+ if (vals.period) _period = vals.period;
121
+ emitChange();
122
+ }
123
+ }));
124
+ }
125
+
126
+ const overlay = createFieldOverlay(trigger, panel, {
127
+ trigger: 'click',
128
+ matchWidth: false,
129
+ onOpen: renderPanel
130
+ });
131
+
132
+ updateDisplay();
133
+
134
+ if (typeof value === 'function') {
135
+ createEffect(() => { parseTime(value()); updateDisplay(); });
136
+ }
137
+
138
+ // Reactive disabled
139
+ if (typeof disabled === 'function') {
140
+ createEffect(() => { trigger.disabled = disabled(); });
141
+ } else if (disabled) {
142
+ trigger.disabled = true;
143
+ }
144
+
145
+ onDestroy(() => { overlay.destroy(); });
146
+
147
+ if (label || help) {
148
+ const { wrapper } = createFormField(wrap, { label, error, help, required, success, variant, size });
149
+ return wrapper;
150
+ }
151
+
152
+ return wrap;
153
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * TimeRangePicker — Two time selectors for start/end time range.
3
+ * Uses renderTimeColumns primitive, createFieldOverlay behavior.
4
+ *
5
+ * @module decantr/components/time-range-picker
6
+ */
7
+ import { onDestroy } from '../core/index.js';
8
+ import { createEffect } from '../state/index.js';
9
+ import { tags } from '../tags/index.js';
10
+ import { injectBase, cx } from './_base.js';
11
+ import { createFormField } from './_behaviors.js';
12
+ import { applyFieldState, createFieldOverlay, renderTimeColumns } from './_primitives.js';
13
+
14
+ const { div, button: buttonTag, span } = tags;
15
+
16
+ /**
17
+ * @param {Object} [props]
18
+ * @param {Array<string>|Function} [props.value] - ['09:00', '17:00']
19
+ * @param {string} [props.placeholder='Select time range']
20
+ * @param {Function} [props.onchange]
21
+ * @param {boolean|Function} [props.disabled]
22
+ * @param {boolean|string|Function} [props.error]
23
+ * @param {boolean|string|Function} [props.success]
24
+ * @param {string} [props.variant='outlined'] - 'outlined'|'filled'|'ghost'
25
+ * @param {string} [props.size] - xs|sm|lg
26
+ * @param {string} [props.label]
27
+ * @param {string} [props.help]
28
+ * @param {boolean} [props.required]
29
+ * @param {string} [props.class]
30
+ * @returns {HTMLElement}
31
+ */
32
+ export function TimeRangePicker(props = {}) {
33
+ injectBase();
34
+ const {
35
+ value, placeholder = 'Select time range', onchange,
36
+ disabled, error, success, variant, size, label, help, required, class: cls
37
+ } = props;
38
+
39
+ let _sh = 9, _sm = 0, _eh = 17, _em = 0;
40
+ let _hasValue = false;
41
+
42
+ function parseValue(v) {
43
+ if (!v || !Array.isArray(v) || v.length < 2) return;
44
+ const sp = (v[0] || '').split(':').map(Number);
45
+ const ep = (v[1] || '').split(':').map(Number);
46
+ _sh = sp[0] || 0; _sm = sp[1] || 0;
47
+ _eh = ep[0] || 0; _em = ep[1] || 0;
48
+ _hasValue = true;
49
+ }
50
+
51
+ parseValue(typeof value === 'function' ? value() : value);
52
+
53
+ function formatTime(hh, mm) {
54
+ return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;
55
+ }
56
+
57
+ function toMinutes(hh, mm) { return hh * 60 + mm; }
58
+ function isValid() { return toMinutes(_eh, _em) > toMinutes(_sh, _sm); }
59
+
60
+ // Display
61
+ const displayEl = span({ class: 'd-select-display' });
62
+ const trigger = buttonTag({
63
+ type: 'button',
64
+ class: 'd-select',
65
+ 'aria-haspopup': 'dialog',
66
+ 'aria-expanded': 'false'
67
+ }, displayEl, span({ class: 'd-select-arrow' }, '\u23F0'));
68
+
69
+ const panel = div({ class: 'd-timerange-panel' });
70
+ const wrap = div({ class: cx('d-timerange', cls) }, trigger, panel);
71
+
72
+ applyFieldState(wrap, { error, success, disabled, variant, size });
73
+
74
+ function updateDisplay() {
75
+ if (_hasValue) {
76
+ displayEl.textContent = `${formatTime(_sh, _sm)} — ${formatTime(_eh, _em)}`;
77
+ displayEl.classList.remove('d-select-placeholder');
78
+ } else {
79
+ displayEl.textContent = placeholder;
80
+ displayEl.classList.add('d-select-placeholder');
81
+ }
82
+ }
83
+
84
+ function emitChange() {
85
+ _hasValue = true;
86
+ updateDisplay();
87
+ if (onchange) onchange([formatTime(_sh, _sm), formatTime(_eh, _em)]);
88
+ }
89
+
90
+ let errorEl = null;
91
+
92
+ function renderValidation() {
93
+ if (!errorEl) return;
94
+ errorEl.textContent = isValid() ? '' : 'End time must be after start time';
95
+ }
96
+
97
+ function renderPanel() {
98
+ panel.replaceChildren();
99
+
100
+ // Start time columns
101
+ const startLabel = div({ class: 'd-timerange-label' }, 'Start');
102
+ const startCols = renderTimeColumns({
103
+ hours: _sh,
104
+ minutes: _sm,
105
+ onChange: (vals) => {
106
+ _sh = vals.hours;
107
+ _sm = vals.minutes;
108
+ renderValidation();
109
+ emitChange();
110
+ }
111
+ });
112
+ const startSection = div({ class: 'd-timerange-section' }, startLabel, startCols);
113
+
114
+ const divider = div({ class: 'd-timerange-divider' }, '\u2014');
115
+
116
+ // End time columns
117
+ const endLabel = div({ class: 'd-timerange-label' }, 'End');
118
+ const endCols = renderTimeColumns({
119
+ hours: _eh,
120
+ minutes: _em,
121
+ onChange: (vals) => {
122
+ _eh = vals.hours;
123
+ _em = vals.minutes;
124
+ renderValidation();
125
+ emitChange();
126
+ }
127
+ });
128
+ const endSection = div({ class: 'd-timerange-section' }, endLabel, endCols);
129
+
130
+ errorEl = div({ class: 'd-timerange-error', role: 'alert' });
131
+
132
+ const row = div({ class: 'd-timerange-row' }, startSection, divider, endSection);
133
+ panel.appendChild(row);
134
+ panel.appendChild(errorEl);
135
+ renderValidation();
136
+ }
137
+
138
+ const overlay = createFieldOverlay(trigger, panel, {
139
+ trigger: 'click',
140
+ matchWidth: false,
141
+ onOpen: renderPanel
142
+ });
143
+
144
+ // Reactive disabled
145
+ if (typeof disabled === 'function') {
146
+ createEffect(() => {
147
+ trigger.disabled = disabled();
148
+ trigger.setAttribute('aria-disabled', String(!!disabled()));
149
+ });
150
+ } else if (disabled) {
151
+ trigger.disabled = true;
152
+ trigger.setAttribute('aria-disabled', 'true');
153
+ }
154
+
155
+ // Reactive value
156
+ if (typeof value === 'function') {
157
+ createEffect(() => { parseValue(value()); updateDisplay(); });
158
+ }
159
+
160
+ onDestroy(() => { overlay.destroy(); });
161
+
162
+ updateDisplay();
163
+
164
+ if (label || help) {
165
+ const { wrapper } = createFormField(wrap, { label, error, help, required, success, variant, size });
166
+ return wrapper;
167
+ }
168
+
169
+ return wrap;
170
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Timeline — Vertical/horizontal sequence of events with connecting lines.
3
+ *
4
+ * @module decantr/components/timeline
5
+ */
6
+ import { h } from '../core/index.js';
7
+ import { createSignal, createEffect } from '../state/index.js';
8
+ import { injectBase, cx } from './_base.js';
9
+
10
+ /**
11
+ * @param {Object} [props]
12
+ * @param {{ label?: string, content: string|Node, color?: string, icon?: string|Node, time?: string, status?: 'default'|'success'|'warning'|'error'|'info', tag?: string, position?: 'left'|'right', collapsible?: boolean, defaultOpen?: boolean, onclick?: Function, disabled?: boolean }[]} [props.items]
13
+ * @param {'left'|'right'|'alternate'|'custom'} [props.mode='left']
14
+ * @param {boolean|string} [props.pending=false] - Show pending last item
15
+ * @param {string|Node} [props.pendingDot] - Custom dot for pending item
16
+ * @param {'default'|'branded'} [props.variant='default']
17
+ * @param {string} [props.size] - sm|lg
18
+ * @param {number|Function} [props.active] - Index of active item (reactive)
19
+ * @param {boolean} [props.gradient=false] - Gradient connector line
20
+ * @param {boolean} [props.glass=false] - Glass background on dots
21
+ * @param {boolean} [props.reverse=false] - Reverse item order
22
+ * @param {'vertical'|'horizontal'} [props.direction='vertical'] - Layout direction
23
+ * @param {boolean|Function} [props.loading=false] - Skeleton loading state (reactive)
24
+ * @param {number} [props.loadingCount=3] - Skeleton item count
25
+ * @param {Function} [props.onClick] - Global click handler (item, index)
26
+ * @param {string} [props.class]
27
+ * @returns {HTMLElement}
28
+ */
29
+ export function Timeline(props = {}) {
30
+ injectBase();
31
+ const {
32
+ items = [], mode = 'left', pending = false, pendingDot,
33
+ variant = 'default', size, active, gradient = false, glass = false,
34
+ reverse = false, direction = 'vertical', loading = false,
35
+ loadingCount = 3, onClick, class: cls
36
+ } = props;
37
+
38
+ const isBranded = variant === 'branded';
39
+ const isHorizontal = direction === 'horizontal';
40
+
41
+ const container = h('div', {
42
+ class: cx('d-timeline',
43
+ mode === 'alternate' && 'd-timeline-alternate',
44
+ mode === 'right' && 'd-timeline-right',
45
+ mode === 'custom' && 'd-timeline-custom',
46
+ isHorizontal && 'd-timeline-horizontal',
47
+ size && `d-timeline-${size}`,
48
+ isBranded && 'd-timeline-lg d-timeline-gradient d-timeline-glass',
49
+ !isBranded && gradient && 'd-timeline-gradient',
50
+ !isBranded && glass && 'd-timeline-glass',
51
+ cls
52
+ )
53
+ });
54
+
55
+ // Skeleton loading state
56
+ if (typeof loading === 'function') {
57
+ const [content, setContent] = createSignal(null);
58
+ createEffect(() => {
59
+ const isLoading = loading();
60
+ container.replaceChildren();
61
+ if (isLoading) {
62
+ renderSkeleton(container, loadingCount);
63
+ } else {
64
+ renderItems(container, items, { mode, pending, pendingDot, active, reverse, isHorizontal, onClick, isBranded, gradient });
65
+ }
66
+ });
67
+ return container;
68
+ }
69
+
70
+ if (loading) {
71
+ renderSkeleton(container, loadingCount);
72
+ } else {
73
+ renderItems(container, items, { mode, pending, pendingDot, active, reverse, isHorizontal, onClick, isBranded, gradient });
74
+ }
75
+
76
+ return container;
77
+ }
78
+
79
+ /**
80
+ * Render timeline items into the container.
81
+ */
82
+ function renderItems(container, items, opts) {
83
+ const { mode, pending, pendingDot, active, reverse, isHorizontal, onClick, isBranded, gradient } = opts;
84
+ const ordered = reverse ? [...items].reverse() : items;
85
+ const dots = [];
86
+
87
+ ordered.forEach((item, i) => {
88
+ const itemDisabled = item.disabled;
89
+ const itemClickable = !itemDisabled && (item.onclick || onClick);
90
+
91
+ const el = h('div', {
92
+ class: cx('d-timeline-item',
93
+ mode === 'custom' && item.position === 'right' && 'd-timeline-item-right',
94
+ mode === 'custom' && item.position !== 'right' && 'd-timeline-item-left',
95
+ itemClickable && 'd-timeline-item-clickable',
96
+ itemDisabled && 'd-timeline-item-disabled'
97
+ )
98
+ });
99
+
100
+ if (itemClickable) {
101
+ el.addEventListener('click', () => {
102
+ if (item.onclick) item.onclick(item, i);
103
+ if (onClick) onClick(item, i);
104
+ });
105
+ }
106
+
107
+ // Dot
108
+ const dot = h('div', { class: cx('d-timeline-dot', item.icon && 'd-timeline-dot-lg') });
109
+ if (item.icon) {
110
+ if (typeof item.icon === 'string') dot.textContent = item.icon;
111
+ else dot.appendChild(item.icon);
112
+ }
113
+ if (item.status && item.status !== 'default') dot.dataset.status = item.status;
114
+ if (item.color && !item.status) dot.dataset.color = item.color;
115
+ el.appendChild(dot);
116
+ dots.push(dot);
117
+
118
+ // Line (not on last item unless pending)
119
+ if (i < ordered.length - 1 || pending) {
120
+ el.appendChild(h('div', { class: 'd-timeline-line' }));
121
+ }
122
+
123
+ // Opposite label for alternate mode
124
+ if (mode === 'alternate' && item.label) {
125
+ const opposite = h('div', { class: 'd-timeline-opposite' }, item.label);
126
+ // In alternate mode, insert opposite before or after content depending on even/odd
127
+ el.insertBefore(opposite, el.firstChild);
128
+ }
129
+
130
+ // Content
131
+ const content = h('div', { class: 'd-timeline-content' });
132
+ if (item.time) content.appendChild(h('div', { class: 'd-timeline-label' }, item.time));
133
+ if (item.tag) {
134
+ content.appendChild(h('span', { class: 'd-timeline-tag' }, item.tag));
135
+ }
136
+
137
+ if (item.collapsible) {
138
+ // Collapsible content
139
+ const [open, setOpen] = createSignal(item.defaultOpen !== false);
140
+ const trigger = h('div', { class: 'd-timeline-collapse-trigger' });
141
+ createEffect(() => {
142
+ trigger.textContent = open() ? '▾ Collapse' : '▸ Expand';
143
+ });
144
+ trigger.addEventListener('click', (e) => {
145
+ e.stopPropagation();
146
+ setOpen(!open());
147
+ });
148
+
149
+ const region = h('div', { class: 'd-timeline-collapse-region', role: 'region' });
150
+ if (typeof item.content === 'string') {
151
+ region.appendChild(h('div', null, item.content));
152
+ } else if (item.content?.nodeType) {
153
+ region.appendChild(item.content);
154
+ }
155
+
156
+ createEffect(() => {
157
+ region.style.maxHeight = open() ? 'none' : '0px';
158
+ region.style.overflow = open() ? 'visible' : 'hidden';
159
+ });
160
+
161
+ content.appendChild(trigger);
162
+ content.appendChild(region);
163
+ } else {
164
+ if (typeof item.content === 'string') {
165
+ content.appendChild(h('div', null, item.content));
166
+ } else if (item.content?.nodeType) {
167
+ content.appendChild(item.content);
168
+ }
169
+ }
170
+
171
+ el.appendChild(content);
172
+ container.appendChild(el);
173
+ });
174
+
175
+ // Active item highlight
176
+ if (active != null) {
177
+ const applyActive = (idx) => {
178
+ dots.forEach((d, i) => {
179
+ d.classList.toggle('d-timeline-dot-active', i === idx);
180
+ });
181
+ };
182
+ if (typeof active === 'function') {
183
+ createEffect(() => applyActive(active()));
184
+ } else {
185
+ applyActive(active);
186
+ }
187
+ }
188
+
189
+ // Pending item
190
+ if (pending) {
191
+ const pendingItem = h('div', { class: 'd-timeline-item' });
192
+ const dot = h('div', { class: 'd-timeline-dot d-timeline-dot-pending' });
193
+ if (pendingDot) {
194
+ if (typeof pendingDot === 'string') dot.textContent = pendingDot;
195
+ else if (pendingDot.nodeType) dot.appendChild(pendingDot);
196
+ }
197
+ pendingItem.appendChild(dot);
198
+ pendingItem.appendChild(h('div', { class: 'd-timeline-content' },
199
+ h('div', { class: 'd-timeline-label' }, typeof pending === 'string' ? pending : 'Loading...')
200
+ ));
201
+ container.appendChild(pendingItem);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Render skeleton loading items.
207
+ */
208
+ function renderSkeleton(container, count) {
209
+ container.classList.add('d-timeline-skeleton');
210
+ for (let i = 0; i < count; i++) {
211
+ const el = h('div', { class: 'd-timeline-item' });
212
+ el.appendChild(h('div', { class: 'd-timeline-dot' }));
213
+ if (i < count - 1) {
214
+ el.appendChild(h('div', { class: 'd-timeline-line' }));
215
+ }
216
+ const content = h('div', { class: 'd-timeline-content' });
217
+ const bar1 = h('div', { class: 'd-timeline-skel-bar' });
218
+ bar1.style.width = `${60 + Math.random() * 30}%`;
219
+ const bar2 = h('div', { class: 'd-timeline-skel-bar' });
220
+ bar2.style.width = `${40 + Math.random() * 40}%`;
221
+ content.appendChild(bar1);
222
+ content.appendChild(bar2);
223
+ el.appendChild(content);
224
+ container.appendChild(el);
225
+ }
226
+ }
@@ -0,0 +1,71 @@
1
+ import { h } from '../core/index.js';
2
+ import { injectBase, cx } from './_base.js';
3
+
4
+ let containerCache = {};
5
+
6
+ function getContainer(position) {
7
+ if (containerCache[position]) return containerCache[position];
8
+ if (typeof document === 'undefined') return null;
9
+
10
+ const el = h('div', { class: cx('d-toast-container', `d-toast-${position}`) });
11
+ document.body.appendChild(el);
12
+ containerCache[position] = el;
13
+ return el;
14
+ }
15
+
16
+ /**
17
+ * Show a toast notification.
18
+ * @param {Object} props
19
+ * @param {string} props.message
20
+ * @param {string} [props.variant] - info|success|warning|error (default: info)
21
+ * @param {number} [props.duration] - Auto-dismiss in ms (default: 3000, 0 = manual)
22
+ * @param {string} [props.position] - top-right|top-left|bottom-right|bottom-left (default: top-right)
23
+ * @returns {{ dismiss: Function }}
24
+ */
25
+ export function toast(props = {}) {
26
+ injectBase();
27
+
28
+ const {
29
+ message,
30
+ variant = 'info',
31
+ duration = 3000,
32
+ position = 'top-right'
33
+ } = props;
34
+
35
+ const container = getContainer(position);
36
+ if (!container) return { dismiss: () => {} };
37
+
38
+ const toastClass = cx('d-toast', `d-toast-${variant}`);
39
+ const el = h('div', { class: toastClass, role: 'status', 'aria-live': 'polite' },
40
+ h('span', { class: 'd-toast-message' }, message),
41
+ h('button', {
42
+ class: 'd-toast-close',
43
+ 'aria-label': 'Dismiss',
44
+ type: 'button'
45
+ }, '\u00d7')
46
+ );
47
+
48
+ const dismiss = () => {
49
+ el.classList.add('d-toast-exit');
50
+ setTimeout(() => {
51
+ if (el.parentNode) el.parentNode.removeChild(el);
52
+ }, 200);
53
+ };
54
+
55
+ el.querySelector('.d-toast-close').addEventListener('click', dismiss);
56
+ container.appendChild(el);
57
+
58
+ if (duration > 0) {
59
+ setTimeout(dismiss, duration);
60
+ }
61
+
62
+ return { dismiss };
63
+ }
64
+
65
+ /** Reset toast containers (for testing) */
66
+ export function resetToasts() {
67
+ for (const el of Object.values(containerCache)) {
68
+ if (el.parentNode) el.parentNode.removeChild(el);
69
+ }
70
+ containerCache = {};
71
+ }