@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,12 @@
1
+ export interface FieldContextValue {
2
+ controlId: string;
3
+ descriptionId?: string;
4
+ errorId?: string;
5
+ required: boolean;
6
+ invalid: boolean;
7
+ disabled: boolean;
8
+ readOnly: boolean;
9
+ }
10
+ export declare const FieldContext: import("react").Context<FieldContextValue | null>;
11
+ export declare function useFieldContext(): FieldContextValue | null;
12
+ export declare function mergeAriaIds(...values: Array<string | undefined>): string | undefined;
@@ -0,0 +1,10 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const FieldContext = createContext(null);
3
+ export function useFieldContext() {
4
+ return useContext(FieldContext);
5
+ }
6
+ export function mergeAriaIds(...values) {
7
+ const ids = values.flatMap((value) => value?.split(/\s+/).filter(Boolean) ?? []);
8
+ const merged = Array.from(new Set(ids));
9
+ return merged.length ? merged.join(' ') : undefined;
10
+ }
@@ -0,0 +1,23 @@
1
+ import type { AriaAttributes } from 'react';
2
+ interface NativeControlState {
3
+ id?: string;
4
+ required?: boolean;
5
+ disabled?: boolean;
6
+ readOnly?: boolean;
7
+ 'aria-describedby'?: string;
8
+ 'aria-errormessage'?: string;
9
+ 'aria-invalid'?: AriaAttributes['aria-invalid'];
10
+ }
11
+ export declare function useResolvedControlProps(props: NativeControlState & {
12
+ invalid?: boolean;
13
+ }): {
14
+ id: string | undefined;
15
+ required: boolean;
16
+ disabled: boolean;
17
+ readOnly: boolean;
18
+ invalid: boolean;
19
+ describedBy: string | undefined;
20
+ errorMessage: string | undefined;
21
+ ariaInvalid: boolean | "true" | "false" | "grammar" | "spelling" | undefined;
22
+ };
23
+ export {};
@@ -0,0 +1,20 @@
1
+ import { mergeAriaIds, useFieldContext } from './context.js';
2
+ export function useResolvedControlProps(props) {
3
+ const field = useFieldContext();
4
+ const resolvedInvalid = Boolean(props.invalid
5
+ || field?.invalid
6
+ || props['aria-invalid'] === true
7
+ || props['aria-invalid'] === 'true');
8
+ const describedBy = mergeAriaIds(props['aria-describedby'], field?.descriptionId, field?.errorId);
9
+ const errorMessage = mergeAriaIds(props['aria-errormessage'], resolvedInvalid ? field?.errorId : undefined);
10
+ return {
11
+ id: field?.controlId ?? props.id,
12
+ required: Boolean(props.required || field?.required),
13
+ disabled: Boolean(props.disabled || field?.disabled),
14
+ readOnly: Boolean(props.readOnly || field?.readOnly),
15
+ invalid: resolvedInvalid,
16
+ describedBy,
17
+ errorMessage,
18
+ ariaInvalid: resolvedInvalid ? true : props['aria-invalid'],
19
+ };
20
+ }
@@ -0,0 +1,7 @@
1
+ export { Field } from './Field.js';
2
+ export type { FieldProps } from './Field.js';
3
+ export { Input } from './Input.js';
4
+ export type { InputProps, InputVariant, InputTextAlign, InputValueChangeDetails, InputClassNames } from './Input.js';
5
+ export { Textarea } from './Textarea.js';
6
+ export type { TextareaProps, TextareaCountDetails } from './Textarea.js';
7
+ export type { FieldControlSize, FieldControlStateProps } from './types.js';
@@ -0,0 +1,3 @@
1
+ export { Field } from './Field.js';
2
+ export { Input } from './Input.js';
3
+ export { Textarea } from './Textarea.js';
@@ -0,0 +1,6 @@
1
+ export type FieldControlSize = 'sm' | 'md' | 'lg';
2
+ export interface FieldControlStateProps {
3
+ invalid?: boolean;
4
+ size?: FieldControlSize;
5
+ seed?: string | number;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ export type HeatmapKey = string | number;
3
+ export type HeatmapScaleType = 'continuous' | 'quantize';
4
+ export interface HeatmapAxisItem {
5
+ id: HeatmapKey;
6
+ label: ReactNode;
7
+ ariaLabel?: string;
8
+ }
9
+ export interface HeatmapCell {
10
+ rowId: HeatmapKey;
11
+ columnId: HeatmapKey;
12
+ value?: number | null;
13
+ disabled?: boolean;
14
+ data?: unknown;
15
+ }
16
+ export interface HeatmapSelection {
17
+ rowId: HeatmapKey;
18
+ columnId: HeatmapKey;
19
+ }
20
+ export interface HeatmapCellContext {
21
+ row: HeatmapAxisItem;
22
+ column: HeatmapAxisItem;
23
+ color: string | null;
24
+ missing: boolean;
25
+ selected: boolean;
26
+ active: boolean;
27
+ }
28
+ export interface HeatmapDetailsContext extends HeatmapCellContext {
29
+ valueLabel: string;
30
+ }
31
+ export interface HeatmapProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
32
+ rows: readonly HeatmapAxisItem[];
33
+ columns: readonly HeatmapAxisItem[];
34
+ cells: readonly HeatmapCell[];
35
+ label: ReactNode;
36
+ description?: ReactNode;
37
+ scaleType?: HeatmapScaleType;
38
+ domain?: readonly [number, number];
39
+ colorRange?: readonly string[];
40
+ binCount?: number;
41
+ showLegend?: boolean;
42
+ showValues?: boolean;
43
+ legendLabel?: ReactNode;
44
+ missingLabel?: ReactNode;
45
+ missingValueLabel?: string;
46
+ selectionMode?: 'single' | 'none';
47
+ selectedCell?: HeatmapSelection | null;
48
+ defaultSelectedCell?: HeatmapSelection | null;
49
+ onSelectedCellChange?: (selection: HeatmapSelection | null, cell: HeatmapCell | null) => void;
50
+ getValueLabel?: (value: number) => string;
51
+ getCellAriaLabel?: (cell: HeatmapCell, context: HeatmapDetailsContext) => string;
52
+ renderCell?: (cell: HeatmapCell, context: HeatmapCellContext) => ReactNode;
53
+ renderDetails?: (cell: HeatmapCell, context: HeatmapDetailsContext) => ReactNode;
54
+ cellSize?: number | string;
55
+ rowLabelWidth?: number | string;
56
+ rowHeaderLabel?: string;
57
+ emptyMessage?: ReactNode;
58
+ }
59
+ export declare const Heatmap: import("react").ForwardRefExoticComponent<HeatmapProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,248 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { scaleLinear, scaleQuantize } from 'd3-scale';
3
+ import { forwardRef, useEffect, useId, useMemo, useRef, useState, } from 'react';
4
+ import { PencilSurface } from '../PencilSurface.js';
5
+ import { PencilText } from '../PencilText.js';
6
+ const defaultColors = ['#f0dda0', '#b6d09b', '#70b19c', '#43869a', '#365f82'];
7
+ function classes(...values) {
8
+ return values.filter(Boolean).join(' ');
9
+ }
10
+ function keyPart(value) {
11
+ return JSON.stringify([typeof value, value]);
12
+ }
13
+ function cellKey(rowId, columnId) {
14
+ return JSON.stringify([typeof rowId, rowId, typeof columnId, columnId]);
15
+ }
16
+ function sameSelection(a, b) {
17
+ return a != null && b != null && a.rowId === b.rowId && a.columnId === b.columnId;
18
+ }
19
+ function axisLabel(item) {
20
+ if (item.ariaLabel)
21
+ return item.ariaLabel;
22
+ if (typeof item.label === 'string' || typeof item.label === 'number')
23
+ return String(item.label);
24
+ return String(item.id);
25
+ }
26
+ function isMissing(cell) {
27
+ return typeof cell.value !== 'number' || !Number.isFinite(cell.value);
28
+ }
29
+ function normalizeCssSize(value, fallback) {
30
+ return typeof value === 'number'
31
+ ? Number.isFinite(value) ? `${Math.max(1, value)}px` : fallback
32
+ : value || fallback;
33
+ }
34
+ function normalizeDomain(values, requested) {
35
+ let minimum;
36
+ let maximum;
37
+ if (requested && Number.isFinite(requested[0]) && Number.isFinite(requested[1])) {
38
+ minimum = Math.min(requested[0], requested[1]);
39
+ maximum = Math.max(requested[0], requested[1]);
40
+ }
41
+ else if (values.length > 0) {
42
+ minimum = values[0];
43
+ maximum = values[0];
44
+ for (let index = 1; index < values.length; index += 1) {
45
+ minimum = Math.min(minimum, values[index]);
46
+ maximum = Math.max(maximum, values[index]);
47
+ }
48
+ }
49
+ else {
50
+ return [0, 1];
51
+ }
52
+ if (minimum !== maximum)
53
+ return [minimum, maximum];
54
+ const padding = Math.abs(minimum) * 0.5 || 1;
55
+ return [minimum - padding, maximum + padding];
56
+ }
57
+ function paletteDomain(domain, count) {
58
+ const [minimum, maximum] = domain;
59
+ if (count <= 1)
60
+ return [minimum];
61
+ return Array.from({ length: count }, (_, index) => minimum + ((maximum - minimum) * index) / (count - 1));
62
+ }
63
+ function clampBinCount(value) {
64
+ if (value === undefined || !Number.isFinite(value))
65
+ return 5;
66
+ return Math.max(2, Math.min(9, Math.round(value ?? 5)));
67
+ }
68
+ function resolvePosition(selection, rows, columns) {
69
+ if (!selection)
70
+ return null;
71
+ const rowIndex = rows.findIndex(row => row.id === selection.rowId);
72
+ const columnIndex = columns.findIndex(column => column.id === selection.columnId);
73
+ return rowIndex < 0 || columnIndex < 0 ? null : { rowIndex, columnIndex };
74
+ }
75
+ function positionEquals(a, b) {
76
+ return a?.rowIndex === b?.rowIndex && a?.columnIndex === b?.columnIndex;
77
+ }
78
+ function defaultValueLabel(value) {
79
+ return String(value);
80
+ }
81
+ function paintStyle(color) {
82
+ return {
83
+ '--inkra-accent': color,
84
+ '--inkra-accent-soft': color,
85
+ '--inkra-accent-strong': color,
86
+ };
87
+ }
88
+ export const Heatmap = forwardRef(function Heatmap({ rows, columns, cells, label, description, scaleType = 'continuous', domain: requestedDomain, colorRange = defaultColors, binCount, showLegend = true, showValues = true, legendLabel = 'Intensity', missingLabel = '—', missingValueLabel = 'Missing', selectionMode = 'single', selectedCell, defaultSelectedCell = null, onSelectedCellChange, getValueLabel = defaultValueLabel, getCellAriaLabel, renderCell, renderDetails, cellSize = 56, rowLabelWidth = '8rem', rowHeaderLabel = 'Rows', emptyMessage = 'No heatmap data', className, style, ...props }, forwardedRef) {
89
+ const generatedId = useId();
90
+ const labelId = `inkra-heatmap-${generatedId}-label`;
91
+ const descriptionId = description == null ? undefined : `inkra-heatmap-${generatedId}-description`;
92
+ const detailsId = `inkra-heatmap-${generatedId}-details`;
93
+ const isControlled = selectedCell !== undefined;
94
+ const [internalSelection, setInternalSelection] = useState(defaultSelectedCell);
95
+ const resolvedSelection = isControlled ? selectedCell ?? null : internalSelection;
96
+ const initialPosition = resolvePosition(resolvedSelection, rows, columns) ?? (rows.length > 0 && columns.length > 0 ? { rowIndex: 0, columnIndex: 0 } : null);
97
+ const [activePosition, setActivePosition] = useState(initialPosition);
98
+ const [hoveredPosition, setHoveredPosition] = useState(null);
99
+ const [focusedPosition, setFocusedPosition] = useState(null);
100
+ const cellRefs = useRef(new Map());
101
+ const rowSignature = JSON.stringify(rows.map(row => [typeof row.id, row.id]));
102
+ const columnSignature = JSON.stringify(columns.map(column => [typeof column.id, column.id]));
103
+ const cellsByKey = useMemo(() => {
104
+ const map = new Map();
105
+ for (const cell of cells)
106
+ map.set(cellKey(cell.rowId, cell.columnId), cell);
107
+ return map;
108
+ }, [cells]);
109
+ const matrix = useMemo(() => rows.map(row => columns.map(column => (cellsByKey.get(cellKey(row.id, column.id)) ?? { rowId: row.id, columnId: column.id, value: null }))), [cellsByKey, columns, rows]);
110
+ const values = useMemo(() => matrix.flat().filter(cell => !isMissing(cell)).map(cell => cell.value), [matrix]);
111
+ const domain = useMemo(() => normalizeDomain(values, requestedDomain), [requestedDomain, values]);
112
+ const colors = colorRange.length >= 2 ? colorRange : defaultColors;
113
+ const continuousScale = useMemo(() => scaleLinear()
114
+ .domain(paletteDomain(domain, colors.length))
115
+ .range([...colors])
116
+ .clamp(true), [colors, domain]);
117
+ const resolvedBinCount = clampBinCount(binCount);
118
+ const quantizedColors = useMemo(() => Array.from({ length: resolvedBinCount }, (_, index) => continuousScale(domain[0] + ((domain[1] - domain[0]) * index) / Math.max(1, resolvedBinCount - 1))), [continuousScale, domain, resolvedBinCount]);
119
+ const quantizeScale = useMemo(() => scaleQuantize()
120
+ .domain(domain)
121
+ .range(quantizedColors), [domain, quantizedColors]);
122
+ const colorForValue = (value) => scaleType === 'quantize' ? quantizeScale(value) : continuousScale(value);
123
+ const selectedPosition = resolvePosition(resolvedSelection, rows, columns);
124
+ const inspectedPosition = focusedPosition ?? hoveredPosition ?? selectedPosition ?? activePosition;
125
+ const inspectedCell = inspectedPosition ? matrix[inspectedPosition.rowIndex]?.[inspectedPosition.columnIndex] : undefined;
126
+ useEffect(() => {
127
+ const next = resolvePosition(resolvedSelection, rows, columns);
128
+ if (next)
129
+ setActivePosition(current => positionEquals(current, next) ? current : next);
130
+ }, [columnSignature, resolvedSelection?.columnId, resolvedSelection?.rowId, rowSignature]);
131
+ useEffect(() => {
132
+ if (rows.length === 0 || columns.length === 0) {
133
+ setActivePosition(null);
134
+ return;
135
+ }
136
+ setActivePosition(current => current && current.rowIndex < rows.length && current.columnIndex < columns.length
137
+ ? current
138
+ : { rowIndex: 0, columnIndex: 0 });
139
+ }, [columns.length, rows.length]);
140
+ const focusPosition = (position) => {
141
+ setActivePosition(position);
142
+ const cell = matrix[position.rowIndex]?.[position.columnIndex];
143
+ if (!cell)
144
+ return;
145
+ cellRefs.current.get(cellKey(cell.rowId, cell.columnId))?.focus();
146
+ };
147
+ const select = (cell) => {
148
+ if (selectionMode === 'none' || cell.disabled)
149
+ return;
150
+ const next = { rowId: cell.rowId, columnId: cell.columnId };
151
+ if (!isControlled)
152
+ setInternalSelection(next);
153
+ onSelectedCellChange?.(next, cell);
154
+ };
155
+ const onCellKeyDown = (event, position, cell) => {
156
+ let next = position;
157
+ if (event.key === 'ArrowLeft')
158
+ next = { ...position, columnIndex: Math.max(0, position.columnIndex - 1) };
159
+ else if (event.key === 'ArrowRight')
160
+ next = { ...position, columnIndex: Math.min(columns.length - 1, position.columnIndex + 1) };
161
+ else if (event.key === 'ArrowUp')
162
+ next = { ...position, rowIndex: Math.max(0, position.rowIndex - 1) };
163
+ else if (event.key === 'ArrowDown')
164
+ next = { ...position, rowIndex: Math.min(rows.length - 1, position.rowIndex + 1) };
165
+ else if (event.key === 'Home')
166
+ next = event.ctrlKey ? { rowIndex: 0, columnIndex: 0 } : { ...position, columnIndex: 0 };
167
+ else if (event.key === 'End')
168
+ next = event.ctrlKey
169
+ ? { rowIndex: rows.length - 1, columnIndex: columns.length - 1 }
170
+ : { ...position, columnIndex: columns.length - 1 };
171
+ else if (event.key === 'Enter' || event.key === ' ') {
172
+ event.preventDefault();
173
+ select(cell);
174
+ return;
175
+ }
176
+ else
177
+ return;
178
+ event.preventDefault();
179
+ focusPosition(next);
180
+ };
181
+ const rootStyle = {
182
+ ...style,
183
+ '--inkra-heatmap-cell-size': normalizeCssSize(cellSize, '56px'),
184
+ '--inkra-heatmap-row-label-width': normalizeCssSize(rowLabelWidth, '8rem'),
185
+ '--inkra-heatmap-column-count': columns.length,
186
+ };
187
+ const renderCellContent = (cell, context) => {
188
+ if (renderCell)
189
+ return renderCell(cell, context);
190
+ if (context.missing)
191
+ return missingLabel;
192
+ return showValues ? getValueLabel(cell.value) : _jsx("span", { "aria-hidden": "true", className: "inkra-heatmap__value-mark" });
193
+ };
194
+ const detailsFor = (cell, position) => {
195
+ const row = rows[position.rowIndex];
196
+ const column = columns[position.columnIndex];
197
+ const missing = isMissing(cell);
198
+ const valueLabel = missing ? missingValueLabel : getValueLabel(cell.value);
199
+ const context = {
200
+ row,
201
+ column,
202
+ color: missing ? null : colorForValue(cell.value),
203
+ missing,
204
+ selected: sameSelection(resolvedSelection, cell),
205
+ active: positionEquals(inspectedPosition, position),
206
+ valueLabel,
207
+ };
208
+ return { context, valueLabel };
209
+ };
210
+ return (_jsxs("div", { ...props, className: classes('inkra-heatmap', className), ref: forwardedRef, style: rootStyle, children: [_jsxs("div", { className: "inkra-heatmap__heading", children: [_jsx("div", { className: "inkra-heatmap__label", id: labelId, children: typeof label === 'string' ? _jsx(PencilText, { seed: `${generatedId}-label`, children: label }) : label }), description == null ? null : _jsx("div", { className: "inkra-heatmap__description", id: descriptionId, children: description })] }), rows.length === 0 || columns.length === 0 ? (_jsx("div", { className: "inkra-heatmap__empty", children: emptyMessage })) : (_jsx("div", { className: "inkra-heatmap__viewport", children: _jsxs("div", { "aria-describedby": classes(descriptionId, detailsId), "aria-labelledby": labelId, "aria-rowcount": rows.length + 1, "aria-colcount": columns.length + 1, className: "inkra-heatmap__grid", role: "grid", children: [_jsxs("div", { "aria-rowindex": 1, className: "inkra-heatmap__row inkra-heatmap__row--header", role: "row", children: [_jsx("span", { "aria-colindex": 1, "aria-label": rowHeaderLabel, className: "inkra-heatmap__corner", role: "columnheader" }), columns.map((column, columnIndex) => (_jsx("span", { "aria-colindex": columnIndex + 2, className: "inkra-heatmap__column-label", role: "columnheader", children: column.label }, keyPart(column.id))))] }), matrix.map((rowCells, rowIndex) => {
211
+ const row = rows[rowIndex];
212
+ return (_jsxs("div", { "aria-rowindex": rowIndex + 2, className: "inkra-heatmap__row", role: "row", children: [_jsx("span", { "aria-colindex": 1, className: "inkra-heatmap__row-label", role: "rowheader", children: row.label }), rowCells.map((cell, columnIndex) => {
213
+ const position = { rowIndex, columnIndex };
214
+ const column = columns[columnIndex];
215
+ const missing = isMissing(cell);
216
+ const selected = sameSelection(resolvedSelection, cell);
217
+ const active = positionEquals(inspectedPosition, position);
218
+ const color = missing ? null : colorForValue(cell.value);
219
+ const valueLabel = missing ? missingValueLabel : getValueLabel(cell.value);
220
+ const context = { row, column, color, missing, selected, active, valueLabel };
221
+ const ariaLabel = getCellAriaLabel?.(cell, context)
222
+ ?? `${axisLabel(row)}, ${axisLabel(column)}: ${valueLabel}`;
223
+ const resolvedPaintStyle = color ? paintStyle(color) : undefined;
224
+ return (_jsxs("button", { "aria-colindex": columnIndex + 2, "aria-disabled": cell.disabled || undefined, "aria-label": ariaLabel, "aria-selected": selectionMode === 'single' ? selected : undefined, className: "inkra-heatmap__cell", "data-active": active || undefined, "data-disabled": cell.disabled || undefined, "data-missing": missing || undefined, "data-selected": selected || undefined, "data-value": missing ? undefined : cell.value, onBlur: () => setFocusedPosition(current => positionEquals(current, position) ? null : current), onClick: () => select(cell), onFocus: () => {
225
+ setActivePosition(position);
226
+ setFocusedPosition(position);
227
+ }, onKeyDown: event => onCellKeyDown(event, position, cell), onMouseEnter: () => setHoveredPosition(position), onMouseLeave: () => setHoveredPosition(current => positionEquals(current, position) ? null : current), ref: node => {
228
+ const key = cellKey(cell.rowId, cell.columnId);
229
+ if (node)
230
+ cellRefs.current.set(key, node);
231
+ else
232
+ cellRefs.current.delete(key);
233
+ }, role: "gridcell", tabIndex: positionEquals(activePosition, position) ? 0 : -1, type: "button", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-heatmap__cell-paint", radius: 6, seed: `heatmap-${cellKey(cell.rowId, cell.columnId)}-paint`, style: resolvedPaintStyle, variant: missing ? 'outline' : 'action' }), selected ? _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-heatmap__cell-selection", radius: 6, seed: `heatmap-${cellKey(cell.rowId, cell.columnId)}-selection`, variant: "selection" }) : null, _jsx("span", { className: "inkra-heatmap__cell-content", children: renderCellContent(cell, context) })] }, cellKey(cell.rowId, cell.columnId)));
234
+ })] }, keyPart(row.id)));
235
+ })] }) })), showLegend && rows.length > 0 && columns.length > 0 ? (_jsxs("div", { "aria-label": typeof legendLabel === 'string' ? legendLabel : undefined, className: "inkra-heatmap__legend", role: "group", children: [_jsx("div", { className: "inkra-heatmap__legend-label", children: legendLabel }), scaleType === 'continuous' ? (_jsxs("div", { className: "inkra-heatmap__legend-continuous", children: [_jsx("div", { "aria-hidden": "true", className: "inkra-heatmap__legend-strip", children: Array.from({ length: 7 }, (_, index) => {
236
+ const value = domain[0] + ((domain[1] - domain[0]) * index) / 6;
237
+ const color = continuousScale(value);
238
+ return _jsx(PencilSurface, { as: "span", className: "inkra-heatmap__legend-swatch", seed: `heatmap-legend-${index}`, style: paintStyle(color), variant: "action" }, index);
239
+ }) }), _jsxs("div", { className: "inkra-heatmap__legend-values", children: [_jsx("span", { children: getValueLabel(domain[0]) }), _jsx("span", { children: getValueLabel((domain[0] + domain[1]) / 2) }), _jsx("span", { children: getValueLabel(domain[1]) })] })] })) : (_jsx("div", { className: "inkra-heatmap__legend-bins", role: "list", children: quantizedColors.map((color, index) => {
240
+ const [from, to] = quantizeScale.invertExtent(color);
241
+ return _jsxs("div", { className: "inkra-heatmap__legend-bin", role: "listitem", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-heatmap__legend-swatch", seed: `heatmap-bin-${index}`, style: paintStyle(color), variant: "action" }), _jsxs("span", { children: [getValueLabel(from), "\u2013", getValueLabel(to)] })] }, `${color}-${index}`);
242
+ }) })), _jsxs("div", { className: "inkra-heatmap__legend-missing", children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-heatmap__legend-swatch", seed: "heatmap-missing", variant: "outline" }), _jsx("span", { children: missingValueLabel })] })] })) : null, _jsx("div", { "aria-live": "polite", className: "inkra-heatmap__details", id: detailsId, children: inspectedCell && inspectedPosition ? (() => {
243
+ const { context, valueLabel } = detailsFor(inspectedCell, inspectedPosition);
244
+ return renderDetails
245
+ ? renderDetails(inspectedCell, context)
246
+ : _jsxs(_Fragment, { children: [_jsxs("strong", { children: [axisLabel(context.row), " \u00B7 ", axisLabel(context.column)] }), _jsx("span", { children: valueLabel })] });
247
+ })() : null })] }));
248
+ });
@@ -0,0 +1,37 @@
1
+ import type { HTMLAttributes, InputHTMLAttributes, ReactNode } from 'react';
2
+ import type { FieldControlSize, FieldControlStateProps } from '../form/types.js';
3
+ import { detectHotkeyPlatform, formatHotkey, getHotkeyTokens, hotkeyFromKeyboardEvent, hotkeySignature, normalizeHotkey } from './model.js';
4
+ import type { HotkeyModifier, HotkeyPlatform, HotkeyValue, ResolvedHotkeyPlatform } from './model.js';
5
+ export type HotkeyValidationResult = boolean | string | null | undefined;
6
+ export type HotkeyInputChangeReason = 'record' | 'clear';
7
+ export interface HotkeyInputChangeDetails {
8
+ reason: HotkeyInputChangeReason;
9
+ conflict?: string;
10
+ }
11
+ export interface HotkeyProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
12
+ value: HotkeyValue;
13
+ platform?: HotkeyPlatform;
14
+ separator?: ReactNode;
15
+ size?: FieldControlSize;
16
+ }
17
+ export interface HotkeyInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'children' | 'defaultValue' | 'onChange' | 'readOnly' | 'size' | 'value'>, FieldControlStateProps {
18
+ value?: HotkeyValue | null;
19
+ defaultValue?: HotkeyValue | null;
20
+ onValueChange?: (value: HotkeyValue | null, details: HotkeyInputChangeDetails) => void;
21
+ onCancel?: () => void;
22
+ validateHotkey?: (value: HotkeyValue) => HotkeyValidationResult;
23
+ onConflict?: (value: HotkeyValue, message: string) => void;
24
+ conflict?: boolean | string;
25
+ conflictMessage?: string;
26
+ platform?: HotkeyPlatform;
27
+ placeholder?: string;
28
+ clearable?: boolean;
29
+ clearButtonLabel?: string;
30
+ blurOnCapture?: boolean;
31
+ readOnly?: boolean;
32
+ classNames?: Partial<Record<'root' | 'control' | 'input' | 'value' | 'message', string>>;
33
+ }
34
+ export declare const Hotkey: import("react").ForwardRefExoticComponent<HotkeyProps & import("react").RefAttributes<HTMLElement>>;
35
+ export declare const HotkeyInput: import("react").ForwardRefExoticComponent<HotkeyInputProps & import("react").RefAttributes<HTMLInputElement>>;
36
+ export type { FieldControlSize, HotkeyModifier, HotkeyPlatform, HotkeyValue, ResolvedHotkeyPlatform, };
37
+ export { detectHotkeyPlatform, formatHotkey, getHotkeyTokens, hotkeyFromKeyboardEvent, hotkeySignature, normalizeHotkey, };
@@ -0,0 +1,130 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState, } from 'react';
3
+ import { PencilIcon } from '../PencilIcon.js';
4
+ import { PencilSurface } from '../PencilSurface.js';
5
+ import { PencilText } from '../PencilText.js';
6
+ import { useResolvedControlProps } from '../form/controlProps.js';
7
+ import { detectHotkeyPlatform, formatHotkey, getHotkeyTokens, hotkeyFromKeyboardEvent, hotkeySignature, isHotkeyClear, isHotkeyEscape, normalizeHotkey, } from './model.js';
8
+ const clearPaths = [{ d: 'M18 6 6 18' }, { d: 'm6 6 12 12' }];
9
+ const classes = (...values) => values.filter(Boolean).join(' ');
10
+ function browserPlatform() {
11
+ if (typeof navigator === 'undefined')
12
+ return '';
13
+ const userAgentData = navigator.userAgentData;
14
+ return userAgentData?.platform ?? navigator.platform ?? navigator.userAgent;
15
+ }
16
+ function useHotkeyPlatform(platform) {
17
+ const explicit = platform === 'auto' ? null : platform;
18
+ const [detected, setDetected] = useState('windows');
19
+ useEffect(() => {
20
+ if (!explicit)
21
+ setDetected(detectHotkeyPlatform(browserPlatform()));
22
+ }, [explicit]);
23
+ return explicit ?? detected;
24
+ }
25
+ function conflictText(result, fallback) {
26
+ if (typeof result === 'string')
27
+ return result || undefined;
28
+ return result ? fallback : undefined;
29
+ }
30
+ export const Hotkey = forwardRef(function Hotkey({ 'aria-label': ariaLabel, className, platform = 'auto', separator, size = 'md', value, ...props }, ref) {
31
+ const resolvedPlatform = useHotkeyPlatform(platform);
32
+ const normalized = normalizeHotkey(value);
33
+ const tokens = getHotkeyTokens(normalized, resolvedPlatform);
34
+ const label = formatHotkey(normalized, resolvedPlatform);
35
+ return (_jsx("kbd", { ...props, "aria-label": ariaLabel ?? label, className: classes('inkra-hotkey', className), "data-platform": resolvedPlatform, "data-size": size, ref: ref, children: tokens.map((token, index) => (_jsxs("span", { className: "inkra-hotkey__part", children: [index > 0 && resolvedPlatform !== 'mac' ? (_jsx("span", { "aria-hidden": "true", className: "inkra-hotkey__separator", children: separator ?? '+' })) : null, _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-hotkey__key", radius: 4, seed: `${hotkeySignature(normalized)}-${resolvedPlatform}-${index}`, variant: "quiet", children: _jsx(PencilText, { seed: `${hotkeySignature(normalized)}-${resolvedPlatform}-${index}-label`, children: token }) })] }, `${token}-${index}`))) }));
36
+ });
37
+ export const HotkeyInput = forwardRef(function HotkeyInput({ 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, blurOnCapture = true, className, classNames = {}, clearable = true, clearButtonLabel = 'Clear shortcut', conflict, conflictMessage = 'Shortcut already assigned', defaultValue = null, disabled, id, invalid = false, onBlur, onCancel, onCompositionEnd, onCompositionStart, onConflict, onFocus, onKeyDown, onValueChange, placeholder = 'Press shortcut', platform = 'auto', readOnly, required, seed, size = 'md', validateHotkey, value, ...props }, forwardedRef) {
38
+ const generatedId = useId();
39
+ const inputRef = useRef(null);
40
+ const composingRef = useRef(false);
41
+ const isControlled = value !== undefined;
42
+ const [uncontrolledValue, setUncontrolledValue] = useState(() => defaultValue ? normalizeHotkey(defaultValue) : null);
43
+ const [focused, setFocused] = useState(false);
44
+ const [localConflict, setLocalConflict] = useState(null);
45
+ const currentValue = isControlled ? (value ? normalizeHotkey(value) : null) : uncontrolledValue;
46
+ const resolvedPlatform = useHotkeyPlatform(platform);
47
+ const externalConflict = conflictText(conflict, conflictMessage);
48
+ const currentSignature = hotkeySignature(currentValue);
49
+ const validationMessage = externalConflict ?? (localConflict?.signature === currentSignature ? localConflict.message : undefined);
50
+ const messageId = validationMessage ? `${id ?? generatedId}-hotkey-conflict` : undefined;
51
+ const state = useResolvedControlProps({
52
+ id,
53
+ required,
54
+ disabled,
55
+ readOnly,
56
+ invalid: invalid || Boolean(validationMessage),
57
+ 'aria-describedby': [ariaDescribedBy, messageId].filter(Boolean).join(' ') || undefined,
58
+ 'aria-errormessage': validationMessage ? messageId : ariaErrorMessage,
59
+ 'aria-invalid': ariaInvalid,
60
+ });
61
+ const resolvedSeed = seed ?? `inkra-hotkey-input-${state.id ?? generatedId}`;
62
+ const displayValue = currentValue ? formatHotkey(currentValue, resolvedPlatform) : '';
63
+ useImperativeHandle(forwardedRef, () => inputRef.current);
64
+ const updateValue = (nextValue, details) => {
65
+ if (!isControlled)
66
+ setUncontrolledValue(nextValue);
67
+ onValueChange?.(nextValue, details);
68
+ };
69
+ const clear = () => {
70
+ if (state.disabled || state.readOnly || !currentValue)
71
+ return;
72
+ setLocalConflict(null);
73
+ updateValue(null, { reason: 'clear' });
74
+ inputRef.current?.focus({ preventScroll: true });
75
+ };
76
+ const handleKeyDown = (event) => {
77
+ onKeyDown?.(event);
78
+ if (event.defaultPrevented || state.disabled || state.readOnly)
79
+ return;
80
+ const nativeEvent = event.nativeEvent;
81
+ const eventLike = {
82
+ key: event.key,
83
+ code: event.code,
84
+ ctrlKey: event.ctrlKey,
85
+ altKey: event.altKey,
86
+ shiftKey: event.shiftKey,
87
+ metaKey: event.metaKey,
88
+ isComposing: composingRef.current || nativeEvent.isComposing,
89
+ keyCode: nativeEvent.keyCode,
90
+ };
91
+ if (isHotkeyEscape(eventLike)) {
92
+ onCancel?.();
93
+ event.currentTarget.blur();
94
+ return;
95
+ }
96
+ if (isHotkeyClear(eventLike)) {
97
+ clear();
98
+ return;
99
+ }
100
+ if (event.key === 'Tab')
101
+ return;
102
+ const nextValue = hotkeyFromKeyboardEvent(eventLike);
103
+ if (!nextValue)
104
+ return;
105
+ const result = validateHotkey?.(nextValue);
106
+ const message = conflictText(result, conflictMessage);
107
+ setLocalConflict(message ? { signature: hotkeySignature(nextValue), message } : null);
108
+ if (message)
109
+ onConflict?.(nextValue, message);
110
+ updateValue(nextValue, { reason: 'record', ...(message ? { conflict: message } : {}) });
111
+ if (blurOnCapture)
112
+ event.currentTarget.blur();
113
+ };
114
+ const handleFocus = (event) => {
115
+ setFocused(true);
116
+ onFocus?.(event);
117
+ };
118
+ const handleBlur = (event) => {
119
+ setFocused(false);
120
+ onBlur?.(event);
121
+ };
122
+ return (_jsxs("span", { className: classes('inkra-hotkey-input', classNames.root), "data-disabled": state.disabled || undefined, "data-focus-within": focused || undefined, "data-invalid": state.invalid || undefined, "data-readonly": state.readOnly || undefined, "data-size": size, children: [_jsxs(PencilSurface, { as: "span", className: classes('inkra-hotkey-input__control', classNames.control), clipContent: true, seed: resolvedSeed, variant: state.invalid ? 'danger' : 'outline', children: [_jsx("input", { ...props, "aria-describedby": state.describedBy, "aria-errormessage": state.errorMessage, "aria-invalid": state.ariaInvalid, "aria-label": ariaLabel, autoComplete: "off", className: classes('inkra-hotkey-input__native', className, classNames.input), disabled: state.disabled, id: state.id, inputMode: "none", onBlur: handleBlur, onChange: () => { }, onCompositionEnd: event => {
123
+ composingRef.current = false;
124
+ onCompositionEnd?.(event);
125
+ }, onCompositionStart: event => {
126
+ composingRef.current = true;
127
+ onCompositionStart?.(event);
128
+ }, onFocus: handleFocus, onKeyDown: handleKeyDown, readOnly: state.readOnly, ref: inputRef, required: state.required, spellCheck: false, value: displayValue }), _jsx("span", { "aria-hidden": "true", className: classes('inkra-hotkey-input__value', classNames.value), children: currentValue ? _jsx(Hotkey, { value: currentValue, platform: resolvedPlatform, size: size }) : _jsx(PencilText, { className: "inkra-hotkey-input__placeholder", seed: `${resolvedSeed}-placeholder`, children: placeholder }) }), clearable && currentValue ? (_jsx("button", { "aria-label": clearButtonLabel, className: "inkra-hotkey-input__clear", disabled: state.disabled || state.readOnly, onClick: clear, onPointerDown: event => event.preventDefault(), title: clearButtonLabel, type: "button", children: _jsx(PencilIcon, { paths: clearPaths, seed: `${resolvedSeed}-clear`, size: size === 'sm' ? 18 : size === 'lg' ? 26 : 22, sourceSize: 24 }) })) : null] }), validationMessage ? (_jsx(PencilText, { as: "span", className: classes('inkra-hotkey-input__message', classNames.message), id: messageId, role: "alert", seed: `${resolvedSeed}-message`, children: validationMessage })) : null] }));
129
+ });
130
+ export { detectHotkeyPlatform, formatHotkey, getHotkeyTokens, hotkeyFromKeyboardEvent, hotkeySignature, normalizeHotkey, };
@@ -0,0 +1,27 @@
1
+ export type HotkeyPlatform = 'auto' | 'mac' | 'windows';
2
+ export type ResolvedHotkeyPlatform = Exclude<HotkeyPlatform, 'auto'>;
3
+ export type HotkeyModifier = 'control' | 'alt' | 'shift' | 'meta';
4
+ export interface HotkeyValue {
5
+ key: string;
6
+ code?: string;
7
+ modifiers?: readonly HotkeyModifier[];
8
+ }
9
+ export interface HotkeyKeyboardEventLike {
10
+ key: string;
11
+ code?: string;
12
+ ctrlKey?: boolean;
13
+ altKey?: boolean;
14
+ shiftKey?: boolean;
15
+ metaKey?: boolean;
16
+ isComposing?: boolean;
17
+ keyCode?: number;
18
+ }
19
+ export declare function detectHotkeyPlatform(platform?: string): ResolvedHotkeyPlatform;
20
+ export declare function normalizeHotkey(value: HotkeyValue): HotkeyValue;
21
+ export declare function normalizeHotkeyKey(key: string): string;
22
+ export declare function hotkeyFromKeyboardEvent(event: HotkeyKeyboardEventLike): HotkeyValue | null;
23
+ export declare function hotkeySignature(value: HotkeyValue | null | undefined): string;
24
+ export declare function getHotkeyTokens(value: HotkeyValue, platform: ResolvedHotkeyPlatform): string[];
25
+ export declare function formatHotkey(value: HotkeyValue, platform: ResolvedHotkeyPlatform): string;
26
+ export declare function isHotkeyEscape(event: HotkeyKeyboardEventLike): boolean;
27
+ export declare function isHotkeyClear(event: HotkeyKeyboardEventLike): boolean;