@inkra/react 0.0.0-stage → 0.2.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 (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +316 -2
  3. package/THIRD_PARTY_NOTICES.md +39 -0
  4. package/Yozai-OFL.txt +94 -0
  5. package/dist/Button.d.ts +36 -0
  6. package/dist/Button.js +89 -0
  7. package/dist/PencilIcon.d.ts +22 -0
  8. package/dist/PencilIcon.js +210 -0
  9. package/dist/PencilLine.d.ts +10 -0
  10. package/dist/PencilLine.js +91 -0
  11. package/dist/PencilSurface.d.ts +19 -0
  12. package/dist/PencilSurface.js +19 -0
  13. package/dist/PencilText.d.ts +16 -0
  14. package/dist/PencilText.js +338 -0
  15. package/dist/accordion/Accordion.d.ts +27 -0
  16. package/dist/accordion/Accordion.js +40 -0
  17. package/dist/accordion/index.d.ts +2 -0
  18. package/dist/accordion/index.js +1 -0
  19. package/dist/alert-dialog/index.d.ts +64 -0
  20. package/dist/alert-dialog/index.js +109 -0
  21. package/dist/assets/ui-hand-yozai.woff2 +0 -0
  22. package/dist/audio-player/index.d.ts +60 -0
  23. package/dist/audio-player/index.js +101 -0
  24. package/dist/breadcrumb/index.d.ts +16 -0
  25. package/dist/breadcrumb/index.js +25 -0
  26. package/dist/calendar/index.d.ts +21 -0
  27. package/dist/calendar/index.js +41 -0
  28. package/dist/carousel/index.d.ts +51 -0
  29. package/dist/carousel/index.js +290 -0
  30. package/dist/choice-grid/index.d.ts +38 -0
  31. package/dist/choice-grid/index.js +85 -0
  32. package/dist/color-picker/color.d.ts +15 -0
  33. package/dist/color-picker/color.js +55 -0
  34. package/dist/color-picker/index.d.ts +31 -0
  35. package/dist/color-picker/index.js +69 -0
  36. package/dist/command/index.d.ts +23 -0
  37. package/dist/command/index.js +39 -0
  38. package/dist/content/index.d.ts +106 -0
  39. package/dist/content/index.js +80 -0
  40. package/dist/context-menu/index.d.ts +76 -0
  41. package/dist/context-menu/index.js +240 -0
  42. package/dist/data-table/index.d.ts +47 -0
  43. package/dist/data-table/index.js +91 -0
  44. package/dist/date-picker/index.d.ts +50 -0
  45. package/dist/date-picker/index.js +180 -0
  46. package/dist/day-agenda/index.d.ts +43 -0
  47. package/dist/day-agenda/index.js +47 -0
  48. package/dist/day-agenda/layout.d.ts +20 -0
  49. package/dist/day-agenda/layout.js +60 -0
  50. package/dist/display/ProgressRing.d.ts +6 -0
  51. package/dist/display/ProgressRing.js +146 -0
  52. package/dist/display/index.d.ts +59 -0
  53. package/dist/display/index.js +75 -0
  54. package/dist/dockable-panel/index.d.ts +41 -0
  55. package/dist/dockable-panel/index.js +253 -0
  56. package/dist/dockable-panel/model.d.ts +21 -0
  57. package/dist/dockable-panel/model.js +61 -0
  58. package/dist/drawer/index.d.ts +63 -0
  59. package/dist/drawer/index.js +86 -0
  60. package/dist/dropdown-menu/index.d.ts +34 -0
  61. package/dist/dropdown-menu/index.js +40 -0
  62. package/dist/editable/index.d.ts +53 -0
  63. package/dist/editable/index.js +162 -0
  64. package/dist/empty/index.d.ts +21 -0
  65. package/dist/empty/index.js +23 -0
  66. package/dist/flip/index.d.ts +49 -0
  67. package/dist/flip/index.js +138 -0
  68. package/dist/form/EditablePencilText.d.ts +13 -0
  69. package/dist/form/EditablePencilText.js +323 -0
  70. package/dist/form/Field.d.ts +13 -0
  71. package/dist/form/Field.js +26 -0
  72. package/dist/form/Input.d.ts +27 -0
  73. package/dist/form/Input.js +97 -0
  74. package/dist/form/InputAction.d.ts +7 -0
  75. package/dist/form/InputAction.js +24 -0
  76. package/dist/form/Textarea.d.ts +17 -0
  77. package/dist/form/Textarea.js +102 -0
  78. package/dist/form/context.d.ts +12 -0
  79. package/dist/form/context.js +10 -0
  80. package/dist/form/controlProps.d.ts +23 -0
  81. package/dist/form/controlProps.js +20 -0
  82. package/dist/form/index.d.ts +7 -0
  83. package/dist/form/index.js +3 -0
  84. package/dist/form/types.d.ts +6 -0
  85. package/dist/form/types.js +1 -0
  86. package/dist/heatmap/index.d.ts +59 -0
  87. package/dist/heatmap/index.js +248 -0
  88. package/dist/hotkey/index.d.ts +37 -0
  89. package/dist/hotkey/index.js +130 -0
  90. package/dist/hotkey/model.d.ts +27 -0
  91. package/dist/hotkey/model.js +97 -0
  92. package/dist/hover-card/index.d.ts +33 -0
  93. package/dist/hover-card/index.js +141 -0
  94. package/dist/image-cropper/index.d.ts +82 -0
  95. package/dist/image-cropper/index.js +261 -0
  96. package/dist/image-cropper/model.d.ts +42 -0
  97. package/dist/image-cropper/model.js +94 -0
  98. package/dist/image-viewer/index.d.ts +79 -0
  99. package/dist/image-viewer/index.js +220 -0
  100. package/dist/image-viewer/model.d.ts +9 -0
  101. package/dist/image-viewer/model.js +18 -0
  102. package/dist/images/layers-2x.png +0 -0
  103. package/dist/images/layers.png +0 -0
  104. package/dist/images/marker-icon.png +0 -0
  105. package/dist/index.d.ts +75 -0
  106. package/dist/index.js +69 -0
  107. package/dist/input-number/index.d.ts +16 -0
  108. package/dist/input-number/index.js +77 -0
  109. package/dist/internal/InputActionContext.d.ts +1 -0
  110. package/dist/internal/InputActionContext.js +3 -0
  111. package/dist/internal/cache.d.ts +14 -0
  112. package/dist/internal/cache.js +54 -0
  113. package/dist/internal/line.d.ts +4 -0
  114. package/dist/internal/line.js +21 -0
  115. package/dist/internal/paper.d.ts +1 -0
  116. package/dist/internal/paper.js +65 -0
  117. package/dist/internal/random.d.ts +3 -0
  118. package/dist/internal/random.js +30 -0
  119. package/dist/internal/recovery.d.ts +1 -0
  120. package/dist/internal/recovery.js +52 -0
  121. package/dist/internal/renderTokens.d.ts +10 -0
  122. package/dist/internal/renderTokens.js +13 -0
  123. package/dist/internal/stroke.d.ts +24 -0
  124. package/dist/internal/stroke.js +144 -0
  125. package/dist/internal/surfaceRenderer.d.ts +41 -0
  126. package/dist/internal/surfaceRenderer.js +178 -0
  127. package/dist/internal/textQueue.d.ts +1 -0
  128. package/dist/internal/textQueue.js +37 -0
  129. package/dist/internal/textRenderer.d.ts +28 -0
  130. package/dist/internal/textRenderer.js +239 -0
  131. package/dist/internal/travelIcons.d.ts +24 -0
  132. package/dist/internal/travelIcons.js +14 -0
  133. package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
  134. package/dist/internal/useBrowserLayoutEffect.js +3 -0
  135. package/dist/internal/usePencilSurface.d.ts +3 -0
  136. package/dist/internal/usePencilSurface.js +116 -0
  137. package/dist/internal/useSelectionDrawing.d.ts +2 -0
  138. package/dist/internal/useSelectionDrawing.js +90 -0
  139. package/dist/internal/wash.d.ts +16 -0
  140. package/dist/internal/wash.js +149 -0
  141. package/dist/journey-map/MapScale.d.ts +4 -0
  142. package/dist/journey-map/MapScale.js +54 -0
  143. package/dist/journey-map/index.d.ts +39 -0
  144. package/dist/journey-map/index.js +197 -0
  145. package/dist/journey-map/terrain.d.ts +8 -0
  146. package/dist/journey-map/terrain.js +127 -0
  147. package/dist/keypad/index.d.ts +23 -0
  148. package/dist/keypad/index.js +63 -0
  149. package/dist/keypad/model.d.ts +20 -0
  150. package/dist/keypad/model.js +33 -0
  151. package/dist/layout/index.d.ts +63 -0
  152. package/dist/layout/index.js +62 -0
  153. package/dist/log/index.d.ts +45 -0
  154. package/dist/log/index.js +159 -0
  155. package/dist/log/model.d.ts +16 -0
  156. package/dist/log/model.js +13 -0
  157. package/dist/node-graph/index.d.ts +123 -0
  158. package/dist/node-graph/index.js +304 -0
  159. package/dist/number-ticker/index.d.ts +29 -0
  160. package/dist/number-ticker/index.js +132 -0
  161. package/dist/number-ticker/model.d.ts +8 -0
  162. package/dist/number-ticker/model.js +34 -0
  163. package/dist/overflow-list/index.d.ts +39 -0
  164. package/dist/overflow-list/index.js +230 -0
  165. package/dist/overflow-list/layout.d.ts +19 -0
  166. package/dist/overflow-list/layout.js +44 -0
  167. package/dist/overlay/Overlay.d.ts +65 -0
  168. package/dist/overlay/Overlay.js +77 -0
  169. package/dist/overlay/Popover.d.ts +43 -0
  170. package/dist/overlay/Popover.js +45 -0
  171. package/dist/overlay/index.d.ts +4 -0
  172. package/dist/overlay/index.js +2 -0
  173. package/dist/overlay/themeBridge.d.ts +8 -0
  174. package/dist/overlay/themeBridge.js +98 -0
  175. package/dist/pagination/index.d.ts +23 -0
  176. package/dist/pagination/index.js +41 -0
  177. package/dist/pagination/model.d.ts +4 -0
  178. package/dist/pagination/model.js +42 -0
  179. package/dist/pan-zoom/index.d.ts +71 -0
  180. package/dist/pan-zoom/index.js +175 -0
  181. package/dist/pencil.d.ts +10 -0
  182. package/dist/pencil.js +39 -0
  183. package/dist/radar/index.d.ts +54 -0
  184. package/dist/radar/index.js +220 -0
  185. package/dist/range-slider/index.d.ts +16 -0
  186. package/dist/range-slider/index.js +22 -0
  187. package/dist/rating/index.d.ts +28 -0
  188. package/dist/rating/index.js +127 -0
  189. package/dist/resizable/index.d.ts +32 -0
  190. package/dist/resizable/index.js +50 -0
  191. package/dist/scroll-area/index.d.ts +36 -0
  192. package/dist/scroll-area/index.js +44 -0
  193. package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
  194. package/dist/segmented-progress/SegmentedProgress.js +42 -0
  195. package/dist/select/MultipleSelect.d.ts +44 -0
  196. package/dist/select/MultipleSelect.js +124 -0
  197. package/dist/select/SearchableSelect.d.ts +45 -0
  198. package/dist/select/SearchableSelect.js +50 -0
  199. package/dist/select/Select.d.ts +109 -0
  200. package/dist/select/Select.js +205 -0
  201. package/dist/select/index.d.ts +4 -0
  202. package/dist/select/index.js +2 -0
  203. package/dist/selection/Checkbox.d.ts +9 -0
  204. package/dist/selection/Checkbox.js +37 -0
  205. package/dist/selection/RadioGroup.d.ts +23 -0
  206. package/dist/selection/RadioGroup.js +82 -0
  207. package/dist/selection/Switch.d.ts +8 -0
  208. package/dist/selection/Switch.js +36 -0
  209. package/dist/selection/index.d.ts +6 -0
  210. package/dist/selection/index.js +3 -0
  211. package/dist/selection/useNativeChecked.d.ts +13 -0
  212. package/dist/selection/useNativeChecked.js +27 -0
  213. package/dist/slider/index.d.ts +11 -0
  214. package/dist/slider/index.js +45 -0
  215. package/dist/slot/index.d.ts +24 -0
  216. package/dist/slot/index.js +29 -0
  217. package/dist/sortable/index.d.ts +50 -0
  218. package/dist/sortable/index.js +278 -0
  219. package/dist/sortable/model.d.ts +30 -0
  220. package/dist/sortable/model.js +64 -0
  221. package/dist/sparkline/index.d.ts +19 -0
  222. package/dist/sparkline/index.js +240 -0
  223. package/dist/sparkline/model.d.ts +16 -0
  224. package/dist/sparkline/model.js +28 -0
  225. package/dist/steps/index.d.ts +16 -0
  226. package/dist/steps/index.js +37 -0
  227. package/dist/styles.css +8226 -0
  228. package/dist/tabs/index.d.ts +22 -0
  229. package/dist/tabs/index.js +42 -0
  230. package/dist/tag-input/index.d.ts +69 -0
  231. package/dist/tag-input/index.js +325 -0
  232. package/dist/tag-input/model.d.ts +14 -0
  233. package/dist/tag-input/model.js +38 -0
  234. package/dist/time-picker/index.d.ts +15 -0
  235. package/dist/time-picker/index.js +168 -0
  236. package/dist/timeline/index.d.ts +14 -0
  237. package/dist/timeline/index.js +16 -0
  238. package/dist/toast/index.d.ts +27 -0
  239. package/dist/toast/index.js +36 -0
  240. package/dist/toggle-group/index.d.ts +17 -0
  241. package/dist/toggle-group/index.js +26 -0
  242. package/dist/tokens.css +356 -0
  243. package/dist/tooltip/Tooltip.d.ts +24 -0
  244. package/dist/tooltip/Tooltip.js +32 -0
  245. package/dist/tooltip/index.d.ts +2 -0
  246. package/dist/tooltip/index.js +1 -0
  247. package/dist/transfer/index.d.ts +64 -0
  248. package/dist/transfer/index.js +174 -0
  249. package/dist/transfer/model.d.ts +12 -0
  250. package/dist/transfer/model.js +33 -0
  251. package/dist/tree/index.d.ts +39 -0
  252. package/dist/tree/index.js +36 -0
  253. package/dist/tree-select/index.d.ts +59 -0
  254. package/dist/tree-select/index.js +142 -0
  255. package/dist/tree-select/model.d.ts +31 -0
  256. package/dist/tree-select/model.js +135 -0
  257. package/dist/typewriter/index.d.ts +21 -0
  258. package/dist/typewriter/index.js +101 -0
  259. package/dist/typewriter/model.d.ts +7 -0
  260. package/dist/typewriter/model.js +45 -0
  261. package/dist/upload/index.d.ts +59 -0
  262. package/dist/upload/index.js +230 -0
  263. package/dist/virtual-list/index.d.ts +44 -0
  264. package/dist/virtual-list/index.js +128 -0
  265. package/dist/virtual-list/model.d.ts +7 -0
  266. package/dist/virtual-list/model.js +27 -0
  267. package/package.json +119 -4
@@ -0,0 +1,22 @@
1
+ import * as Primitive from '@radix-ui/react-tabs';
2
+ import { type ReactNode } from 'react';
3
+ export interface TabsProps extends Primitive.TabsProps {
4
+ size?: 'sm' | 'md' | 'lg';
5
+ animated?: boolean;
6
+ }
7
+ export interface TabsListProps extends Primitive.TabsListProps {
8
+ align?: 'start' | 'center' | 'end';
9
+ fullWidth?: boolean;
10
+ divided?: boolean;
11
+ }
12
+ export interface TabsTriggerProps extends Omit<Primitive.TabsTriggerProps, 'asChild'> {
13
+ startIcon?: ReactNode;
14
+ endIcon?: ReactNode;
15
+ }
16
+ export type TabsContentProps = Primitive.TabsContentProps;
17
+ export declare const Tabs: {
18
+ Root: import("react").ForwardRefExoticComponent<TabsProps & import("react").RefAttributes<HTMLDivElement>>;
19
+ List: import("react").ForwardRefExoticComponent<TabsListProps & import("react").RefAttributes<HTMLDivElement>>;
20
+ Trigger: import("react").ForwardRefExoticComponent<TabsTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
21
+ Content: import("react").ForwardRefExoticComponent<Primitive.TabsContentProps & import("react").RefAttributes<HTMLDivElement>>;
22
+ };
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as Primitive from '@radix-ui/react-tabs';
3
+ import { createContext, forwardRef, useContext, useRef, useState } from 'react';
4
+ import { PencilSurface } from '../PencilSurface.js';
5
+ import { PencilText } from '../PencilText.js';
6
+ import { PencilLine } from '../PencilLine.js';
7
+ import { useSelectionDrawing } from '../internal/useSelectionDrawing.js';
8
+ const ValueContext = createContext(undefined);
9
+ const AnimatedContext = createContext(true);
10
+ const OrientationContext = createContext('horizontal');
11
+ const DividedContext = createContext(false);
12
+ const Root = forwardRef(function TabsRoot({ value, defaultValue, onValueChange, className, size = 'md', animated = true, orientation = 'horizontal', ...props }, ref) {
13
+ const [internal, setInternal] = useState(defaultValue);
14
+ const current = value === undefined ? internal : value;
15
+ const lastChange = useRef(current);
16
+ lastChange.current = current;
17
+ return _jsx(ValueContext.Provider, { value: current, children: _jsx(AnimatedContext.Provider, { value: animated, children: _jsx(OrientationContext.Provider, { value: orientation, children: _jsx(Primitive.Root, { ...props, orientation: orientation, ref: ref, "data-size": size, className: ['inkra-tabs', className].filter(Boolean).join(' '), value: current ?? '', onValueChange: next => {
18
+ // Pointer focus and pointer down can request the same tab before React renders.
19
+ if (lastChange.current === next)
20
+ return;
21
+ lastChange.current = next;
22
+ if (value === undefined)
23
+ setInternal(next);
24
+ onValueChange?.(next);
25
+ } }) }) }) });
26
+ });
27
+ const List = forwardRef(function TabsList({ className, align = 'start', fullWidth = false, divided = false, ...props }, ref) {
28
+ return _jsx(DividedContext.Provider, { value: divided, children: _jsx(Primitive.List, { ...props, ref: ref, "data-align": align, "data-full-width": fullWidth || undefined, "data-divided": divided || undefined, className: ['inkra-tabs__list', className].filter(Boolean).join(' ') }) });
29
+ });
30
+ const Trigger = forwardRef(function TabsTrigger({ value, className, children, startIcon, endIcon, ...props }, ref) {
31
+ const current = useContext(ValueContext);
32
+ const divided = useContext(DividedContext);
33
+ const orientation = useContext(OrientationContext);
34
+ const surface = useRef(null);
35
+ useSelectionDrawing(surface, current === value, useContext(AnimatedContext));
36
+ return _jsxs(Primitive.Trigger, { ...props, value: value, ref: ref, className: ['inkra-tabs__trigger', className].filter(Boolean).join(' '), children: [divided && _jsx(PencilLine, { className: "inkra-tabs__divider", orientation: orientation === 'horizontal' ? 'vertical' : 'horizontal', seed: `tab-divider-${value}` }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-tabs__selection", variant: "selection", seed: `tab-${value}`, ref: surface }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-tabs__focus-ring", variant: "outline", seed: `tab-${value}-focus` }), startIcon && _jsx("span", { className: "inkra-tabs__icon", "aria-hidden": "true", children: startIcon }), _jsx("span", { className: "inkra-tabs__label", children: typeof children === 'string' ? _jsx(PencilText, { children: children }) : children }), endIcon && _jsx("span", { className: "inkra-tabs__icon", "aria-hidden": "true", children: endIcon })] });
37
+ });
38
+ const Content = forwardRef(function TabsContent({ className, value, ...props }, ref) {
39
+ const current = useContext(ValueContext);
40
+ return _jsx(Primitive.Content, { ...props, value: value, ref: ref, hidden: current !== value, className: ['inkra-tabs__content', className].filter(Boolean).join(' ') });
41
+ });
42
+ export const Tabs = { Root, List, Trigger, Content };
@@ -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>>;