@idyeah/diya 0.0.0 → 1.0.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 (190) hide show
  1. package/CHANGELOG.md +317 -0
  2. package/NOTICE +29 -0
  3. package/README.md +596 -5
  4. package/dist/DiyaProvider.d.ts +79 -0
  5. package/dist/DiyaProvider.js +77 -0
  6. package/dist/Icon.d.ts +10 -0
  7. package/dist/Icon.js +27 -0
  8. package/dist/accordion/Accordion.d.ts +34 -0
  9. package/dist/accordion/Accordion.js +15 -0
  10. package/dist/alert/Alert.d.ts +49 -0
  11. package/dist/alert/Alert.js +11 -0
  12. package/dist/alert/messages.d.ts +9 -0
  13. package/dist/alert/messages.js +2 -0
  14. package/dist/api.json +7277 -0
  15. package/dist/api.schema.json +214 -0
  16. package/dist/application-patterns/Chatbot.d.ts +114 -0
  17. package/dist/application-patterns/Chatbot.js +110 -0
  18. package/dist/application-patterns/SideNavigation.d.ts +74 -0
  19. package/dist/application-patterns/SideNavigation.js +31 -0
  20. package/dist/application-patterns/chatbot-messages.d.ts +20 -0
  21. package/dist/application-patterns/chatbot-messages.js +2 -0
  22. package/dist/application-patterns/side-navigation-messages.d.ts +5 -0
  23. package/dist/application-patterns/side-navigation-messages.js +2 -0
  24. package/dist/avatar/Avatar.d.ts +32 -0
  25. package/dist/avatar/Avatar.js +19 -0
  26. package/dist/avatar/messages.d.ts +7 -0
  27. package/dist/avatar/messages.js +2 -0
  28. package/dist/badge/Badge.d.ts +42 -0
  29. package/dist/badge/Badge.js +16 -0
  30. package/dist/badge/badge-utils.d.ts +6 -0
  31. package/dist/badge/badge-utils.js +24 -0
  32. package/dist/badge/messages.d.ts +4 -0
  33. package/dist/badge/messages.js +2 -0
  34. package/dist/breadcrumbs/Breadcrumbs.d.ts +34 -0
  35. package/dist/breadcrumbs/Breadcrumbs.js +19 -0
  36. package/dist/breadcrumbs/messages.d.ts +5 -0
  37. package/dist/breadcrumbs/messages.js +2 -0
  38. package/dist/button/Button.d.ts +46 -0
  39. package/dist/button/Button.js +44 -0
  40. package/dist/calendar/Calendar.d.ts +55 -0
  41. package/dist/calendar/Calendar.js +61 -0
  42. package/dist/calendar/messages.d.ts +8 -0
  43. package/dist/calendar/messages.js +2 -0
  44. package/dist/calendar/month.d.ts +61 -0
  45. package/dist/calendar/month.js +134 -0
  46. package/dist/card/Card.d.ts +46 -0
  47. package/dist/card/Card.js +8 -0
  48. package/dist/choice/Choice.d.ts +72 -0
  49. package/dist/choice/Choice.js +68 -0
  50. package/dist/choice/messages.d.ts +4 -0
  51. package/dist/choice/messages.js +2 -0
  52. package/dist/code-block/CodeBlock.d.ts +20 -0
  53. package/dist/code-block/CodeBlock.js +49 -0
  54. package/dist/code-block/messages.d.ts +8 -0
  55. package/dist/code-block/messages.js +2 -0
  56. package/dist/combobox/Combobox.d.ts +60 -0
  57. package/dist/combobox/Combobox.js +160 -0
  58. package/dist/combobox/messages.d.ts +9 -0
  59. package/dist/combobox/messages.js +2 -0
  60. package/dist/combobox/useAnchoredPopover.d.ts +5 -0
  61. package/dist/combobox/useAnchoredPopover.js +100 -0
  62. package/dist/data-viz/DataViz.d.ts +140 -0
  63. package/dist/data-viz/DataViz.js +469 -0
  64. package/dist/data-viz/index.d.ts +2 -0
  65. package/dist/data-viz/index.js +1 -0
  66. package/dist/data-viz/messages.d.ts +11 -0
  67. package/dist/data-viz/messages.js +2 -0
  68. package/dist/date-picker/DatePicker.d.ts +57 -0
  69. package/dist/date-picker/DatePicker.js +146 -0
  70. package/dist/date-picker/messages.d.ts +13 -0
  71. package/dist/date-picker/messages.js +2 -0
  72. package/dist/dialog/Dialog.d.ts +84 -0
  73. package/dist/dialog/Dialog.js +107 -0
  74. package/dist/dialog/messages.d.ts +9 -0
  75. package/dist/dialog/messages.js +2 -0
  76. package/dist/fonts/DIYASans-Italic.woff2 +0 -0
  77. package/dist/fonts/DIYASans.woff2 +0 -0
  78. package/dist/fonts/OFL.txt +94 -0
  79. package/dist/index.d.ts +50 -0
  80. package/dist/index.js +46 -0
  81. package/dist/internal/brand.d.ts +33 -0
  82. package/dist/internal/brand.js +57 -0
  83. package/dist/internal/cx.d.ts +2 -0
  84. package/dist/internal/cx.js +4 -0
  85. package/dist/internal/link.d.ts +14 -0
  86. package/dist/internal/link.js +7 -0
  87. package/dist/internal/slot.d.ts +15 -0
  88. package/dist/internal/slot.js +67 -0
  89. package/dist/layout/Inline.d.ts +4 -0
  90. package/dist/layout/Inline.js +7 -0
  91. package/dist/layout/Stack.d.ts +4 -0
  92. package/dist/layout/Stack.js +7 -0
  93. package/dist/layout/layout.d.ts +32 -0
  94. package/dist/layout/layout.js +12 -0
  95. package/dist/link/TextLink.d.ts +23 -0
  96. package/dist/link/TextLink.js +17 -0
  97. package/dist/list/List.d.ts +48 -0
  98. package/dist/list/List.js +17 -0
  99. package/dist/list/messages.d.ts +4 -0
  100. package/dist/list/messages.js +2 -0
  101. package/dist/menu/Menu.d.ts +49 -0
  102. package/dist/menu/Menu.js +54 -0
  103. package/dist/messages-defaults.d.ts +201 -0
  104. package/dist/messages-defaults.js +61 -0
  105. package/dist/messages.d.ts +12 -0
  106. package/dist/messages.js +1 -0
  107. package/dist/metric/Metric.d.ts +42 -0
  108. package/dist/metric/Metric.js +11 -0
  109. package/dist/metric/messages.d.ts +9 -0
  110. package/dist/metric/messages.js +2 -0
  111. package/dist/overlay/FloatingSurface.d.ts +29 -0
  112. package/dist/overlay/FloatingSurface.js +254 -0
  113. package/dist/page-header/PageHeader.d.ts +38 -0
  114. package/dist/page-header/PageHeader.js +10 -0
  115. package/dist/pagination/Pagination.d.ts +44 -0
  116. package/dist/pagination/Pagination.js +26 -0
  117. package/dist/pagination/messages.d.ts +11 -0
  118. package/dist/pagination/messages.js +2 -0
  119. package/dist/popover/Popover.d.ts +23 -0
  120. package/dist/popover/Popover.js +8 -0
  121. package/dist/progress/Progress.d.ts +33 -0
  122. package/dist/progress/Progress.js +14 -0
  123. package/dist/progress/messages.d.ts +4 -0
  124. package/dist/progress/messages.js +2 -0
  125. package/dist/prose/Prose.d.ts +14 -0
  126. package/dist/prose/Prose.js +7 -0
  127. package/dist/rating/Rating.d.ts +32 -0
  128. package/dist/rating/Rating.js +27 -0
  129. package/dist/rating/messages.d.ts +6 -0
  130. package/dist/rating/messages.js +2 -0
  131. package/dist/search/Search.d.ts +11 -0
  132. package/dist/search/Search.js +9 -0
  133. package/dist/select/Select.d.ts +65 -0
  134. package/dist/select/Select.js +19 -0
  135. package/dist/select/messages.d.ts +4 -0
  136. package/dist/select/messages.js +2 -0
  137. package/dist/site-header/SiteHeader.d.ts +88 -0
  138. package/dist/site-header/SiteHeader.js +211 -0
  139. package/dist/site-header/messages.d.ts +8 -0
  140. package/dist/site-header/messages.js +2 -0
  141. package/dist/slider/Slider.d.ts +54 -0
  142. package/dist/slider/Slider.js +89 -0
  143. package/dist/slider/messages.d.ts +5 -0
  144. package/dist/slider/messages.js +2 -0
  145. package/dist/styles.css +3379 -0
  146. package/dist/switch/Switch.d.ts +47 -0
  147. package/dist/switch/Switch.js +18 -0
  148. package/dist/table/Table.d.ts +99 -0
  149. package/dist/table/Table.js +158 -0
  150. package/dist/table/messages.d.ts +23 -0
  151. package/dist/table/messages.js +2 -0
  152. package/dist/tabs/Tabs.d.ts +49 -0
  153. package/dist/tabs/Tabs.js +26 -0
  154. package/dist/tag/Tag.d.ts +37 -0
  155. package/dist/tag/Tag.js +20 -0
  156. package/dist/tag/messages.d.ts +4 -0
  157. package/dist/tag/messages.js +2 -0
  158. package/dist/text-area/TextArea.d.ts +47 -0
  159. package/dist/text-area/TextArea.js +19 -0
  160. package/dist/text-input/TextInput.d.ts +53 -0
  161. package/dist/text-input/TextInput.js +35 -0
  162. package/dist/text-input/messages.d.ts +4 -0
  163. package/dist/text-input/messages.js +3 -0
  164. package/dist/text-input/native-field.d.ts +15 -0
  165. package/dist/text-input/native-field.js +56 -0
  166. package/dist/toast/Toast.d.ts +24 -0
  167. package/dist/toast/Toast.js +79 -0
  168. package/dist/toast/messages.d.ts +4 -0
  169. package/dist/toast/messages.js +2 -0
  170. package/dist/tooltip/Tooltip.d.ts +22 -0
  171. package/dist/tooltip/Tooltip.js +92 -0
  172. package/dist/workflow/ContentState.d.ts +44 -0
  173. package/dist/workflow/ContentState.js +11 -0
  174. package/dist/workflow/Drawer.d.ts +48 -0
  175. package/dist/workflow/Drawer.js +51 -0
  176. package/dist/workflow/FileUpload.d.ts +67 -0
  177. package/dist/workflow/FileUpload.js +47 -0
  178. package/dist/workflow/Form.d.ts +52 -0
  179. package/dist/workflow/Form.js +16 -0
  180. package/dist/workflow/Wizard.d.ts +76 -0
  181. package/dist/workflow/Wizard.js +31 -0
  182. package/dist/workflow/drawer-messages.d.ts +4 -0
  183. package/dist/workflow/drawer-messages.js +2 -0
  184. package/dist/workflow/file-upload-messages.d.ts +11 -0
  185. package/dist/workflow/file-upload-messages.js +2 -0
  186. package/dist/workflow/form-messages.d.ts +5 -0
  187. package/dist/workflow/form-messages.js +2 -0
  188. package/dist/workflow/wizard-messages.d.ts +9 -0
  189. package/dist/workflow/wizard-messages.js +2 -0
  190. package/package.json +82 -7
@@ -0,0 +1,211 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
+ import { DEFAULT_FONT_FAMILY, useDiyaMessages } from '../DiyaProvider.js';
5
+ import { cx } from '../internal/cx.js';
6
+ import { useDiyaLinkComponent } from '../internal/link.js';
7
+ import { composeRefs } from '../internal/slot.js';
8
+ import { Button } from '../button/Button.js';
9
+ import { Search } from '../search/Search.js';
10
+ import { siteHeaderMessages } from './messages.js';
11
+ const useClientLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
12
+ function pixels(value, fallback = 0) {
13
+ const number = Number.parseFloat(value);
14
+ return Number.isFinite(number) ? number : fallback;
15
+ }
16
+ /** Measure actual slot contents rather than the extra space allocated to a wrapping row. */
17
+ function contentWidth(node) {
18
+ if (!node)
19
+ return 0;
20
+ const style = getComputedStyle(node);
21
+ const padding = pixels(style.paddingLeft) + pixels(style.paddingRight) + pixels(style.borderLeftWidth) + pixels(style.borderRightWidth);
22
+ const children = Array.from(node.children);
23
+ const hasText = Array.from(node.childNodes).some(child => child.nodeType === 3 && child.textContent?.trim());
24
+ if (children.length && !hasText) {
25
+ const width = children.reduce((total, child) => total + Math.max(child.scrollWidth, child.getBoundingClientRect().width), 0);
26
+ if (width)
27
+ return width + Math.max(0, children.length - 1) * pixels(style.columnGap || style.gap) + padding;
28
+ }
29
+ const range = node.ownerDocument.createRange();
30
+ range.selectNodeContents(node);
31
+ const width = typeof range.getBoundingClientRect === 'function' ? range.getBoundingClientRect().width : 0;
32
+ return width ? width + padding : Math.max(node.scrollWidth, node.getBoundingClientRect().width);
33
+ }
34
+ /** A responsive top bar with brand, navigation, search and utilities that collapses its links into a menu when space runs out. The ref, `className`, `style` and other attributes go to the outer container. */
35
+ export const SiteHeader = forwardRef(function SiteHeader({ brand, brandLabel, brandHref, onBrandClick, items = [], value, onValueChange, navigationLabel, search, utilities, avatar, className, onFocusCapture, onBlurCapture, onKeyDown, ...props }, ref) {
36
+ const t = useDiyaMessages('siteHeader', siteHeaderMessages);
37
+ const Link = useDiyaLinkComponent();
38
+ const [open, setOpen] = useState(false);
39
+ const [layout, setLayout] = useState({ compact: false, stacked: false, toolsWrap: false });
40
+ const currentLayout = useRef(layout);
41
+ const container = useRef(null);
42
+ const setContainer = useMemo(() => composeRefs(container, ref), [ref]);
43
+ const headerNode = useRef(null);
44
+ const brandNode = useRef(null);
45
+ const navigation = useRef(null);
46
+ const tools = useRef(null);
47
+ const account = useRef(null);
48
+ const trigger = useRef(null);
49
+ const lastFocus = useRef(null);
50
+ const pendingFocus = useRef(null);
51
+ const textMeasure = useRef(null);
52
+ const id = useId();
53
+ const navigationKey = JSON.stringify(items);
54
+ // A router link that doesn't forward refs still gets measured.
55
+ function brandElement() {
56
+ return brandNode.current ?? container.current?.querySelector('.diya-site-brand') ?? null;
57
+ }
58
+ function remainingControl() {
59
+ const controls = Array.from(container.current?.querySelectorAll('a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled)') ?? []);
60
+ return controls.find(node => !node.closest('[hidden], [inert]')) ?? headerNode.current;
61
+ }
62
+ useClientLayoutEffect(() => {
63
+ const node = container.current;
64
+ if (!node)
65
+ return;
66
+ let active = true;
67
+ // Navigation labels are strings. Canvas measures their real font even while
68
+ // collapsed, without exposing duplicate links or temporarily hiding focused nodes.
69
+ if (!textMeasure.current) {
70
+ try {
71
+ textMeasure.current = node.ownerDocument.createElement('canvas').getContext('2d');
72
+ }
73
+ catch { /* A conservative text-width fallback covers unavailable Canvas contexts. */ }
74
+ }
75
+ function measure() {
76
+ if (!active || !node)
77
+ return;
78
+ const width = node.getBoundingClientRect().width;
79
+ if (!width)
80
+ return;
81
+ const style = getComputedStyle(node);
82
+ const token = (name, fallback) => pixels(style.getPropertyValue(name), fallback);
83
+ const fullInset = token('--diya-internal-site-full-inset', 24);
84
+ const compactInset = token('--diya-internal-site-compact-inset', 16);
85
+ const brandGap = token('--diya-internal-site-brand-nav-gap', 32);
86
+ const spacer = token('--diya-internal-site-header-spacer', 24);
87
+ const groupGap = token('--diya-internal-site-utilities-gap', 16);
88
+ const compactGap = token('--diya-internal-site-compact-gap', 8);
89
+ const searchWidth = search ? token('--diya-internal-site-search-min-width', 160) : 0;
90
+ const brandWidth = contentWidth(brandElement());
91
+ const toolsWidth = contentWidth(tools.current);
92
+ const accountWidth = contentWidth(account.current);
93
+ const controls = [toolsWidth, accountWidth].filter(value => value > 0);
94
+ const nav = navigation.current;
95
+ const navItems = Array.from(nav?.querySelectorAll('[data-navigation-value]') ?? []);
96
+ const navWidth = navItems.reduce((total, item) => {
97
+ const itemStyle = getComputedStyle(item);
98
+ const label = item.textContent ?? '';
99
+ const context = textMeasure.current;
100
+ if (context)
101
+ context.font = itemStyle.font || `${itemStyle.fontWeight || '400'} ${itemStyle.fontSize || '14px'} ${itemStyle.fontFamily || DEFAULT_FONT_FAMILY}`;
102
+ const textWidth = context?.measureText(label).width ?? label.length * pixels(itemStyle.fontSize, 14);
103
+ return total + textWidth + Math.max(0, Array.from(label).length - 1) * pixels(itemStyle.letterSpacing) +
104
+ pixels(itemStyle.paddingLeft) + pixels(itemStyle.paddingRight) + pixels(itemStyle.borderLeftWidth) + pixels(itemStyle.borderRightWidth);
105
+ }, 0) + Math.max(0, navItems.length - 1) * (nav ? pixels(getComputedStyle(nav).columnGap || getComputedStyle(nav).gap, 4) : 0);
106
+ const trailing = [searchWidth, ...controls].filter(value => value > 0);
107
+ const trailingWidth = trailing.reduce((total, value) => total + value, 0) + Math.max(0, trailing.length - 1) * groupGap;
108
+ const fullWidth = brandWidth + (items.length ? brandGap + navWidth : 0) + (trailing.length ? spacer + trailingWidth : 0) + fullInset * 2;
109
+ const compact = width <= 640 || fullWidth > width;
110
+ const compactControls = [...controls, ...(compact && items.length ? [40] : [])];
111
+ const compactControlWidth = compactControls.reduce((total, value) => total + value, 0);
112
+ const inlineGroups = [searchWidth, ...compactControls].filter(value => value > 0);
113
+ const inlineWidth = brandWidth + (inlineGroups.length ? spacer : 0) + inlineGroups.reduce((total, value) => total + value, 0) + Math.max(0, inlineGroups.length - 1) * groupGap + fullInset * 2;
114
+ const stacked = width <= 640 || (compact && inlineWidth > width);
115
+ const primaryWidth = brandWidth + (compactControls.length ? compactGap : 0) + compactControlWidth + Math.max(0, compactControls.length - 1) * compactGap + compactInset * 2;
116
+ const toolsWrap = stacked && !!utilities && primaryWidth > width;
117
+ const next = { compact, stacked, toolsWrap };
118
+ const previous = currentLayout.current;
119
+ if (previous.compact === compact && previous.stacked === stacked && previous.toolsWrap === toolsWrap)
120
+ return;
121
+ const focused = node.ownerDocument.activeElement;
122
+ if (focused instanceof HTMLElement && node.contains(focused)) {
123
+ pendingFocus.current = { node: focused, navigationValue: navigation.current?.contains(focused) ? focused.dataset.navigationValue : undefined, toggle: focused === trigger.current };
124
+ }
125
+ currentLayout.current = next;
126
+ setOpen(false);
127
+ setLayout(next);
128
+ }
129
+ measure();
130
+ const observer = typeof ResizeObserver === 'function' ? new ResizeObserver(measure) : null;
131
+ [node, brandElement(), navigation.current, tools.current, account.current].forEach(element => { if (element)
132
+ observer?.observe(element); });
133
+ window.addEventListener('resize', measure);
134
+ const fonts = node.ownerDocument.fonts;
135
+ fonts?.addEventListener('loadingdone', measure);
136
+ void fonts?.ready.then(measure);
137
+ return () => {
138
+ active = false;
139
+ observer?.disconnect();
140
+ window.removeEventListener('resize', measure);
141
+ fonts?.removeEventListener('loadingdone', measure);
142
+ };
143
+ }, [navigationKey, brand, !!search, utilities, avatar, layout.compact, layout.stacked, layout.toolsWrap]);
144
+ useClientLayoutEffect(() => {
145
+ if (layout !== currentLayout.current)
146
+ return;
147
+ const recovery = pendingFocus.current;
148
+ pendingFocus.current = null;
149
+ const firstLink = navigation.current?.querySelector('a[href], button:not(:disabled)');
150
+ if (recovery) {
151
+ const matching = Array.from(navigation.current?.querySelectorAll('[data-navigation-value]') ?? []).find(node => node.dataset.navigationValue === recovery.navigationValue && !node.matches(':disabled'));
152
+ const destination = recovery.navigationValue !== undefined
153
+ ? layout.compact ? trigger.current ?? remainingControl() : matching ?? firstLink ?? remainingControl()
154
+ : recovery.toggle && !layout.compact ? firstLink ?? remainingControl()
155
+ : recovery.node.isConnected ? recovery.node : trigger.current ?? remainingControl();
156
+ destination?.focus({ preventScroll: true });
157
+ }
158
+ else if (lastFocus.current && !lastFocus.current.isConnected && document.activeElement === document.body) {
159
+ ;
160
+ (trigger.current ?? remainingControl())?.focus({ preventScroll: true });
161
+ }
162
+ if (!items.length)
163
+ setOpen(false);
164
+ }, [layout, navigationKey]);
165
+ useEffect(() => {
166
+ if (!open)
167
+ return;
168
+ const outside = (event) => { if (!container.current?.contains(event.target))
169
+ setOpen(false); };
170
+ document.addEventListener('pointerdown', outside);
171
+ return () => document.removeEventListener('pointerdown', outside);
172
+ }, [open]);
173
+ function select(item) {
174
+ if (item.disabled)
175
+ return;
176
+ if (open) {
177
+ setOpen(false);
178
+ trigger.current?.focus();
179
+ }
180
+ onValueChange?.(item.value);
181
+ }
182
+ function links() {
183
+ return items.map((item) => item.href && !item.disabled ? (_jsx(Link, { href: item.href, "data-navigation-value": item.value, "aria-current": value === item.value ? 'page' : undefined, onClick: (event) => {
184
+ if (event.button || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
185
+ return;
186
+ if (onValueChange)
187
+ event.preventDefault();
188
+ select(item);
189
+ }, children: item.label }, item.value)) : (_jsx("button", { type: "button", disabled: item.disabled, "data-navigation-value": item.value, "aria-current": value === item.value ? 'page' : undefined, onClick: () => select(item), children: item.label }, item.value)));
190
+ }
191
+ const nav = !!items.length && _jsx("nav", { ref: navigation, id: id, className: `diya-site-nav${layout.compact ? ' diya-site-mobile-nav' : ''}`, "aria-label": navigationLabel ?? t.navigation, hidden: layout.compact && !open, children: links() }, "navigation");
192
+ const searchForm = search && _jsx("form", { className: "diya-site-search", role: "search", "aria-label": search.label ?? t.search, onSubmit: event => { event.preventDefault(); search.onSubmit?.(search.value); }, children: _jsx(Search, { label: search.label ?? t.search, hideLabel: true, placeholder: search.placeholder ?? t.search, value: search.value, onValueChange: search.onValueChange, clearable: true, shape: "rounded" }) }, "search");
193
+ const toolsGroup = utilities && _jsx("div", { ref: tools, className: "diya-site-tools", children: utilities }, "tools");
194
+ const avatarGroup = avatar && _jsx("div", { ref: account, className: "diya-site-avatar", children: avatar }, "avatar");
195
+ const toggle = layout.compact && !!items.length && _jsx(Button, { ref: trigger, className: "diya-site-menu-toggle", variant: "quiet", iconOnly: true, "aria-label": open ? t.closeNavigation : t.openNavigation, "aria-expanded": open, "aria-controls": id, onClick: () => setOpen(previous => !previous), children: _jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: _jsx("path", { d: open ? 'm6 6 12 12M18 6 6 18' : 'M4 7h16M4 12h16M4 17h16' }) }) }, "toggle");
196
+ const utilityGroup = (utilities || avatar || toggle) && _jsx("div", { className: "diya-site-utilities", children: layout.toolsWrap ? _jsxs(_Fragment, { children: [avatarGroup, toggle, toolsGroup] }) : _jsxs(_Fragment, { children: [toolsGroup, avatarGroup, toggle] }) }, "utilities");
197
+ return (_jsx("div", { ...props, ref: setContainer, className: cx('diya-site-header-container', className), "data-compact": layout.compact, "data-stacked": layout.stacked, "data-tools-wrap": layout.toolsWrap, onFocusCapture: event => { onFocusCapture?.(event); lastFocus.current = event.target; }, onBlurCapture: event => {
198
+ onBlurCapture?.(event);
199
+ if (!event.currentTarget.contains(event.relatedTarget) && !pendingFocus.current) {
200
+ setOpen(false);
201
+ lastFocus.current = null;
202
+ }
203
+ }, onKeyDown: event => {
204
+ onKeyDown?.(event);
205
+ if (open && event.key === 'Escape' && !event.defaultPrevented) {
206
+ event.preventDefault();
207
+ setOpen(false);
208
+ trigger.current?.focus();
209
+ }
210
+ }, children: _jsx("header", { ref: headerNode, tabIndex: -1, className: "diya-site-header", children: _jsxs("div", { className: "diya-site-header-row", children: [_jsxs("div", { className: "diya-site-identity", children: [brandHref ? (_jsx(Link, { ref: (node) => { brandNode.current = node; }, className: "diya-site-brand", href: brandHref, "aria-label": brandLabel ?? t.home, children: brand })) : onBrandClick ? (_jsx("button", { ref: node => { brandNode.current = node; }, type: "button", className: "diya-site-brand", "aria-label": brandLabel ?? t.home, onClick: onBrandClick, children: brand })) : (_jsx("span", { ref: node => { brandNode.current = node; }, className: "diya-site-brand", children: brand })), !layout.compact && nav] }), layout.stacked ? _jsxs(_Fragment, { children: [utilityGroup, searchForm] }) : _jsxs(_Fragment, { children: [searchForm, utilityGroup] }), layout.compact && nav] }) }) }));
211
+ });
@@ -0,0 +1,8 @@
1
+ /** siteHeader: English defaults, overridable through DiyaProvider `messages`. */
2
+ export declare const siteHeaderMessages: {
3
+ home: string;
4
+ navigation: string;
5
+ openNavigation: string;
6
+ closeNavigation: string;
7
+ search: string;
8
+ };
@@ -0,0 +1,2 @@
1
+ /** siteHeader: English defaults, overridable through DiyaProvider `messages`. */
2
+ export const siteHeaderMessages = { home: 'Home', navigation: 'Main navigation', openNavigation: 'Open navigation', closeNavigation: 'Close navigation', search: 'Search' };
@@ -0,0 +1,54 @@
1
+ import { type InputHTMLAttributes } from 'react';
2
+ import { sliderMessages } from './messages.js';
3
+ declare module '../messages.js' {
4
+ interface DiyaMessages {
5
+ slider: typeof sliderMessages;
6
+ }
7
+ }
8
+ type SliderBase = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'size' | 'value' | 'defaultValue' | 'min' | 'max' | 'step' | 'disabled' | 'children'> & {
9
+ /** Prevents dragging and dims the slider. */
10
+ disabled?: boolean;
11
+ /** Shows the current numbers above the track (the minimum and the value, or start and end in range mode); defaults to true. */
12
+ showValues?: boolean;
13
+ /** Shows a visible scale of five evenly spaced numbers from `min` to `max` under the track. */
14
+ showMarks?: boolean;
15
+ /** Formats every number the slider shows (its values and marks) and what screen readers announce for each handle, such as `(value) => '$' + value`. Defaults to the plain number. */
16
+ formatValue?: (value: number) => string;
17
+ /** Lowest value; defaults to 0. Numeric strings work too, so react-hook-form's register() spreads cleanly. */
18
+ min?: number | string;
19
+ /** Highest value; defaults to 100. Numeric strings work too. */
20
+ max?: number | string;
21
+ /** Increment between values, greater than 0; defaults to 1. Numeric strings work too. */
22
+ step?: number | string;
23
+ /** Visible label above the slider; also names the inputs for assistive tech. Hide it with hideLabel. */
24
+ label: string;
25
+ /** Hides the visible label; the inputs keep it as their accessible name. */
26
+ hideLabel?: boolean;
27
+ };
28
+ /** One number. */
29
+ export type SliderSingleProps = SliderBase & {
30
+ /** Single mode (the default): one handle, leave unset or false. */
31
+ range?: false;
32
+ /** The current value when controlled; out-of-range numbers are clamped. Pair it with `onValueChange`. */
33
+ value?: number;
34
+ /** The starting value when uncontrolled; defaults to `min`, and a form reset returns to it. */
35
+ defaultValue?: number;
36
+ /** Called with the new value while dragging or on a track click. */
37
+ onValueChange?: (value: number) => void;
38
+ };
39
+ /** Two numbers, [start, end]. */
40
+ export type SliderRangeProps = SliderBase & {
41
+ /** Range mode: two handles for a start and end; the handles cannot cross. */
42
+ range: true;
43
+ /** The current [start, end] when controlled, with start ≤ end. Pair it with `onValueChange`. */
44
+ value?: [number, number];
45
+ /** The starting [start, end] when uncontrolled; defaults to [min, max], and a form reset returns to it. */
46
+ defaultValue?: [number, number];
47
+ /** Called with the new [start, end] while dragging or on a track click. */
48
+ onValueChange?: (value: [number, number]) => void;
49
+ };
50
+ /** One number by default; `range` takes and returns [start, end]. Native attributes and the ref reach the (start) input; `name`, `onChange` and `onBlur` reach both inputs in range mode. */
51
+ export type SliderProps = SliderSingleProps | SliderRangeProps;
52
+ /** A labeled slider with one handle, or two for a start and end with `range`; 0–100 unless `min` and `max` say otherwise. */
53
+ export declare const Slider: import("react").ForwardRefExoticComponent<SliderProps & import("react").RefAttributes<HTMLInputElement>>;
54
+ export {};
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useRef } from 'react';
4
+ import { useDiyaMessages } from '../DiyaProvider.js';
5
+ import { cx } from '../internal/cx.js';
6
+ import { setNativeValue, useFormReset, useMergedRef, useNativeMirror } from '../text-input/native-field.js';
7
+ import { sliderMessages } from './messages.js';
8
+ const numberOr = (value, fallback) => {
9
+ const number = typeof value === 'string' ? Number.parseFloat(value) : value;
10
+ return number !== undefined && Number.isFinite(number) ? number : fallback;
11
+ };
12
+ const decimalsOf = (value) => (String(value).split('.')[1] ?? '').length;
13
+ const samePair = (a, b) => a[0] === b[0] && a[1] === b[1];
14
+ /** A labeled slider with one handle, or two for a start and end with `range`; 0–100 unless `min` and `max` say otherwise. */
15
+ export const Slider = forwardRef(function Slider(props, ref) {
16
+ const { disabled = false, showValues = true, showMarks = false, formatValue, min = 0, max = 100, step = 1, label, hideLabel = false, className, style, name, form, onChange, onBlur, range: _range, value: _value, defaultValue: _defaultValue, onValueChange: _onValueChange, ...rest } = props;
17
+ const t = useDiyaMessages('slider', sliderMessages);
18
+ const range = props.range === true;
19
+ const lowest = numberOr(min, 0);
20
+ const highest = Math.max(lowest + Number.EPSILON, numberOr(max, 100));
21
+ const span = highest - lowest;
22
+ const rawStep = numberOr(step, 1);
23
+ const increment = rawStep > 0 ? Math.min(rawStep, span) : 1;
24
+ const decimals = Math.max(decimalsOf(increment), decimalsOf(lowest));
25
+ const clamp = (value) => Math.max(lowest, Math.min(highest, value));
26
+ const snap = (value) => clamp(Number((Math.round((value - lowest) / increment) * increment + lowest).toFixed(decimals)));
27
+ const first = useRef(null);
28
+ const second = useRef(null);
29
+ const firstRef = useMergedRef(ref, first);
30
+ const controlled = props.value !== undefined;
31
+ const fallback = props.range ? props.defaultValue ?? [lowest, highest] : [props.defaultValue ?? lowest, props.defaultValue ?? lowest];
32
+ const readDom = () => {
33
+ if (!first.current)
34
+ return undefined;
35
+ const start = Number(first.current.value);
36
+ return [start, range && second.current ? Number(second.current.value) : start];
37
+ };
38
+ const [current, sync] = useNativeMirror(readDom, fallback, samePair);
39
+ useFormReset(first, sync);
40
+ // Internally the slider always tracks [low, high]; a single value is [value, value].
41
+ const value = controlled ? props.range ? props.value ?? fallback : [props.value ?? lowest, props.value ?? lowest] : current;
42
+ const report = (next) => props.range ? props.onValueChange?.(next) : props.onValueChange?.(next[0]);
43
+ const low = Number.isFinite(value[0]) ? clamp(value[0]) : lowest;
44
+ const high = Number.isFinite(value[1]) ? Math.max(low, clamp(value[1])) : low;
45
+ const percent = (number) => `${((number - lowest) / span) * 100}%`;
46
+ const marks = Array.from({ length: 5 }, (_, index) => Number((lowest + (span * index) / 4).toFixed(Math.max(decimals, 2))));
47
+ const shared = { name, form, disabled, onBlur };
48
+ const show = (number) => (formatValue ? formatValue(number) : String(number));
49
+ return (_jsxs("div", { className: cx('diya-slider', className), style: style, "data-disabled": disabled, children: [!hideLabel && _jsx("span", { className: "diya-slider-label", "aria-hidden": "true", children: label }), showValues && (_jsxs("div", { className: "diya-slider-values", "aria-hidden": "true", children: [_jsx("span", { children: show(range ? low : lowest) }), _jsx("span", { children: show(range ? high : low) })] })), _jsxs("div", { className: "diya-slider-inputs", onPointerDown: (event) => {
50
+ if (disabled || event.target instanceof HTMLInputElement)
51
+ return;
52
+ const box = event.currentTarget.getBoundingClientRect();
53
+ const clicked = snap(lowest + ((event.clientX - box.left - 9) / (box.width - 18)) * span);
54
+ const target = range && Math.abs(clicked - high) < Math.abs(clicked - low) ? second.current : first.current;
55
+ if (!target)
56
+ return;
57
+ // Moves the handle through the input itself, so onChange and form libraries see it.
58
+ setNativeValue(target, String(clicked));
59
+ target.focus();
60
+ }, style: {
61
+ '--diya-internal-slider-start': percent(range ? low : lowest),
62
+ '--diya-internal-slider-end': percent(range ? high : low),
63
+ }, children: [_jsx("div", { className: "diya-slider-rail", "aria-hidden": "true", children: _jsx("span", {}) }), _jsx("input", { ...rest, ...shared, ref: firstRef, type: "range", min: lowest, max: highest, step: increment, ...(controlled ? { value: low } : { defaultValue: fallback[0] }), "aria-label": range ? t.minimum(label) : label, "aria-valuemax": range ? high : undefined, "aria-valuetext": formatValue ? formatValue(low) : rest['aria-valuetext'], onChange: (event) => {
64
+ const pair = controlled ? [low, high] : readDom() ?? [low, high];
65
+ let start = Number(event.target.value);
66
+ // The handles cannot cross.
67
+ if (range && start > pair[1]) {
68
+ start = pair[1];
69
+ if (!controlled)
70
+ event.target.value = String(start);
71
+ }
72
+ onChange?.(event);
73
+ if (!controlled)
74
+ sync();
75
+ report([start, range ? pair[1] : start]);
76
+ } }), range && (_jsx("input", { ...shared, ref: second, type: "range", min: lowest, max: highest, step: increment, ...(controlled ? { value: high } : { defaultValue: fallback[1] }), "aria-label": t.maximum(label), "aria-valuemin": low, "aria-valuetext": formatValue ? formatValue(high) : undefined, onChange: (event) => {
77
+ const pair = controlled ? [low, high] : readDom() ?? [low, high];
78
+ let end = Number(event.target.value);
79
+ if (end < pair[0]) {
80
+ end = pair[0];
81
+ if (!controlled)
82
+ event.target.value = String(end);
83
+ }
84
+ onChange?.(event);
85
+ if (!controlled)
86
+ sync();
87
+ report([pair[0], end]);
88
+ } }))] }), showMarks && (_jsx("div", { className: "diya-slider-marks", "aria-hidden": "true", children: marks.map((mark) => (_jsx("span", { children: show(mark) }, mark))) }))] }));
89
+ });
@@ -0,0 +1,5 @@
1
+ /** slider: English defaults, overridable through DiyaProvider `messages`. */
2
+ export declare const sliderMessages: {
3
+ minimum: (label: string) => string;
4
+ maximum: (label: string) => string;
5
+ };
@@ -0,0 +1,2 @@
1
+ /** slider: English defaults, overridable through DiyaProvider `messages`. */
2
+ export const sliderMessages = { minimum: (label) => `${label} minimum`, maximum: (label) => `${label} maximum` };