@inkra/react 0.1.0 → 0.3.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 (177) hide show
  1. package/README.md +186 -1
  2. package/dist/PencilIcon.js +14 -16
  3. package/dist/PencilLine.js +18 -12
  4. package/dist/PencilText.js +192 -59
  5. package/dist/alert-dialog/index.d.ts +64 -0
  6. package/dist/alert-dialog/index.js +111 -0
  7. package/dist/audio-player/index.d.ts +60 -0
  8. package/dist/audio-player/index.js +101 -0
  9. package/dist/carousel/index.d.ts +51 -0
  10. package/dist/carousel/index.js +290 -0
  11. package/dist/choice-grid/index.d.ts +38 -0
  12. package/dist/choice-grid/index.js +85 -0
  13. package/dist/color-picker/color.d.ts +15 -0
  14. package/dist/color-picker/color.js +55 -0
  15. package/dist/color-picker/index.d.ts +31 -0
  16. package/dist/color-picker/index.js +69 -0
  17. package/dist/command/index.d.ts +45 -0
  18. package/dist/command/index.js +39 -0
  19. package/dist/context-menu/index.d.ts +76 -0
  20. package/dist/context-menu/index.js +241 -0
  21. package/dist/data-table/index.d.ts +47 -0
  22. package/dist/data-table/index.js +91 -0
  23. package/dist/date-picker/index.d.ts +50 -0
  24. package/dist/date-picker/index.js +180 -0
  25. package/dist/display/ProgressRing.d.ts +6 -0
  26. package/dist/display/ProgressRing.js +146 -0
  27. package/dist/display/index.d.ts +27 -0
  28. package/dist/display/index.js +18 -2
  29. package/dist/dockable-panel/index.d.ts +41 -0
  30. package/dist/dockable-panel/index.js +253 -0
  31. package/dist/dockable-panel/model.d.ts +21 -0
  32. package/dist/dockable-panel/model.js +61 -0
  33. package/dist/drawer/index.d.ts +63 -0
  34. package/dist/drawer/index.js +88 -0
  35. package/dist/dropdown-menu/index.d.ts +1 -1
  36. package/dist/dropdown-menu/index.js +3 -2
  37. package/dist/editable/index.d.ts +53 -0
  38. package/dist/editable/index.js +162 -0
  39. package/dist/empty/index.d.ts +21 -0
  40. package/dist/empty/index.js +23 -0
  41. package/dist/flip/index.d.ts +49 -0
  42. package/dist/flip/index.js +138 -0
  43. package/dist/form/EditablePencilText.js +188 -143
  44. package/dist/form/Input.js +1 -1
  45. package/dist/form/Textarea.d.ts +12 -0
  46. package/dist/form/Textarea.js +83 -4
  47. package/dist/form/index.d.ts +1 -1
  48. package/dist/heatmap/index.d.ts +59 -0
  49. package/dist/heatmap/index.js +248 -0
  50. package/dist/hotkey/index.d.ts +37 -0
  51. package/dist/hotkey/index.js +130 -0
  52. package/dist/hotkey/model.d.ts +27 -0
  53. package/dist/hotkey/model.js +97 -0
  54. package/dist/hover-card/index.d.ts +33 -0
  55. package/dist/hover-card/index.js +141 -0
  56. package/dist/image-cropper/index.d.ts +82 -0
  57. package/dist/image-cropper/index.js +261 -0
  58. package/dist/image-cropper/model.d.ts +42 -0
  59. package/dist/image-cropper/model.js +94 -0
  60. package/dist/image-viewer/index.d.ts +79 -0
  61. package/dist/image-viewer/index.js +220 -0
  62. package/dist/image-viewer/model.d.ts +9 -0
  63. package/dist/image-viewer/model.js +18 -0
  64. package/dist/index.d.ts +47 -0
  65. package/dist/index.js +47 -0
  66. package/dist/input-number/index.d.ts +6 -0
  67. package/dist/input-number/index.js +126 -15
  68. package/dist/input-number/model.d.ts +13 -0
  69. package/dist/input-number/model.js +50 -0
  70. package/dist/internal/cache.d.ts +1 -1
  71. package/dist/internal/cache.js +1 -1
  72. package/dist/internal/renderTokens.d.ts +10 -0
  73. package/dist/internal/renderTokens.js +13 -0
  74. package/dist/internal/surfaceRenderer.d.ts +14 -6
  75. package/dist/internal/surfaceRenderer.js +15 -12
  76. package/dist/internal/textRenderer.d.ts +1 -0
  77. package/dist/internal/textRenderer.js +33 -7
  78. package/dist/internal/usePencilSurface.js +16 -8
  79. package/dist/internal/useSelectionDrawing.js +15 -11
  80. package/dist/journey-map/MapScale.js +3 -1
  81. package/dist/journey-map/index.js +3 -1
  82. package/dist/keypad/index.d.ts +23 -0
  83. package/dist/keypad/index.js +63 -0
  84. package/dist/keypad/model.d.ts +20 -0
  85. package/dist/keypad/model.js +33 -0
  86. package/dist/layout/index.d.ts +63 -0
  87. package/dist/layout/index.js +62 -0
  88. package/dist/log/index.d.ts +45 -0
  89. package/dist/log/index.js +159 -0
  90. package/dist/log/model.d.ts +16 -0
  91. package/dist/log/model.js +13 -0
  92. package/dist/node-graph/index.d.ts +123 -0
  93. package/dist/node-graph/index.js +304 -0
  94. package/dist/number-ticker/index.d.ts +31 -0
  95. package/dist/number-ticker/index.js +135 -0
  96. package/dist/number-ticker/model.d.ts +41 -0
  97. package/dist/number-ticker/model.js +163 -0
  98. package/dist/number-ticker/precision.d.ts +7 -0
  99. package/dist/number-ticker/precision.js +134 -0
  100. package/dist/overflow-list/index.d.ts +39 -0
  101. package/dist/overflow-list/index.js +230 -0
  102. package/dist/overflow-list/layout.d.ts +19 -0
  103. package/dist/overflow-list/layout.js +44 -0
  104. package/dist/overlay/Overlay.d.ts +1 -1
  105. package/dist/overlay/Overlay.js +4 -2
  106. package/dist/overlay/Popover.d.ts +1 -1
  107. package/dist/overlay/Popover.js +3 -2
  108. package/dist/overlay/themeBridge.d.ts +12 -1
  109. package/dist/overlay/themeBridge.js +27 -1
  110. package/dist/pagination/index.d.ts +23 -0
  111. package/dist/pagination/index.js +41 -0
  112. package/dist/pagination/model.d.ts +4 -0
  113. package/dist/pagination/model.js +42 -0
  114. package/dist/pan-zoom/index.d.ts +71 -0
  115. package/dist/pan-zoom/index.js +175 -0
  116. package/dist/radar/index.d.ts +56 -0
  117. package/dist/radar/index.js +220 -0
  118. package/dist/range-slider/index.d.ts +16 -0
  119. package/dist/range-slider/index.js +22 -0
  120. package/dist/rating/index.d.ts +28 -0
  121. package/dist/rating/index.js +127 -0
  122. package/dist/resizable/index.d.ts +32 -0
  123. package/dist/resizable/index.js +50 -0
  124. package/dist/scroll-area/index.d.ts +36 -0
  125. package/dist/scroll-area/index.js +44 -0
  126. package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
  127. package/dist/segmented-progress/SegmentedProgress.js +42 -0
  128. package/dist/slider/index.js +1 -1
  129. package/dist/slot/index.d.ts +24 -0
  130. package/dist/slot/index.js +29 -0
  131. package/dist/sortable/index.d.ts +50 -0
  132. package/dist/sortable/index.js +283 -0
  133. package/dist/sortable/model.d.ts +30 -0
  134. package/dist/sortable/model.js +64 -0
  135. package/dist/sparkline/index.d.ts +19 -0
  136. package/dist/sparkline/index.js +240 -0
  137. package/dist/sparkline/model.d.ts +16 -0
  138. package/dist/sparkline/model.js +28 -0
  139. package/dist/steps/index.d.ts +16 -0
  140. package/dist/steps/index.js +37 -0
  141. package/dist/styles.css +4957 -269
  142. package/dist/tag-input/index.d.ts +69 -0
  143. package/dist/tag-input/index.js +325 -0
  144. package/dist/tag-input/model.d.ts +14 -0
  145. package/dist/tag-input/model.js +38 -0
  146. package/dist/time-picker/index.d.ts +15 -0
  147. package/dist/time-picker/index.js +168 -0
  148. package/dist/timeline/index.d.ts +14 -0
  149. package/dist/timeline/index.js +16 -0
  150. package/dist/toast/index.d.ts +27 -0
  151. package/dist/toast/index.js +36 -0
  152. package/dist/toggle-group/index.d.ts +17 -0
  153. package/dist/toggle-group/index.js +26 -0
  154. package/dist/tokens.css +10 -9
  155. package/dist/tooltip/Tooltip.d.ts +1 -1
  156. package/dist/tooltip/Tooltip.js +3 -2
  157. package/dist/transfer/index.d.ts +64 -0
  158. package/dist/transfer/index.js +174 -0
  159. package/dist/transfer/model.d.ts +12 -0
  160. package/dist/transfer/model.js +33 -0
  161. package/dist/tree/index.d.ts +39 -0
  162. package/dist/tree/index.js +36 -0
  163. package/dist/tree-select/index.d.ts +59 -0
  164. package/dist/tree-select/index.js +142 -0
  165. package/dist/tree-select/model.d.ts +31 -0
  166. package/dist/tree-select/model.js +135 -0
  167. package/dist/typewriter/index.d.ts +21 -0
  168. package/dist/typewriter/index.js +101 -0
  169. package/dist/typewriter/model.d.ts +7 -0
  170. package/dist/typewriter/model.js +45 -0
  171. package/dist/upload/index.d.ts +59 -0
  172. package/dist/upload/index.js +230 -0
  173. package/dist/virtual-list/index.d.ts +44 -0
  174. package/dist/virtual-list/index.js +129 -0
  175. package/dist/virtual-list/model.d.ts +7 -0
  176. package/dist/virtual-list/model.js +27 -0
  177. package/package.json +23 -3
@@ -0,0 +1,69 @@
1
+ import type { HTMLAttributes, InputHTMLAttributes } from 'react';
2
+ import type { FieldControlSize, FieldControlStateProps } from '../form/types.js';
3
+ import { createDefaultTag, defaultTagInputDelimiters, defaultTagValueKey, hasTagValue, normalizeTagInputValue, splitTagInputValue } from './model.js';
4
+ import type { TagInputIdentity } from './model.js';
5
+ export interface TagInputTag extends TagInputIdentity {
6
+ label?: string;
7
+ }
8
+ export type TagInputVariant = 'underline' | 'outline' | 'soft';
9
+ export type TagInputCommitReason = 'enter' | 'delimiter' | 'paste';
10
+ export type TagInputChangeReason = 'add' | 'remove' | 'clear';
11
+ export type TagInputValidationCode = 'duplicate' | 'max-tags' | 'invalid';
12
+ export type TagInputValidationResult = boolean | string | null | undefined;
13
+ export interface TagInputChangeDetails {
14
+ reason: TagInputChangeReason;
15
+ tags: readonly TagInputTag[];
16
+ inputReason?: TagInputCommitReason;
17
+ }
18
+ export interface TagInputDraftChangeDetails {
19
+ reason: 'input' | 'commit' | 'clear';
20
+ }
21
+ export interface TagInputValidationContext {
22
+ tags: readonly TagInputTag[];
23
+ reason: TagInputCommitReason;
24
+ }
25
+ export interface TagInputValidationDetails extends TagInputValidationContext {
26
+ candidate: string;
27
+ code: TagInputValidationCode;
28
+ }
29
+ export interface TagInputLabels {
30
+ tagList: string;
31
+ removeTag: (tag: TagInputTag) => string;
32
+ clearAll: string;
33
+ duplicate: (candidate: string) => string;
34
+ maxTags: (maximum: number) => string;
35
+ invalid: (candidate: string) => string;
36
+ }
37
+ export type TagInputClassNames = Partial<Record<'root' | 'control' | 'list' | 'tag' | 'input' | 'clear' | 'message', string>>;
38
+ export interface TagInputCreateContext extends TagInputValidationContext {
39
+ }
40
+ export interface TagInputProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'defaultValue' | 'onChange'>, FieldControlStateProps {
41
+ value?: readonly TagInputTag[];
42
+ defaultValue?: readonly TagInputTag[];
43
+ onValueChange?: (value: readonly TagInputTag[], details: TagInputChangeDetails) => void;
44
+ inputValue?: string;
45
+ defaultInputValue?: string;
46
+ onInputValueChange?: (value: string, details: TagInputDraftChangeDetails) => void;
47
+ createTag?: (candidate: string, context: TagInputCreateContext) => TagInputTag | null | undefined;
48
+ validateTag?: (candidate: string, context: TagInputValidationContext) => TagInputValidationResult;
49
+ onValidationError?: (message: string, details: TagInputValidationDetails) => void;
50
+ validationMessage?: string;
51
+ getTagValueKey?: (value: string) => string;
52
+ delimiters?: readonly string[];
53
+ maxTags?: number;
54
+ disabled?: boolean;
55
+ readOnly?: boolean;
56
+ required?: boolean;
57
+ name?: string;
58
+ form?: string;
59
+ placeholder?: string;
60
+ clearable?: boolean;
61
+ fullWidth?: boolean;
62
+ variant?: TagInputVariant;
63
+ labels?: Partial<TagInputLabels>;
64
+ inputProps?: Omit<InputHTMLAttributes<HTMLInputElement>, 'defaultValue' | 'disabled' | 'form' | 'id' | 'name' | 'placeholder' | 'readOnly' | 'required' | 'size' | 'value'>;
65
+ classNames?: TagInputClassNames;
66
+ }
67
+ export declare const TagInput: import("react").ForwardRefExoticComponent<TagInputProps & import("react").RefAttributes<HTMLInputElement>>;
68
+ export { createDefaultTag, defaultTagInputDelimiters, defaultTagValueKey, hasTagValue, normalizeTagInputValue, splitTagInputValue, };
69
+ export type { FieldControlSize, TagInputIdentity };
@@ -0,0 +1,325 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
3
+ import { PencilIcon } from '../PencilIcon.js';
4
+ import { PencilLine } from '../PencilLine.js';
5
+ import { PencilSurface } from '../PencilSurface.js';
6
+ import { PencilText } from '../PencilText.js';
7
+ import { useResolvedControlProps } from '../form/controlProps.js';
8
+ import { createDefaultTag, defaultTagInputDelimiters, defaultTagValueKey, hasTagValue, normalizeTagInputValue, splitTagInputValue, } from './model.js';
9
+ const removePaths = [{ d: 'M18 6 6 18' }, { d: 'm6 6 12 12' }];
10
+ const clearPaths = [{ d: 'M4 7h16' }, { d: 'M9 7V4h6v3' }, { d: 'm7 7 1 13h8l1-13' }];
11
+ const classes = (...values) => values.filter(Boolean).join(' ');
12
+ const defaultLabels = {
13
+ tagList: 'Selected tags',
14
+ removeTag: tag => `Remove ${tag.label ?? tag.value}`,
15
+ clearAll: 'Clear all tags',
16
+ duplicate: candidate => `${candidate} is already added.`,
17
+ maxTags: maximum => `You can add up to ${maximum} tags.`,
18
+ invalid: candidate => `${candidate} is not a valid tag.`,
19
+ };
20
+ function joinAriaIds(...values) {
21
+ const ids = values.flatMap(value => value?.split(/\s+/).filter(Boolean) ?? []);
22
+ return [...new Set(ids)].join(' ') || undefined;
23
+ }
24
+ function resolvedLabels(labels) {
25
+ return {
26
+ tagList: labels?.tagList ?? defaultLabels.tagList,
27
+ removeTag: labels?.removeTag ?? defaultLabels.removeTag,
28
+ clearAll: labels?.clearAll ?? defaultLabels.clearAll,
29
+ duplicate: labels?.duplicate ?? defaultLabels.duplicate,
30
+ maxTags: labels?.maxTags ?? defaultLabels.maxTags,
31
+ invalid: labels?.invalid ?? defaultLabels.invalid,
32
+ };
33
+ }
34
+ function validationText(result, fallback) {
35
+ if (typeof result === 'string')
36
+ return result || fallback;
37
+ if (result === false)
38
+ return fallback;
39
+ return undefined;
40
+ }
41
+ export const TagInput = forwardRef(function TagInput({ 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, classNames = {}, clearable = true, createTag, defaultInputValue = '', defaultValue = [], delimiters = defaultTagInputDelimiters, disabled, form, fullWidth = true, getTagValueKey = defaultTagValueKey, id, inputProps = {}, inputValue, invalid = false, labels, maxTags, name, onClick, onInputValueChange, onValidationError, onValueChange, placeholder, readOnly, required, seed, size = 'md', validationMessage, validateTag, value, variant = 'outline', ...rootProps }, forwardedRef) {
42
+ const generatedId = useId();
43
+ const inputRef = useRef(null);
44
+ const removeRefs = useRef(new Map());
45
+ const composingRef = useRef(false);
46
+ const defaultTagsRef = useRef(defaultValue);
47
+ const defaultDraftRef = useRef(defaultInputValue);
48
+ const [uncontrolledTags, setUncontrolledTags] = useState(() => [...defaultValue]);
49
+ const [uncontrolledDraft, setUncontrolledDraft] = useState(defaultInputValue);
50
+ const [localValidationMessage, setLocalValidationMessage] = useState();
51
+ const [focusWithin, setFocusWithin] = useState(false);
52
+ const [pendingFocus, setPendingFocus] = useState(null);
53
+ const tagsControlled = value !== undefined;
54
+ const draftControlled = inputValue !== undefined;
55
+ const currentTags = tagsControlled ? value : uncontrolledTags;
56
+ const currentDraft = draftControlled ? inputValue : uncontrolledDraft;
57
+ const copy = useMemo(() => resolvedLabels(labels), [labels]);
58
+ const activeValidationMessage = validationMessage ?? localValidationMessage;
59
+ const messageId = activeValidationMessage ? `inkra-tag-input-${generatedId}-message` : undefined;
60
+ const state = useResolvedControlProps({
61
+ id,
62
+ required,
63
+ disabled,
64
+ readOnly,
65
+ invalid: invalid || Boolean(activeValidationMessage),
66
+ 'aria-describedby': joinAriaIds(ariaDescribedBy, messageId),
67
+ 'aria-errormessage': activeValidationMessage ? messageId : ariaErrorMessage,
68
+ 'aria-invalid': ariaInvalid,
69
+ });
70
+ const resolvedSeed = seed ?? `inkra-tag-input-${state.id ?? generatedId}`;
71
+ const limit = typeof maxTags === 'number' && Number.isFinite(maxTags)
72
+ ? Math.max(0, Math.floor(maxTags))
73
+ : Number.POSITIVE_INFINITY;
74
+ const atLimit = currentTags.length >= limit;
75
+ const canEdit = !state.disabled && !state.readOnly;
76
+ const surfaceVariant = state.invalid
77
+ ? 'danger'
78
+ : variant === 'soft'
79
+ ? 'quiet'
80
+ : variant === 'underline'
81
+ ? 'ghost'
82
+ : 'outline';
83
+ defaultTagsRef.current = defaultValue;
84
+ defaultDraftRef.current = defaultInputValue;
85
+ useImperativeHandle(forwardedRef, () => inputRef.current);
86
+ useEffect(() => {
87
+ if (!pendingFocus)
88
+ return;
89
+ const target = 'input' in pendingFocus
90
+ ? inputRef.current
91
+ : removeRefs.current.get(pendingFocus.tagId) ?? inputRef.current;
92
+ target?.focus({ preventScroll: true });
93
+ setPendingFocus(null);
94
+ }, [currentTags, pendingFocus]);
95
+ useEffect(() => {
96
+ const input = inputRef.current;
97
+ const ownerForm = input?.form;
98
+ if (!input || !ownerForm || (tagsControlled && draftControlled))
99
+ return;
100
+ let timer;
101
+ const reset = (event) => {
102
+ clearTimeout(timer);
103
+ timer = setTimeout(() => {
104
+ if (event.defaultPrevented)
105
+ return;
106
+ if (!tagsControlled)
107
+ setUncontrolledTags([...defaultTagsRef.current]);
108
+ if (!draftControlled)
109
+ setUncontrolledDraft(defaultDraftRef.current);
110
+ setLocalValidationMessage(undefined);
111
+ }, 0);
112
+ };
113
+ ownerForm.addEventListener('reset', reset);
114
+ return () => {
115
+ clearTimeout(timer);
116
+ ownerForm.removeEventListener('reset', reset);
117
+ };
118
+ }, [draftControlled, form, tagsControlled]);
119
+ const updateDraft = (next, details) => {
120
+ if (!draftControlled)
121
+ setUncontrolledDraft(next);
122
+ onInputValueChange?.(next, details);
123
+ };
124
+ const commitTags = (next, details) => {
125
+ if (!tagsControlled)
126
+ setUncontrolledTags([...next]);
127
+ onValueChange?.(next, details);
128
+ };
129
+ const reportValidation = (candidate, reason, code, message, tags) => {
130
+ setLocalValidationMessage(message);
131
+ onValidationError?.(message, { candidate, code, reason, tags });
132
+ };
133
+ const commitCandidates = (candidates, reason) => {
134
+ if (!canEdit)
135
+ return false;
136
+ const working = [...currentTags];
137
+ const added = [];
138
+ let rejected;
139
+ for (const rawCandidate of candidates) {
140
+ const candidate = normalizeTagInputValue(rawCandidate);
141
+ if (!candidate)
142
+ continue;
143
+ if (working.length >= limit) {
144
+ rejected = { candidate, code: 'max-tags', message: copy.maxTags(limit) };
145
+ break;
146
+ }
147
+ if (hasTagValue(working, candidate, getTagValueKey)) {
148
+ rejected = { candidate, code: 'duplicate', message: copy.duplicate(candidate) };
149
+ break;
150
+ }
151
+ const context = { tags: working, reason };
152
+ const message = validationText(validateTag?.(candidate, context), copy.invalid(candidate));
153
+ if (message) {
154
+ rejected = { candidate, code: 'invalid', message };
155
+ break;
156
+ }
157
+ const tag = createTag ? createTag(candidate, context) : createDefaultTag(candidate);
158
+ if (!tag || !tag.id || !normalizeTagInputValue(tag.value)) {
159
+ rejected = { candidate, code: 'invalid', message: copy.invalid(candidate) };
160
+ break;
161
+ }
162
+ if (working.some(current => current.id === tag.id) || hasTagValue(working, tag.value, getTagValueKey)) {
163
+ rejected = { candidate, code: 'duplicate', message: copy.duplicate(candidate) };
164
+ break;
165
+ }
166
+ working.push(tag);
167
+ added.push(tag);
168
+ }
169
+ if (added.length > 0)
170
+ commitTags(working, { reason: 'add', tags: added, inputReason: reason });
171
+ if (rejected) {
172
+ updateDraft(rejected.candidate, { reason: 'commit' });
173
+ reportValidation(rejected.candidate, reason, rejected.code, rejected.message, working);
174
+ inputRef.current?.focus({ preventScroll: true });
175
+ return false;
176
+ }
177
+ setLocalValidationMessage(undefined);
178
+ updateDraft('', { reason: 'commit' });
179
+ inputRef.current?.focus({ preventScroll: true });
180
+ return added.length > 0;
181
+ };
182
+ const commitDraft = (reason) => {
183
+ const parsed = splitTagInputValue(currentDraft, delimiters);
184
+ return commitCandidates(parsed.parts, reason);
185
+ };
186
+ const removeTag = (index, focus = null) => {
187
+ if (!canEdit)
188
+ return;
189
+ const tag = currentTags[index];
190
+ if (!tag)
191
+ return;
192
+ const next = currentTags.filter((_, tagIndex) => tagIndex !== index);
193
+ const nextFocus = focus ?? (currentTags[index + 1]
194
+ ? { tagId: currentTags[index + 1].id }
195
+ : currentTags[index - 1]
196
+ ? { tagId: currentTags[index - 1].id }
197
+ : { input: true });
198
+ setLocalValidationMessage(undefined);
199
+ commitTags(next, { reason: 'remove', tags: [tag] });
200
+ setPendingFocus(nextFocus);
201
+ };
202
+ const clearAll = () => {
203
+ if (!canEdit || currentTags.length === 0)
204
+ return;
205
+ const removed = [...currentTags];
206
+ setLocalValidationMessage(undefined);
207
+ commitTags([], { reason: 'clear', tags: removed });
208
+ updateDraft('', { reason: 'clear' });
209
+ setPendingFocus({ input: true });
210
+ };
211
+ const focusRemoveButton = (index) => {
212
+ const tag = currentTags[index];
213
+ if (tag)
214
+ removeRefs.current.get(tag.id)?.focus({ preventScroll: true });
215
+ };
216
+ const handleInputChange = (event) => {
217
+ inputProps.onChange?.(event);
218
+ if (event.defaultPrevented)
219
+ return;
220
+ setLocalValidationMessage(undefined);
221
+ updateDraft(event.currentTarget.value, { reason: 'input' });
222
+ };
223
+ const handleInputPaste = (event) => {
224
+ inputProps.onPaste?.(event);
225
+ if (event.defaultPrevented || !canEdit || composingRef.current)
226
+ return;
227
+ const pasted = event.clipboardData.getData('text');
228
+ const start = event.currentTarget.selectionStart ?? currentDraft.length;
229
+ const end = event.currentTarget.selectionEnd ?? start;
230
+ const combined = `${currentDraft.slice(0, start)}${pasted}${currentDraft.slice(end)}`;
231
+ const parsed = splitTagInputValue(combined, delimiters);
232
+ if (!parsed.hasDelimiter)
233
+ return;
234
+ event.preventDefault();
235
+ commitCandidates(parsed.parts, 'paste');
236
+ };
237
+ const handleInputKeyDown = (event) => {
238
+ inputProps.onKeyDown?.(event);
239
+ if (event.defaultPrevented || !canEdit)
240
+ return;
241
+ const nativeEvent = event.nativeEvent;
242
+ if (composingRef.current || nativeEvent.isComposing || nativeEvent.keyCode === 229)
243
+ return;
244
+ const direction = getComputedStyle(event.currentTarget).direction;
245
+ const previousKey = direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
246
+ if (event.key === previousKey && event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === 0) {
247
+ if (currentTags.length > 0 && !state.readOnly) {
248
+ event.preventDefault();
249
+ focusRemoveButton(currentTags.length - 1);
250
+ }
251
+ return;
252
+ }
253
+ if (event.key === 'Backspace' && currentDraft.length === 0 && currentTags.length > 0) {
254
+ event.preventDefault();
255
+ removeTag(currentTags.length - 1, { input: true });
256
+ return;
257
+ }
258
+ if (event.key === 'Enter') {
259
+ event.preventDefault();
260
+ commitDraft('enter');
261
+ return;
262
+ }
263
+ if (delimiters.includes(event.key)) {
264
+ event.preventDefault();
265
+ commitDraft('delimiter');
266
+ }
267
+ };
268
+ const handleTagKeyDown = (event, index) => {
269
+ if (event.defaultPrevented || !canEdit)
270
+ return;
271
+ const direction = getComputedStyle(event.currentTarget).direction;
272
+ const previousKey = direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft';
273
+ const nextKey = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight';
274
+ if (event.key === previousKey) {
275
+ event.preventDefault();
276
+ focusRemoveButton(Math.max(0, index - 1));
277
+ }
278
+ else if (event.key === nextKey) {
279
+ event.preventDefault();
280
+ if (index >= currentTags.length - 1)
281
+ inputRef.current?.focus({ preventScroll: true });
282
+ else
283
+ focusRemoveButton(index + 1);
284
+ }
285
+ else if (event.key === 'Home') {
286
+ event.preventDefault();
287
+ focusRemoveButton(0);
288
+ }
289
+ else if (event.key === 'End') {
290
+ event.preventDefault();
291
+ inputRef.current?.focus({ preventScroll: true });
292
+ }
293
+ else if (event.key === 'Backspace' || event.key === 'Delete') {
294
+ event.preventDefault();
295
+ removeTag(index);
296
+ }
297
+ };
298
+ return (_jsxs("div", { ...rootProps, className: classes('inkra-field-control-hit', 'inkra-tag-input', className, classNames.root), "data-disabled": state.disabled || undefined, "data-focus-within": focusWithin || undefined, "data-full-width": fullWidth, "data-invalid": state.invalid || undefined, "data-max-reached": atLimit || undefined, "data-readonly": state.readOnly || undefined, "data-size": size, "data-variant": variant, onBlurCapture: event => {
299
+ if (!event.currentTarget.contains(event.relatedTarget))
300
+ setFocusWithin(false);
301
+ rootProps.onBlurCapture?.(event);
302
+ }, onClick: event => {
303
+ onClick?.(event);
304
+ if (event.defaultPrevented || state.disabled)
305
+ return;
306
+ const target = event.target;
307
+ if (!target.closest('button, input'))
308
+ inputRef.current?.focus({ preventScroll: true });
309
+ }, onFocusCapture: event => {
310
+ setFocusWithin(true);
311
+ rootProps.onFocusCapture?.(event);
312
+ }, children: [_jsxs(PencilSurface, { as: "div", className: classes('inkra-field-control', 'inkra-tag-input__control', classNames.control), radius: 8, seed: resolvedSeed, variant: surfaceVariant, children: [_jsx("ul", { "aria-label": copy.tagList, className: classes('inkra-tag-input__list', classNames.list), children: currentTags.map((tag, index) => (_jsxs(PencilSurface, { as: "li", className: classes('inkra-tag-input__tag', classNames.tag), clipContent: true, "data-tag-id": tag.id, radius: 6, seed: `${resolvedSeed}-tag-${tag.id}`, variant: "quiet", children: [_jsx("span", { className: "inkra-tag-input__tag-label", children: tag.label ?? tag.value }), !state.readOnly ? (_jsx("button", { "aria-label": copy.removeTag(tag), className: "inkra-tag-input__remove", disabled: state.disabled, onClick: () => removeTag(index), onKeyDown: event => handleTagKeyDown(event, index), ref: node => {
313
+ if (node)
314
+ removeRefs.current.set(tag.id, node);
315
+ else
316
+ removeRefs.current.delete(tag.id);
317
+ }, title: copy.removeTag(tag), type: "button", children: _jsx(PencilIcon, { decorative: true, paths: removePaths, seed: `${resolvedSeed}-remove-${tag.id}`, size: 18, sourceSize: 24 }) })) : null] }, `${tag.id}-${index}`))) }), _jsx("input", { ...inputProps, "aria-describedby": state.describedBy, "aria-errormessage": state.errorMessage, "aria-invalid": state.ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-readonly": state.readOnly || undefined, "aria-required": state.required || undefined, autoComplete: inputProps.autoComplete ?? 'off', className: classes('inkra-input', 'inkra-tag-input__input', inputProps.className, classNames.input), disabled: state.disabled, form: form, id: state.id, onChange: handleInputChange, onCompositionEnd: event => {
318
+ composingRef.current = false;
319
+ inputProps.onCompositionEnd?.(event);
320
+ }, onCompositionStart: event => {
321
+ composingRef.current = true;
322
+ inputProps.onCompositionStart?.(event);
323
+ }, onKeyDown: handleInputKeyDown, onPaste: handleInputPaste, placeholder: placeholder, readOnly: state.readOnly, ref: inputRef, required: state.required && currentTags.length === 0, spellCheck: inputProps.spellCheck ?? false, type: "text", value: currentDraft }), clearable && currentTags.length > 0 && !state.readOnly ? (_jsx("button", { "aria-label": copy.clearAll, className: classes('inkra-tag-input__clear', classNames.clear), disabled: state.disabled, onClick: clearAll, onPointerDown: (event) => event.preventDefault(), title: copy.clearAll, type: "button", children: _jsx(PencilIcon, { decorative: true, paths: clearPaths, seed: `${resolvedSeed}-clear`, size: 20, sourceSize: 24 }) })) : null, variant === 'underline' ? (_jsx(PencilLine, { className: "inkra-tag-input__underline", seed: `${resolvedSeed}-underline`, weight: (focusWithin ? 1.5 : 1) + (state.invalid ? 0.1 : 0) })) : null] }), name ? currentTags.map((tag, index) => (_jsx("input", { disabled: state.disabled, form: form, name: name, type: "hidden", value: tag.value }, `${tag.id}-${index}`))) : null, activeValidationMessage ? (_jsx(PencilText, { as: "span", className: classes('inkra-tag-input__message', classNames.message), id: messageId, role: "alert", seed: `${resolvedSeed}-message`, children: activeValidationMessage })) : null] }));
324
+ });
325
+ export { createDefaultTag, defaultTagInputDelimiters, defaultTagValueKey, hasTagValue, normalizeTagInputValue, splitTagInputValue, };
@@ -0,0 +1,14 @@
1
+ export interface TagInputIdentity {
2
+ id: string;
3
+ value: string;
4
+ }
5
+ export interface SplitTagInputResult {
6
+ parts: string[];
7
+ hasDelimiter: boolean;
8
+ }
9
+ export declare const defaultTagInputDelimiters: readonly [",", ";", "\n"];
10
+ export declare function normalizeTagInputValue(value: string): string;
11
+ export declare function defaultTagValueKey(value: string): string;
12
+ export declare function splitTagInputValue(value: string, delimiters?: readonly string[]): SplitTagInputResult;
13
+ export declare function hasTagValue(tags: readonly TagInputIdentity[], value: string, getTagValueKey?: (value: string) => string): boolean;
14
+ export declare function createDefaultTag(value: string): TagInputIdentity;
@@ -0,0 +1,38 @@
1
+ export const defaultTagInputDelimiters = [',', ';', '\n'];
2
+ export function normalizeTagInputValue(value) {
3
+ return value.trim();
4
+ }
5
+ export function defaultTagValueKey(value) {
6
+ return normalizeTagInputValue(value).toLocaleLowerCase();
7
+ }
8
+ export function splitTagInputValue(value, delimiters = defaultTagInputDelimiters) {
9
+ const activeDelimiters = [...new Set(delimiters.filter(Boolean))]
10
+ .sort((left, right) => right.length - left.length);
11
+ if (activeDelimiters.length === 0)
12
+ return { parts: [value], hasDelimiter: false };
13
+ const parts = [];
14
+ let start = 0;
15
+ let index = 0;
16
+ let hasDelimiter = false;
17
+ while (index < value.length) {
18
+ const delimiter = activeDelimiters.find(candidate => value.startsWith(candidate, index));
19
+ if (!delimiter) {
20
+ index += 1;
21
+ continue;
22
+ }
23
+ parts.push(value.slice(start, index));
24
+ index += delimiter.length;
25
+ start = index;
26
+ hasDelimiter = true;
27
+ }
28
+ parts.push(value.slice(start));
29
+ return { parts, hasDelimiter };
30
+ }
31
+ export function hasTagValue(tags, value, getTagValueKey = defaultTagValueKey) {
32
+ const candidateKey = getTagValueKey(value);
33
+ return tags.some(tag => getTagValueKey(tag.value) === candidateKey);
34
+ }
35
+ export function createDefaultTag(value) {
36
+ const normalized = normalizeTagInputValue(value);
37
+ return { id: normalized, value: normalized };
38
+ }
@@ -0,0 +1,15 @@
1
+ import type { MouseEvent, ReactNode } from 'react';
2
+ import type { InputProps, InputValueChangeDetails } from '../form/Input.js';
3
+ export type TimePickerHourCycle = 'h12' | 'h24';
4
+ export type TimePickerChangeDetails = InputValueChangeDetails;
5
+ export interface TimePickerProps extends Omit<InputProps, 'clearable' | 'clearButtonLabel' | 'onClear' | 'onValueChange' | 'type'> {
6
+ hourCycle?: TimePickerHourCycle;
7
+ locale?: string;
8
+ showSeconds?: boolean;
9
+ clearable?: boolean;
10
+ clearButtonLabel?: string;
11
+ onClear?: (event: MouseEvent<HTMLButtonElement>) => void;
12
+ onValueChange?: (value: string, details: TimePickerChangeDetails) => void;
13
+ startIcon?: ReactNode;
14
+ }
15
+ export declare const TimePicker: import("react").ForwardRefExoticComponent<TimePickerProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,168 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
3
+ import { Button } from '../Button.js';
4
+ import { useResolvedControlProps } from '../form/controlProps.js';
5
+ import { mergeAriaIds, useFieldContext } from '../form/context.js';
6
+ import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
7
+ import { PencilIcon } from '../PencilIcon.js';
8
+ import { PencilText } from '../PencilText.js';
9
+ import { PencilLine } from '../PencilLine.js';
10
+ import { Popover } from '../overlay/Popover.js';
11
+ import { RadioGroup, RadioGroupItem } from '../selection/RadioGroup.js';
12
+ const clockPaths = [
13
+ { d: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20Z' },
14
+ { d: 'M12 6v6l4 2' },
15
+ ];
16
+ const clearPaths = [{ d: 'M18 6 6 18' }, { d: 'm6 6 12 12' }];
17
+ const pad = (value) => {
18
+ const [whole, fraction] = String(value).split('.');
19
+ return `${whole.padStart(2, '0')}${fraction ? `.${fraction}` : ''}`;
20
+ };
21
+ const times = (length, start = 0) => Array.from({ length }, (_, index) => index + start);
22
+ function partsOf(value) {
23
+ if (!/^(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d{1,3})?)?$/.test(value))
24
+ return undefined;
25
+ const [hour, minute, second = 0] = value.split(':').map(Number);
26
+ return [hour, minute, second];
27
+ }
28
+ function TimeColumn({ name, label, value, options, onChange, format = pad }) {
29
+ return _jsx(RadioGroup, { name: name, label: label, value: String(value), variant: "segmented", className: "inkra-time-picker__column", onValueChange: next => onChange(Number(next)), children: options.map(option => _jsx(RadioGroupItem, { value: String(option), label: format(option) }, option)) });
30
+ }
31
+ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef) {
32
+ const { value, defaultValue, onValueChange, onChange, onClear, clearable = true, clearButtonLabel, hourCycle = 'h24', locale, lang, showSeconds = false, step, min, max, name, form, id, disabled, readOnly, required, invalid, 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, style, size = 'md', fullWidth = true, startIcon, endIcon, endAction, startAction, prefix, suffix, placeholder, seed, variant = 'outline', textAlign = 'start', loading, classNames, ...nativeProps } = allProps;
33
+ const generatedId = useId();
34
+ const field = useFieldContext();
35
+ const inputRef = useRef(null);
36
+ const triggerRef = useRef(null);
37
+ const contentRef = useRef(null);
38
+ const initialValue = useRef(String(defaultValue ?? value ?? ''));
39
+ const controlled = Object.prototype.hasOwnProperty.call(allProps, 'value');
40
+ const [internalValue, setInternalValue] = useState(() => String(defaultValue ?? ''));
41
+ const [open, setOpen] = useState(false);
42
+ const [draft, setDraft] = useState('00:00');
43
+ const [draftValid, setDraftValid] = useState(true);
44
+ const [constraintInvalid, setConstraintInvalid] = useState(false);
45
+ useImperativeHandle(forwardedRef, () => inputRef.current);
46
+ const state = useResolvedControlProps({
47
+ id: id ?? `inkra-time-picker-${generatedId}`, disabled, readOnly, required, invalid,
48
+ 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid,
49
+ });
50
+ const currentValue = controlled ? String(value ?? '') : internalValue;
51
+ const secondsVisible = showSeconds || (step !== undefined && step !== 'any' && Number(step) % 60 !== 0);
52
+ const resolvedStep = step ?? (secondsVisible ? 1 : 60);
53
+ const stepBase = String(defaultValue ?? initialValue.current);
54
+ const resolvedLocale = locale ?? lang ?? (hourCycle === 'h12' ? 'en-US' : 'en-GB');
55
+ const zh = resolvedLocale.toLowerCase().startsWith('zh');
56
+ const copy = zh
57
+ ? { title: '选择时间', hour: '时', minute: '分', second: '秒', period: '时段', am: '上午', pm: '下午',
58
+ done: '确定', cancel: '取消', clear: '清空时间', invalid: '请选择符合时间范围和步进的时间。' }
59
+ : { title: 'Choose time', hour: 'Hour', minute: 'Minute', second: 'Second', period: 'Period', am: 'AM', pm: 'PM',
60
+ done: 'Apply', cancel: 'Cancel', clear: 'Clear time', invalid: 'Choose a time within the allowed range and step.' };
61
+ const currentParts = partsOf(currentValue);
62
+ const displayValue = currentParts
63
+ ? new Intl.DateTimeFormat(resolvedLocale, {
64
+ hour: '2-digit', minute: '2-digit', ...(secondsVisible ? { second: '2-digit' } : {}),
65
+ hourCycle: hourCycle === 'h12' ? 'h12' : 'h23',
66
+ }).format(new Date(2000, 0, 1, ...currentParts))
67
+ : placeholder ?? copy.title;
68
+ const canClear = clearable && Boolean(currentValue) && !state.disabled && !state.readOnly;
69
+ const valueId = field ? `${state.id}-value` : undefined;
70
+ const [hour, minute, second] = partsOf(draft) ?? [0, 0, 0];
71
+ const validDraft = (candidate = draft) => {
72
+ // Native validation owns overnight ranges, fractional steps and step bases.
73
+ const input = document.createElement('input');
74
+ input.type = 'time';
75
+ if (min !== undefined)
76
+ input.min = String(min);
77
+ if (max !== undefined)
78
+ input.max = String(max);
79
+ input.step = String(resolvedStep);
80
+ input.defaultValue = stepBase;
81
+ input.required = true;
82
+ input.value = candidate;
83
+ return input.validity.valid;
84
+ };
85
+ const updateOpen = (next) => {
86
+ if (next && (state.disabled || state.readOnly))
87
+ return;
88
+ if (next) {
89
+ const initial = partsOf(currentValue) ? currentValue : partsOf(String(min)) ? String(min) : '00:00';
90
+ const nextDraft = secondsVisible && initial.length === 5 ? `${initial}:00` : initial;
91
+ setDraft(nextDraft);
92
+ setDraftValid(validDraft(nextDraft));
93
+ }
94
+ setOpen(next);
95
+ };
96
+ const updatePart = (part, next) => {
97
+ const values = [hour, minute, second];
98
+ values[part] = next;
99
+ const nextDraft = values.slice(0, secondsVisible ? 3 : 2).map(pad).join(':');
100
+ setDraft(nextDraft);
101
+ setDraftValid(validDraft(nextDraft));
102
+ };
103
+ const commit = (next, reason) => {
104
+ if (!controlled)
105
+ setInternalValue(next);
106
+ setConstraintInvalid(false);
107
+ onValueChange?.(next, { reason });
108
+ setOpen(false);
109
+ };
110
+ useEffect(() => { setDraftValid(validDraft()); }, [draft, min, max, resolvedStep, stepBase]);
111
+ useBrowserLayoutEffect(() => {
112
+ // React's controlled value attribute must not move the native step base on each edit.
113
+ if (inputRef.current)
114
+ inputRef.current.defaultValue = stepBase;
115
+ }, [currentValue, stepBase]);
116
+ useEffect(() => {
117
+ const input = inputRef.current;
118
+ let timer;
119
+ const reset = (event) => {
120
+ clearTimeout(timer);
121
+ timer = setTimeout(() => {
122
+ if (event.defaultPrevented)
123
+ return;
124
+ if (!controlled)
125
+ setInternalValue(String(defaultValue ?? ''));
126
+ else if (input)
127
+ input.value = currentValue;
128
+ setConstraintInvalid(false);
129
+ setOpen(false);
130
+ }, 0);
131
+ };
132
+ input?.form?.addEventListener('reset', reset);
133
+ return () => { clearTimeout(timer); input?.form?.removeEventListener('reset', reset); };
134
+ }, [controlled, currentValue, defaultValue, form]);
135
+ useEffect(() => { if (state.disabled || state.readOnly)
136
+ setOpen(false); }, [state.disabled, state.readOnly]);
137
+ return _jsxs(Popover.Root, { open: open, onOpenChange: updateOpen, children: [_jsxs("div", { className: ['inkra-time-picker', classNames?.root, className].filter(Boolean).join(' '), style: style, dir: nativeProps.dir, "data-full-width": fullWidth, "data-invalid": state.invalid || constraintInvalid || undefined, "data-readonly": state.readOnly || undefined, "data-disabled": state.disabled || undefined, children: [startAction, _jsx(Popover.Trigger, { asChild: true, children: _jsxs(Button, { className: ['inkra-time-picker__trigger', classNames?.control].filter(Boolean).join(' '), id: state.id, ref: triggerRef, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": mergeAriaIds(state.describedBy, valueId), "aria-invalid": constraintInvalid || state.ariaInvalid, "aria-errormessage": state.errorMessage, "aria-required": state.required || undefined, "aria-readonly": state.readOnly || undefined, disabled: state.disabled, fullWidth: true, align: textAlign, variant: variant === 'soft' ? 'secondary' : variant === 'underline' ? 'outline' : variant, size: size, seed: seed, loading: loading, autoFocus: nativeProps.autoFocus, startIcon: startIcon ?? _jsx(PencilIcon, { paths: clockPaths, sourceSize: 24, size: 22 }), endIcon: endIcon, children: [prefix, _jsx(PencilText, { children: displayValue }), suffix] }) }), valueId && _jsx("span", { className: "inkra-time-picker__accessible-value", id: valueId, children: displayValue }), clearable && _jsx(Button, { className: "inkra-time-picker__clear", "aria-label": clearButtonLabel ?? copy.clear, title: clearButtonLabel ?? copy.clear, "aria-hidden": !canClear || undefined, disabled: !canClear, tabIndex: canClear ? 0 : -1, variant: "outline", size: "icon", onClick: event => {
138
+ commit('', 'clear');
139
+ onClear?.(event);
140
+ triggerRef.current?.focus({ preventScroll: true });
141
+ }, children: _jsx(PencilIcon, { paths: clearPaths, sourceSize: 24, size: 22 }) }), endAction, _jsx("input", { ...nativeProps, ref: inputRef, type: "time", className: "inkra-time-picker__native", id: `${state.id}-native`, "aria-hidden": "true", tabIndex: -1, name: name, form: form, value: currentValue, min: min, max: max, step: resolvedStep, lang: lang ?? resolvedLocale, "data-hour-cycle": hourCycle, disabled: state.disabled, readOnly: state.readOnly, required: state.required, onChange: event => {
142
+ if (!controlled)
143
+ setInternalValue(event.currentTarget.value);
144
+ onChange?.(event);
145
+ onValueChange?.(event.currentTarget.value, { reason: 'input' });
146
+ }, onFocus: event => { nativeProps.onFocus?.(event); triggerRef.current?.focus({ preventScroll: true }); }, onInvalid: event => {
147
+ event.preventDefault();
148
+ setConstraintInvalid(true);
149
+ triggerRef.current?.focus();
150
+ nativeProps.onInvalid?.(event);
151
+ } })] }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { className: "inkra-time-picker__popover", ref: contentRef, align: "start", sideOffset: 8, collisionPadding: 12, "aria-label": copy.title, onOpenAutoFocus: event => {
152
+ event.preventDefault();
153
+ requestAnimationFrame(() => {
154
+ const content = contentRef.current;
155
+ content?.querySelectorAll('input:checked').forEach(input => {
156
+ const scroll = input.closest('.inkra-radio-group__items');
157
+ const item = input.closest('.inkra-radio-group__item');
158
+ if (scroll && item)
159
+ scroll.scrollTop += item.getBoundingClientRect().top - scroll.getBoundingClientRect().top - 88;
160
+ });
161
+ (content?.querySelector('input:checked')
162
+ ?? content?.querySelector('input'))?.focus({ preventScroll: true });
163
+ });
164
+ }, children: [_jsx(Popover.Title, { children: copy.title }), _jsx(PencilLine, {}), _jsxs("div", { className: "inkra-time-picker__columns", children: [_jsx(TimeColumn, { name: `${generatedId}-hour`, label: copy.hour, value: hourCycle === 'h12' ? hour % 12 || 12 : hour, options: hourCycle === 'h12' ? times(12, 1) : times(24), onChange: next => updatePart(0, hourCycle === 'h12' ? next % 12 + (hour >= 12 ? 12 : 0) : next) }), _jsx(TimeColumn, { name: `${generatedId}-minute`, label: copy.minute, value: minute, options: times(60), onChange: next => updatePart(1, next) }), secondsVisible && _jsx(TimeColumn, { name: `${generatedId}-second`, label: copy.second, value: Math.floor(second), options: times(60), onChange: next => updatePart(2, next) }), hourCycle === 'h12' && _jsx(TimeColumn, { name: `${generatedId}-period`, label: copy.period, value: hour >= 12 ? 1 : 0, options: [0, 1], format: next => next ? copy.pm : copy.am, onChange: next => updatePart(0, hour % 12 + next * 12) })] }), !draftValid && _jsx(PencilText, { as: "p", className: "inkra-time-picker__error", role: "status", children: copy.invalid }), _jsx(PencilLine, {}), _jsxs("div", { className: "inkra-time-picker__footer", children: [_jsx(Popover.Close, { asChild: true, children: _jsx(Button, { variant: "outline", children: copy.cancel }) }), _jsx(Button, { disabled: !draftValid || state.disabled || state.readOnly, onClick: () => {
165
+ if (validDraft())
166
+ commit(draft, 'input');
167
+ }, children: copy.done })] })] }) })] });
168
+ });
@@ -0,0 +1,14 @@
1
+ import { type OlHTMLAttributes, type ReactNode } from 'react';
2
+ export interface TimelineItem {
3
+ id: string;
4
+ date: ReactNode;
5
+ dateTime?: string;
6
+ title: ReactNode;
7
+ description?: ReactNode;
8
+ content?: ReactNode;
9
+ }
10
+ export interface TimelineProps extends Omit<OlHTMLAttributes<HTMLOListElement>, 'children'> {
11
+ items: readonly TimelineItem[];
12
+ orientation?: 'horizontal' | 'vertical';
13
+ }
14
+ export declare const Timeline: import("react").ForwardRefExoticComponent<TimelineProps & import("react").RefAttributes<HTMLOListElement>>;