@idyeah/diya 0.0.0 → 1.0.1

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 +332 -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 +3386 -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,160 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Icon } from '../Icon.js';
6
+ import { useAnchoredPopover } from './useAnchoredPopover.js';
7
+ import { useDiyaMessages } from '../DiyaProvider.js';
8
+ import { cx } from '../internal/cx.js';
9
+ import { useFormReset, useMergedRef } from '../text-input/native-field.js';
10
+ import { comboboxMessages } from './messages.js';
11
+ const useClientLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
12
+ /** A labeled text field that filters a list of options as you type; picks one value, or several with `multiple`. */
13
+ export const Combobox = forwardRef(function Combobox(props, ref) {
14
+ const { label, options, disabled = false, error, helperText, hideLabel = false, size = 'medium', name, className, style, id: idProp, placeholder, onChange: onInputChange, onClick: onInputClick, onKeyDown: onInputKeyDown, 'aria-describedby': describedBy, multiple: _multiple, value: _value, defaultValue: _defaultValue, onValueChange: _onValueChange, ...rest } = props;
15
+ const t = useDiyaMessages('combobox', comboboxMessages);
16
+ const multiple = props.multiple === true;
17
+ const controlled = props.value !== undefined;
18
+ const initial = () => props.multiple ? props.defaultValue ?? [] : props.defaultValue ? [props.defaultValue] : [];
19
+ const [uncontrolled, setUncontrolled] = useState(initial);
20
+ // Internally a selection is always a list; the public value matches the mode.
21
+ const value = useMemo(() => !controlled ? uncontrolled : props.multiple ? props.value ?? [] : props.value ? [props.value] : [], [controlled, uncontrolled, props.multiple, props.value]);
22
+ const onChange = (next) => {
23
+ if (!controlled)
24
+ setUncontrolled(next);
25
+ if (props.multiple)
26
+ props.onValueChange?.(next);
27
+ else
28
+ props.onValueChange?.(next[0] ?? '');
29
+ };
30
+ const generatedId = useId();
31
+ const id = idProp ?? generatedId;
32
+ const [query, setQuery] = useState('');
33
+ const [open, setOpen] = useState(false);
34
+ const [active, setActive] = useState(-1);
35
+ const [revealRequest, setRevealRequest] = useState(0);
36
+ const keyboardNavigation = useRef(true);
37
+ const field = useRef(null);
38
+ const control = useRef(null);
39
+ const input = useRef(null);
40
+ const inputRef = useMergedRef(ref, input);
41
+ useFormReset(input, () => { if (!controlled)
42
+ setUncontrolled(initial()); });
43
+ const panel = useRef(null);
44
+ const filtered = useMemo(() => options.filter((option) => option.label.toLowerCase().includes(query.toLowerCase())), [options, query]);
45
+ const enabled = filtered.map((option, index) => option.disabled ? -1 : index).filter((index) => index >= 0);
46
+ const activeIndex = filtered[active] && !filtered[active].disabled ? active : enabled[0] ?? -1;
47
+ const isOpen = open && !disabled;
48
+ const popupContent = useMemo(() => ({ filtered, value, size }), [filtered, value, size]);
49
+ const portal = useAnchoredPopover(isOpen, control, panel, 'anchor', popupContent);
50
+ function revealActive() {
51
+ keyboardNavigation.current = true;
52
+ // A boundary key must still reveal its option after the list was scrolled manually.
53
+ setRevealRequest(previous => previous + 1);
54
+ }
55
+ useClientLayoutEffect(() => {
56
+ const list = panel.current;
57
+ const option = list?.querySelector('[data-active="true"]');
58
+ if (!keyboardNavigation.current || !isOpen || !list || !option || !list.clientHeight)
59
+ return;
60
+ // The anchoring hook runs first, including showing a native popover. Scroll only
61
+ // this list: scrollIntoView could move the host page and dismiss the popup.
62
+ const row = option.getBoundingClientRect();
63
+ const top = list.getBoundingClientRect().top + list.clientTop;
64
+ const bottom = top + list.clientHeight;
65
+ if (row.top < top || row.height > list.clientHeight)
66
+ list.scrollTop += row.top - top;
67
+ else if (row.bottom > bottom)
68
+ list.scrollTop += row.bottom - bottom;
69
+ }, [isOpen, activeIndex, filtered, portal, size, revealRequest]);
70
+ function close() { setOpen(false); setQuery(''); setActive(-1); }
71
+ useEffect(() => { if (disabled)
72
+ close(); }, [disabled]);
73
+ useEffect(() => {
74
+ if (!isOpen)
75
+ return;
76
+ const outside = (event) => {
77
+ if (!field.current?.contains(event.target) && !panel.current?.contains(event.target))
78
+ close();
79
+ };
80
+ const resize = () => close();
81
+ const scroll = (event) => { if (!panel.current?.contains(event.target))
82
+ close(); };
83
+ document.addEventListener('pointerdown', outside);
84
+ window.addEventListener('resize', resize);
85
+ window.addEventListener('scroll', scroll, true);
86
+ return () => {
87
+ document.removeEventListener('pointerdown', outside);
88
+ window.removeEventListener('resize', resize);
89
+ window.removeEventListener('scroll', scroll, true);
90
+ };
91
+ }, [isOpen]);
92
+ function choose(option) {
93
+ if (disabled || option.disabled)
94
+ return;
95
+ onChange(multiple ? value.includes(option.value) ? value.filter((entry) => entry !== option.value) : [...value, option.value] : [option.value]);
96
+ setQuery('');
97
+ setActive(options.findIndex((entry) => entry.value === option.value && !entry.disabled));
98
+ input.current?.focus();
99
+ if (!multiple)
100
+ setOpen(false);
101
+ }
102
+ return _jsxs("div", { ref: field, className: cx('diya-combobox', className), style: style, "data-size": size, "data-invalid": !!error, onBlur: (event) => {
103
+ const next = event.relatedTarget;
104
+ if (!field.current?.contains(next) && !panel.current?.contains(next))
105
+ close();
106
+ }, children: [_jsx("label", { htmlFor: id, className: hideLabel ? 'diya-sr-only' : '', children: label }), name && (multiple ? value : [value[0] ?? '']).map((entry, index) => _jsx("input", { type: "hidden", name: name, value: entry, disabled: disabled }, `${index}-${entry}`)), _jsxs("div", { ref: control, className: "diya-combobox-control", children: [multiple && value.map((entry) => _jsxs("span", { className: "diya-combobox-chip", children: [options.find((option) => option.value === entry)?.label, _jsx("button", { type: "button", disabled: disabled, "aria-label": t.remove(options.find((option) => option.value === entry)?.label ?? entry), onClick: () => { if (!disabled) {
107
+ input.current?.focus();
108
+ onChange(value.filter((item) => item !== entry));
109
+ } }, children: "\u00D7" })] }, entry)), _jsx("input", { ...rest, ref: inputRef, id: id, role: "combobox", "aria-expanded": isOpen, "aria-controls": isOpen ? `${id}-options` : undefined, "aria-autocomplete": "list", "aria-activedescendant": isOpen && activeIndex >= 0 ? `${id}-option-${activeIndex}` : undefined, "aria-invalid": !!error, "aria-describedby": [describedBy, error || helperText ? `${id}-help` : undefined].filter(Boolean).join(' ') || undefined, disabled: disabled, placeholder: placeholder ?? (multiple && value.length ? t.addAnother : t.placeholder), value: !multiple && !isOpen && value.length ? options.find((option) => option.value === value[0])?.label || '' : query, onClick: (event) => { onInputClick?.(event); if (!disabled && !event.defaultPrevented) {
110
+ revealActive();
111
+ setOpen(true);
112
+ setActive(enabled[0] ?? -1);
113
+ } }, onChange: (event) => {
114
+ onInputChange?.(event);
115
+ const next = event.target.value;
116
+ revealActive();
117
+ setQuery(next);
118
+ setActive(options.filter((option) => option.label.toLowerCase().includes(next.toLowerCase())).findIndex((option) => !option.disabled));
119
+ setOpen(true);
120
+ }, onKeyDown: (event) => {
121
+ onInputKeyDown?.(event);
122
+ if (disabled || event.defaultPrevented || event.nativeEvent.isComposing)
123
+ return;
124
+ if (event.key === 'Escape' && isOpen) {
125
+ event.preventDefault();
126
+ event.stopPropagation();
127
+ close();
128
+ return;
129
+ }
130
+ if (event.key === 'Backspace' && multiple && !query && value.length)
131
+ onChange(value.slice(0, -1));
132
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
133
+ event.preventDefault();
134
+ revealActive();
135
+ setOpen(true);
136
+ if (!isOpen) {
137
+ setActive(event.key === 'ArrowDown' ? enabled[0] ?? -1 : enabled[enabled.length - 1] ?? -1);
138
+ return;
139
+ }
140
+ const position = enabled.indexOf(activeIndex);
141
+ const next = position + (event.key === 'ArrowDown' ? 1 : -1);
142
+ setActive(enabled[next] ?? activeIndex);
143
+ }
144
+ if (event.key === 'Enter' && isOpen && activeIndex >= 0) {
145
+ event.preventDefault();
146
+ choose(filtered[activeIndex]);
147
+ }
148
+ } }), _jsx("button", { type: "button", className: "diya-combobox-toggle", disabled: disabled, tabIndex: -1, "aria-label": isOpen ? t.closeOptions : t.openOptions, onMouseDown: (event) => event.preventDefault(), onClick: () => {
149
+ if (disabled)
150
+ return;
151
+ revealActive();
152
+ input.current?.focus();
153
+ setQuery('');
154
+ setActive(options.findIndex((option) => !option.disabled));
155
+ setOpen(!isOpen);
156
+ }, children: _jsx(Icon, { name: "chevron-down", size: 14 }) })] }), (error || helperText) && _jsxs("p", { id: `${id}-help`, className: "diya-field-help", children: [error && _jsx("span", { "aria-hidden": "true", children: "!" }), error || helperText] }), isOpen && portal && createPortal(_jsx("ul", { ref: panel, id: `${id}-options`, className: "diya-combobox-options", role: "listbox", "aria-label": label, "aria-multiselectable": multiple || undefined, style: { position: 'fixed', inset: 'auto', margin: 0 }, children: filtered.length ? filtered.map((option, index) => _jsxs("li", { id: `${id}-option-${index}`, role: "option", "aria-selected": value.includes(option.value), "aria-disabled": option.disabled, "data-active": index === activeIndex, onMouseDown: (event) => event.preventDefault(), onMouseEnter: () => { if (!option.disabled) {
157
+ keyboardNavigation.current = false;
158
+ setActive(index);
159
+ } }, onClick: () => choose(option), children: [_jsx("span", { children: option.label }), value.includes(option.value) && _jsx("span", { "aria-hidden": "true", children: "\u2713" })] }, option.value)) : _jsx("li", { role: "presentation", children: t.noResults }) }), portal)] });
160
+ });
@@ -0,0 +1,9 @@
1
+ /** combobox: English defaults, overridable through DiyaProvider `messages`. */
2
+ export declare const comboboxMessages: {
3
+ placeholder: string;
4
+ addAnother: string;
5
+ noResults: string;
6
+ openOptions: string;
7
+ closeOptions: string;
8
+ remove: (label: string) => string;
9
+ };
@@ -0,0 +1,2 @@
1
+ /** combobox: English defaults, overridable through DiyaProvider `messages`. */
2
+ export const comboboxMessages = { placeholder: 'Search options', addAnother: 'Add another…', noResults: 'No results', openOptions: 'Open options', closeOptions: 'Close options', remove: (label) => `Remove ${label}` };
@@ -0,0 +1,5 @@
1
+ import { type RefObject } from 'react';
2
+ /** Preserve the trigger's logical tab order when popup controls live in a portal. */
3
+ export declare function adjacentTabStop(anchor: HTMLElement, surface: HTMLElement | null, backwards: boolean): HTMLElement | undefined;
4
+ /** Native top-layer popover when available; the same scoped portal is the fallback. */
5
+ export declare function useAnchoredPopover<T extends HTMLElement>(open: boolean, anchor: RefObject<HTMLElement | null>, popup: RefObject<T | null>, width: 'anchor' | number, contentKey: unknown): HTMLElement | null;
@@ -0,0 +1,100 @@
1
+ 'use client';
2
+ import { useEffect, useLayoutEffect, useState } from 'react';
3
+ import { useDiyaPortal } from '../DiyaProvider.js';
4
+ const useClientLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
5
+ const tabbableSelector = 'a[href], area[href], button, input:not([type="hidden"]), select, textarea, iframe, object, embed, audio[controls], video[controls], summary, [contenteditable], [tabindex]';
6
+ /** Preserve the trigger's logical tab order when popup controls live in a portal. */
7
+ export function adjacentTabStop(anchor, surface, backwards) {
8
+ const document = anchor.ownerDocument;
9
+ const view = document.defaultView;
10
+ const modal = anchor.closest('dialog[open]');
11
+ const stops = Array.from((modal ?? document).querySelectorAll(tabbableSelector)).filter((element) => {
12
+ if (surface?.contains(element) || element.matches(':disabled'))
13
+ return false;
14
+ const editable = element.hasAttribute('contenteditable') && element.contentEditable !== 'false' && !element.parentElement?.isContentEditable;
15
+ if (element.tabIndex < 0 && (element.hasAttribute('tabindex') || !editable))
16
+ return false;
17
+ for (let ancestor = element; ancestor; ancestor = ancestor.parentElement) {
18
+ if (ancestor.hidden || ancestor.hasAttribute('inert'))
19
+ return false;
20
+ const style = view?.getComputedStyle(ancestor);
21
+ if (style?.display === 'none' || style?.visibility === 'hidden' || style?.visibility === 'collapse' || style?.contentVisibility === 'hidden')
22
+ return false;
23
+ if (ancestor !== element && ancestor.tagName === 'DETAILS' && !ancestor.hasAttribute('open')) {
24
+ const summary = Array.from(ancestor.children).find((child) => child.tagName === 'SUMMARY');
25
+ if (!summary?.contains(element))
26
+ return false;
27
+ }
28
+ }
29
+ return true;
30
+ });
31
+ const ordered = stops.filter((element) => {
32
+ if (element.tagName !== 'INPUT')
33
+ return true;
34
+ const radio = element;
35
+ return radio.type !== 'radio' || !radio.name || radio.checked || !stops.some((candidate) => {
36
+ if (candidate.tagName !== 'INPUT')
37
+ return false;
38
+ const other = candidate;
39
+ return other.type === 'radio' && other.name === radio.name && other.form === radio.form && other.checked;
40
+ });
41
+ }).map((element, index) => ({ element, index, order: Math.max(0, element.tabIndex) })).sort((a, b) => {
42
+ if (a.order === b.order)
43
+ return a.index - b.index;
44
+ if (a.order === 0)
45
+ return 1;
46
+ if (b.order === 0)
47
+ return -1;
48
+ return a.order - b.order;
49
+ });
50
+ const index = ordered.findIndex(({ element }) => element === anchor);
51
+ if (index < 0)
52
+ return undefined;
53
+ const next = index + (backwards ? -1 : 1);
54
+ return ordered[modal ? (next + ordered.length) % ordered.length : next]?.element;
55
+ }
56
+ /** Native top-layer popover when available; the same scoped portal is the fallback. */
57
+ export function useAnchoredPopover(open, anchor, popup, width, contentKey) {
58
+ const providerPortal = useDiyaPortal();
59
+ const modal = anchor.current?.closest('dialog[open]');
60
+ // A provider inside the dialog already has a safe outlet; otherwise stay in the modal subtree.
61
+ const portal = modal && !modal.contains(providerPortal) ? modal : providerPortal;
62
+ const [nativePopover, setNativePopover] = useState(false);
63
+ useEffect(() => {
64
+ setNativePopover(typeof HTMLElement !== 'undefined' && typeof HTMLElement.prototype.showPopover === 'function');
65
+ }, []);
66
+ useClientLayoutEffect(() => {
67
+ const node = popup.current;
68
+ const trigger = anchor.current;
69
+ if (!open || !node || !trigger)
70
+ return;
71
+ if (nativePopover) {
72
+ // Set through the DOM so React 18's public HTML attribute types stay supported.
73
+ node.setAttribute('popover', 'manual');
74
+ try {
75
+ node.showPopover();
76
+ }
77
+ catch {
78
+ node.removeAttribute('popover');
79
+ setNativePopover(false);
80
+ return;
81
+ }
82
+ }
83
+ else
84
+ node.removeAttribute('popover');
85
+ const view = trigger.ownerDocument.defaultView;
86
+ if (!view)
87
+ return;
88
+ const box = trigger.getBoundingClientRect();
89
+ const availableWidth = Math.max(0, view.innerWidth - 24);
90
+ const availableHeight = Math.max(0, view.innerHeight - 24);
91
+ node.style.width = `${Math.min(width === 'anchor' ? box.width : width, availableWidth)}px`;
92
+ const maximum = Number.parseFloat(view.getComputedStyle(node).maxHeight);
93
+ node.style.maxHeight = `${Math.min(Number.isFinite(maximum) ? maximum : availableHeight, availableHeight)}px`;
94
+ node.style.overflowY = 'auto';
95
+ const height = node.getBoundingClientRect().height;
96
+ node.style.left = `${Math.max(12, Math.min(box.left, view.innerWidth - node.getBoundingClientRect().width - 12))}px`;
97
+ node.style.top = `${Math.max(12, box.bottom + height + 8 > view.innerHeight ? box.top - height - 8 : box.bottom + 8)}px`;
98
+ }, [open, portal, nativePopover, anchor, popup, width, contentKey]);
99
+ return portal;
100
+ }
@@ -0,0 +1,140 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ import { chartMessages } from './messages.js';
3
+ declare module '../messages.js' {
4
+ interface DiyaMessages {
5
+ chart: typeof chartMessages;
6
+ }
7
+ }
8
+ /** 'line', 'area', 'bar' and 'combo' plot `series` over `categories`; 'pie', 'donut' and 'treemap' plot `data`. */
9
+ export type ChartType = 'line' | 'area' | 'bar' | 'donut' | 'pie' | 'combo' | 'treemap';
10
+ export type ChartSeries = {
11
+ /** Series name, shown in the legend, tooltip and data table. */
12
+ name: string;
13
+ /** One value per category, in the same order; null leaves a gap. */
14
+ values: (number | null)[];
15
+ /** Overrides this series' palette color. */
16
+ color?: string;
17
+ /** Draws this series as a line or bars, overriding the chart type; useful in 'combo'. */
18
+ type?: 'line' | 'bar';
19
+ };
20
+ export type ChartDatum = {
21
+ /** Slice or tile name, shown in the legend, tooltip and data table. */
22
+ name: string;
23
+ /** Non-negative size; leave it out on a parent to use the sum of its children. */
24
+ value?: number;
25
+ /** Nested tiles, for 'treemap' only. */
26
+ children?: ChartDatum[];
27
+ /** Overrides this item's palette color. */
28
+ color?: string;
29
+ };
30
+ /** DIYA's chart colors, "Vivid": bright system blue, orange, pink, green, violet, teal, indigo and red, ordered so neighbours stay apart. Bright hues sit below 3:1 on white, so every chart also ships its legend, tooltips and a data table. */
31
+ export declare const chartPalettes: {
32
+ light: string[];
33
+ dark: string[];
34
+ };
35
+ export type ChartProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
36
+ /** Chart kind: 'line', 'area', 'bar' and 'combo' (bars with the last series as a line) use `series`; 'pie', 'donut' and 'treemap' use `data`. */
37
+ type: ChartType;
38
+ /** Accessible name for the chart image (not shown). */
39
+ label: string;
40
+ /** Category labels along the category axis, for line, area, bar and combo. */
41
+ categories?: string[];
42
+ /** Value series, for line, area, bar and combo. */
43
+ series?: ChartSeries[];
44
+ /** Slices or tiles, for pie, donut and treemap. */
45
+ data?: ChartDatum[];
46
+ /** Style tweak; defaults to 'standard'. 'smooth' curves lines (line, area, combo), 'stacked' stacks series (line, area, bar), 'horizontal' lays bars sideways (bar), 'thin' narrows the ring (donut). */
47
+ variant?: 'standard' | 'smooth' | 'stacked' | 'horizontal' | 'thin';
48
+ /** Shows the legend; defaults to true. Treemaps never show one. */
49
+ legend?: boolean;
50
+ /** Shows value grid lines on cartesian charts; defaults to true. */
51
+ grid?: boolean;
52
+ /** Shows a tooltip on hover; defaults to true. */
53
+ tooltip?: boolean;
54
+ /** Fixed [min, max] for the value axis; by default it starts at 0 unless values go negative. */
55
+ valueDomain?: [number, number];
56
+ /** Chart height in pixels; defaults to 300. */
57
+ height?: number;
58
+ /** Series or slice colors, in order; defaults to a colored brand first, then DIYA's Vivid chart palette for the theme. */
59
+ palette?: string[];
60
+ /** Text under a donut's total; defaults to "Total". */
61
+ centerLabel?: string;
62
+ /** Formats every shown value (tooltips, value-axis labels, donut total, treemap labels, data table); defaults to the provider locale's number format. Keep it stable between renders. */
63
+ formatValue?: (value: number) => string;
64
+ };
65
+ /** A line, area, bar, combo, pie, donut or treemap chart. Data and formatting belong to the caller; rendering never fetches a service. */
66
+ export declare const Chart: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
67
+ /** Chart kind: 'line', 'area', 'bar' and 'combo' (bars with the last series as a line) use `series`; 'pie', 'donut' and 'treemap' use `data`. */
68
+ type: ChartType;
69
+ /** Accessible name for the chart image (not shown). */
70
+ label: string;
71
+ /** Category labels along the category axis, for line, area, bar and combo. */
72
+ categories?: string[];
73
+ /** Value series, for line, area, bar and combo. */
74
+ series?: ChartSeries[];
75
+ /** Slices or tiles, for pie, donut and treemap. */
76
+ data?: ChartDatum[];
77
+ /** Style tweak; defaults to 'standard'. 'smooth' curves lines (line, area, combo), 'stacked' stacks series (line, area, bar), 'horizontal' lays bars sideways (bar), 'thin' narrows the ring (donut). */
78
+ variant?: "standard" | "smooth" | "stacked" | "horizontal" | "thin";
79
+ /** Shows the legend; defaults to true. Treemaps never show one. */
80
+ legend?: boolean;
81
+ /** Shows value grid lines on cartesian charts; defaults to true. */
82
+ grid?: boolean;
83
+ /** Shows a tooltip on hover; defaults to true. */
84
+ tooltip?: boolean;
85
+ /** Fixed [min, max] for the value axis; by default it starts at 0 unless values go negative. */
86
+ valueDomain?: [number, number];
87
+ /** Chart height in pixels; defaults to 300. */
88
+ height?: number;
89
+ /** Series or slice colors, in order; defaults to a colored brand first, then DIYA's Vivid chart palette for the theme. */
90
+ palette?: string[];
91
+ /** Text under a donut's total; defaults to "Total". */
92
+ centerLabel?: string;
93
+ /** Formats every shown value (tooltips, value-axis labels, donut total, treemap labels, data table); defaults to the provider locale's number format. Keep it stable between renders. */
94
+ formatValue?: (value: number) => string;
95
+ } & import("react").RefAttributes<HTMLDivElement>>;
96
+ export type DataVizProps = Omit<ChartProps, 'label' | keyof HTMLAttributes<HTMLDivElement>> & Omit<HTMLAttributes<HTMLElement>, 'title' | 'children'> & {
97
+ /** Visible card heading; it is also the chart's accessible name, even when the header is hidden. */
98
+ title: string;
99
+ /** Heading level of the title, h1 to h6. Defaults to 3. */
100
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
101
+ /** Shows the title and description; defaults to true. */
102
+ showHeader?: boolean;
103
+ /** Supporting text under the title. */
104
+ description?: string;
105
+ /** Slot in the header corner, e.g. a Select for the time range. */
106
+ actions?: ReactNode;
107
+ /** Slot above the chart, usually a headline number. */
108
+ summary?: ReactNode;
109
+ /** Small content in the footer, e.g. the data source. */
110
+ footer?: ReactNode;
111
+ /** Lifts the card with a shadow instead of a flat border; defaults to false. */
112
+ shadow?: boolean;
113
+ /** Shows a button that reveals the chart's data as a table; defaults to true. */
114
+ showDataToggle?: boolean;
115
+ /** Caption of the data table; defaults to "{title} data". */
116
+ dataCaption?: string;
117
+ };
118
+ /** A chart in a titled card, with an optional summary, actions and a show-data table. */
119
+ export declare const DataViz: import("react").ForwardRefExoticComponent<Omit<ChartProps, "label" | keyof HTMLAttributes<HTMLDivElement>> & Omit<HTMLAttributes<HTMLElement>, "children" | "title"> & {
120
+ /** Visible card heading; it is also the chart's accessible name, even when the header is hidden. */
121
+ title: string;
122
+ /** Heading level of the title, h1 to h6. Defaults to 3. */
123
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
124
+ /** Shows the title and description; defaults to true. */
125
+ showHeader?: boolean;
126
+ /** Supporting text under the title. */
127
+ description?: string;
128
+ /** Slot in the header corner, e.g. a Select for the time range. */
129
+ actions?: ReactNode;
130
+ /** Slot above the chart, usually a headline number. */
131
+ summary?: ReactNode;
132
+ /** Small content in the footer, e.g. the data source. */
133
+ footer?: ReactNode;
134
+ /** Lifts the card with a shadow instead of a flat border; defaults to false. */
135
+ shadow?: boolean;
136
+ /** Shows a button that reveals the chart's data as a table; defaults to true. */
137
+ showDataToggle?: boolean;
138
+ /** Caption of the data table; defaults to "{title} data". */
139
+ dataCaption?: string;
140
+ } & import("react").RefAttributes<HTMLElement>>;