@fgv/ts-app-shell 5.1.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +26 -0
  2. package/dist/index.browser.js +3 -0
  3. package/dist/index.js +43 -0
  4. package/dist/packlets/ai-assist/index.js +6 -0
  5. package/dist/packlets/ai-assist/useAiAssist.js +219 -0
  6. package/dist/packlets/cascade/CascadeContainer.js +83 -0
  7. package/dist/packlets/cascade/ComparisonView.js +48 -0
  8. package/dist/packlets/cascade/EntityTabLayout.js +104 -0
  9. package/dist/packlets/cascade/MobileCascadeStack.js +63 -0
  10. package/dist/packlets/cascade/index.js +37 -0
  11. package/dist/packlets/cascade/model.js +30 -0
  12. package/dist/packlets/cascade/useCascadeOps.js +206 -0
  13. package/dist/packlets/cascade/useCascadeTransitions.js +58 -0
  14. package/dist/packlets/detail/DetailHelpers.js +103 -0
  15. package/dist/packlets/detail/index.js +6 -0
  16. package/dist/packlets/drop-zone/JsonDropZone.js +112 -0
  17. package/dist/packlets/drop-zone/index.js +6 -0
  18. package/dist/packlets/editing/EditFieldHelpers.js +130 -0
  19. package/dist/packlets/editing/MultiActionButton.js +73 -0
  20. package/dist/packlets/editing/NumericInput.js +119 -0
  21. package/dist/packlets/editing/TypeaheadInput.js +207 -0
  22. package/dist/packlets/editing/index.js +10 -0
  23. package/dist/packlets/editing/useTypeaheadMatch.js +102 -0
  24. package/dist/packlets/keyboard/index.js +7 -0
  25. package/dist/packlets/keyboard/registry.js +133 -0
  26. package/dist/packlets/keyboard/useKeyboardShortcuts.js +117 -0
  27. package/dist/packlets/messages/MessagesContext.js +76 -0
  28. package/dist/packlets/messages/MessagesLogger.js +103 -0
  29. package/dist/packlets/messages/StatusBar.js +154 -0
  30. package/dist/packlets/messages/Toast.js +68 -0
  31. package/dist/packlets/messages/index.js +11 -0
  32. package/dist/packlets/messages/model.js +56 -0
  33. package/dist/packlets/messages/useLogReporter.js +66 -0
  34. package/dist/packlets/modal/ConfirmDialog.js +78 -0
  35. package/dist/packlets/modal/Modal.js +55 -0
  36. package/dist/packlets/modal/index.js +7 -0
  37. package/dist/packlets/print/PrintEnclosure.js +60 -0
  38. package/dist/packlets/print/index.js +7 -0
  39. package/dist/packlets/print/openPrintWindow.js +112 -0
  40. package/dist/packlets/responsive/ResponsiveProvider.js +56 -0
  41. package/dist/packlets/responsive/index.js +7 -0
  42. package/dist/packlets/responsive/useResponsiveLayout.js +118 -0
  43. package/dist/packlets/selectors/EntityRow.js +276 -0
  44. package/dist/packlets/selectors/PreferredSelector.js +251 -0
  45. package/dist/packlets/selectors/index.js +24 -0
  46. package/dist/packlets/sidebar/CollectionSection.js +107 -0
  47. package/dist/packlets/sidebar/EntityList.js +164 -0
  48. package/dist/packlets/sidebar/FilterBar.js +42 -0
  49. package/dist/packlets/sidebar/FilterRow.js +182 -0
  50. package/dist/packlets/sidebar/GroupedEntityList.js +183 -0
  51. package/dist/packlets/sidebar/SearchBar.js +34 -0
  52. package/dist/packlets/sidebar/SidebarLayout.js +62 -0
  53. package/dist/packlets/sidebar/index.js +12 -0
  54. package/dist/packlets/theme/ThemeProvider.js +141 -0
  55. package/dist/packlets/theme/index.js +6 -0
  56. package/dist/packlets/top-bar/ModeSelector.js +46 -0
  57. package/dist/packlets/top-bar/TabBar.js +37 -0
  58. package/dist/packlets/top-bar/index.js +7 -0
  59. package/dist/packlets/url-sync/index.js +6 -0
  60. package/dist/packlets/url-sync/useUrlSync.js +157 -0
  61. package/eslint.config.js +22 -0
  62. package/lib/index.browser.d.ts +2 -0
  63. package/lib/index.browser.js +19 -0
  64. package/lib/index.d.ts +28 -0
  65. package/lib/index.js +59 -0
  66. package/lib/packlets/ai-assist/index.d.ts +6 -0
  67. package/lib/packlets/ai-assist/index.js +11 -0
  68. package/lib/packlets/ai-assist/useAiAssist.d.ts +77 -0
  69. package/lib/packlets/ai-assist/useAiAssist.js +223 -0
  70. package/lib/packlets/cascade/CascadeContainer.d.ts +44 -0
  71. package/lib/packlets/cascade/CascadeContainer.js +119 -0
  72. package/lib/packlets/cascade/ComparisonView.d.ts +35 -0
  73. package/lib/packlets/cascade/ComparisonView.js +54 -0
  74. package/lib/packlets/cascade/EntityTabLayout.d.ts +47 -0
  75. package/lib/packlets/cascade/EntityTabLayout.js +110 -0
  76. package/lib/packlets/cascade/MobileCascadeStack.d.ts +20 -0
  77. package/lib/packlets/cascade/MobileCascadeStack.js +99 -0
  78. package/lib/packlets/cascade/index.d.ts +12 -0
  79. package/lib/packlets/cascade/index.js +48 -0
  80. package/lib/packlets/cascade/model.d.ts +57 -0
  81. package/lib/packlets/cascade/model.js +33 -0
  82. package/lib/packlets/cascade/useCascadeOps.d.ts +111 -0
  83. package/lib/packlets/cascade/useCascadeOps.js +209 -0
  84. package/lib/packlets/cascade/useCascadeTransitions.d.ts +19 -0
  85. package/lib/packlets/cascade/useCascadeTransitions.js +62 -0
  86. package/lib/packlets/detail/DetailHelpers.d.ts +83 -0
  87. package/lib/packlets/detail/DetailHelpers.js +113 -0
  88. package/lib/packlets/detail/index.d.ts +6 -0
  89. package/lib/packlets/detail/index.js +14 -0
  90. package/lib/packlets/drop-zone/JsonDropZone.d.ts +40 -0
  91. package/lib/packlets/drop-zone/JsonDropZone.js +149 -0
  92. package/lib/packlets/drop-zone/index.d.ts +6 -0
  93. package/lib/packlets/drop-zone/index.js +10 -0
  94. package/lib/packlets/editing/EditFieldHelpers.d.ts +171 -0
  95. package/lib/packlets/editing/EditFieldHelpers.js +144 -0
  96. package/lib/packlets/editing/MultiActionButton.d.ts +45 -0
  97. package/lib/packlets/editing/MultiActionButton.js +109 -0
  98. package/lib/packlets/editing/NumericInput.d.ts +47 -0
  99. package/lib/packlets/editing/NumericInput.js +155 -0
  100. package/lib/packlets/editing/TypeaheadInput.d.ts +46 -0
  101. package/lib/packlets/editing/TypeaheadInput.js +243 -0
  102. package/lib/packlets/editing/index.d.ts +10 -0
  103. package/lib/packlets/editing/index.js +26 -0
  104. package/lib/packlets/editing/useTypeaheadMatch.d.ts +42 -0
  105. package/lib/packlets/editing/useTypeaheadMatch.js +105 -0
  106. package/lib/packlets/keyboard/index.d.ts +7 -0
  107. package/lib/packlets/keyboard/index.js +15 -0
  108. package/lib/packlets/keyboard/registry.d.ts +92 -0
  109. package/lib/packlets/keyboard/registry.js +138 -0
  110. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts +50 -0
  111. package/lib/packlets/keyboard/useKeyboardShortcuts.js +155 -0
  112. package/lib/packlets/messages/MessagesContext.d.ts +40 -0
  113. package/lib/packlets/messages/MessagesContext.js +113 -0
  114. package/lib/packlets/messages/MessagesLogger.d.ts +50 -0
  115. package/lib/packlets/messages/MessagesLogger.js +107 -0
  116. package/lib/packlets/messages/StatusBar.d.ts +22 -0
  117. package/lib/packlets/messages/StatusBar.js +190 -0
  118. package/lib/packlets/messages/Toast.d.ts +31 -0
  119. package/lib/packlets/messages/Toast.js +105 -0
  120. package/lib/packlets/messages/index.d.ts +11 -0
  121. package/lib/packlets/messages/index.js +24 -0
  122. package/lib/packlets/messages/model.d.ts +59 -0
  123. package/lib/packlets/messages/model.js +61 -0
  124. package/lib/packlets/messages/useLogReporter.d.ts +22 -0
  125. package/lib/packlets/messages/useLogReporter.js +69 -0
  126. package/lib/packlets/modal/ConfirmDialog.d.ts +39 -0
  127. package/lib/packlets/modal/ConfirmDialog.js +114 -0
  128. package/lib/packlets/modal/Modal.d.ts +22 -0
  129. package/lib/packlets/modal/Modal.js +91 -0
  130. package/lib/packlets/modal/index.d.ts +7 -0
  131. package/lib/packlets/modal/index.js +12 -0
  132. package/lib/packlets/print/PrintEnclosure.d.ts +33 -0
  133. package/lib/packlets/print/PrintEnclosure.js +96 -0
  134. package/lib/packlets/print/index.d.ts +7 -0
  135. package/lib/packlets/print/index.js +12 -0
  136. package/lib/packlets/print/openPrintWindow.d.ts +35 -0
  137. package/lib/packlets/print/openPrintWindow.js +118 -0
  138. package/lib/packlets/responsive/ResponsiveProvider.d.ts +35 -0
  139. package/lib/packlets/responsive/ResponsiveProvider.js +93 -0
  140. package/lib/packlets/responsive/index.d.ts +7 -0
  141. package/lib/packlets/responsive/index.js +13 -0
  142. package/lib/packlets/responsive/useResponsiveLayout.d.ts +48 -0
  143. package/lib/packlets/responsive/useResponsiveLayout.js +121 -0
  144. package/lib/packlets/selectors/EntityRow.d.ts +45 -0
  145. package/lib/packlets/selectors/EntityRow.js +315 -0
  146. package/lib/packlets/selectors/PreferredSelector.d.ts +50 -0
  147. package/lib/packlets/selectors/PreferredSelector.js +287 -0
  148. package/lib/packlets/selectors/index.d.ts +5 -0
  149. package/lib/packlets/selectors/index.js +29 -0
  150. package/lib/packlets/sidebar/CollectionSection.d.ts +82 -0
  151. package/lib/packlets/sidebar/CollectionSection.js +143 -0
  152. package/lib/packlets/sidebar/EntityList.d.ts +105 -0
  153. package/lib/packlets/sidebar/EntityList.js +200 -0
  154. package/lib/packlets/sidebar/FilterBar.d.ts +26 -0
  155. package/lib/packlets/sidebar/FilterBar.js +48 -0
  156. package/lib/packlets/sidebar/FilterRow.d.ts +42 -0
  157. package/lib/packlets/sidebar/FilterRow.js +218 -0
  158. package/lib/packlets/sidebar/GroupedEntityList.d.ts +59 -0
  159. package/lib/packlets/sidebar/GroupedEntityList.js +219 -0
  160. package/lib/packlets/sidebar/SearchBar.d.ts +19 -0
  161. package/lib/packlets/sidebar/SearchBar.js +40 -0
  162. package/lib/packlets/sidebar/SidebarLayout.d.ts +28 -0
  163. package/lib/packlets/sidebar/SidebarLayout.js +98 -0
  164. package/lib/packlets/sidebar/index.d.ts +12 -0
  165. package/lib/packlets/sidebar/index.js +22 -0
  166. package/lib/packlets/theme/ThemeProvider.d.ts +68 -0
  167. package/lib/packlets/theme/ThemeProvider.js +178 -0
  168. package/lib/packlets/theme/index.d.ts +6 -0
  169. package/lib/packlets/theme/index.js +11 -0
  170. package/lib/packlets/top-bar/ModeSelector.d.ts +38 -0
  171. package/lib/packlets/top-bar/ModeSelector.js +52 -0
  172. package/lib/packlets/top-bar/TabBar.d.ts +31 -0
  173. package/lib/packlets/top-bar/TabBar.js +43 -0
  174. package/lib/packlets/top-bar/index.d.ts +7 -0
  175. package/lib/packlets/top-bar/index.js +12 -0
  176. package/lib/packlets/url-sync/index.d.ts +6 -0
  177. package/lib/packlets/url-sync/index.js +12 -0
  178. package/lib/packlets/url-sync/useUrlSync.d.ts +75 -0
  179. package/lib/packlets/url-sync/useUrlSync.js +162 -0
  180. package/package.json +82 -0
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.useTypeaheadMatch = useTypeaheadMatch;
25
+ /**
26
+ * Reusable typeahead matching hook with tiered priority support.
27
+ * @packageDocumentation
28
+ */
29
+ const react_1 = require("react");
30
+ // ============================================================================
31
+ // Hook
32
+ // ============================================================================
33
+ /**
34
+ * Hook that provides typeahead matching and filtering with tiered priority support.
35
+ *
36
+ * `findExactMatch` matches by exact id or case-insensitive name (checks priority first).
37
+ * `resolveOnBlur` tries exact match, then single partial match (priority first).
38
+ * `filterSuggestions` returns suggestions split by tier, filtered by substring match.
39
+ *
40
+ * @param suggestions - The full catalog of suggestions
41
+ * @param prioritySuggestions - Optional priority suggestions shown first (e.g. recipe alternates)
42
+ * @returns Match and filter functions
43
+ * @public
44
+ */
45
+ function useTypeaheadMatch(suggestions, prioritySuggestions) {
46
+ // Pre-compute priority IDs for efficient catalog deduplication
47
+ const priorityIds = (0, react_1.useMemo)(() => { var _a; return new Set((_a = prioritySuggestions === null || prioritySuggestions === void 0 ? void 0 : prioritySuggestions.map((s) => s.id)) !== null && _a !== void 0 ? _a : []); }, [prioritySuggestions]);
48
+ const findExactMatch = (0, react_1.useCallback)((input) => {
49
+ const trimmed = input.trim();
50
+ if (!trimmed)
51
+ return undefined;
52
+ const lower = trimmed.toLowerCase();
53
+ if (prioritySuggestions) {
54
+ const match = prioritySuggestions.find((s) => s.id === trimmed || s.name.toLowerCase() === lower);
55
+ if (match)
56
+ return match;
57
+ }
58
+ return suggestions.find((s) => s.id === trimmed || s.name.toLowerCase() === lower);
59
+ }, [suggestions, prioritySuggestions]);
60
+ const resolveOnBlur = (0, react_1.useCallback)((input) => {
61
+ const trimmed = input.trim();
62
+ if (!trimmed)
63
+ return undefined;
64
+ const lower = trimmed.toLowerCase();
65
+ // Try exact match (priority first, then all)
66
+ if (prioritySuggestions) {
67
+ const priorityExact = prioritySuggestions.find((s) => s.id === trimmed || s.name.toLowerCase() === lower);
68
+ if (priorityExact)
69
+ return priorityExact;
70
+ }
71
+ const exact = suggestions.find((s) => s.id === trimmed || s.name.toLowerCase() === lower);
72
+ if (exact)
73
+ return exact;
74
+ // Try partial match in priority suggestions first
75
+ if (prioritySuggestions) {
76
+ const priorityPartials = prioritySuggestions.filter((s) => s.name.toLowerCase().includes(lower) || s.id.toLowerCase().includes(lower));
77
+ if (priorityPartials.length === 1)
78
+ return priorityPartials[0];
79
+ }
80
+ // Fall back to partial match in all suggestions
81
+ const partials = suggestions.filter((s) => s.name.toLowerCase().includes(lower) || s.id.toLowerCase().includes(lower));
82
+ if (partials.length === 1)
83
+ return partials[0];
84
+ return undefined;
85
+ }, [suggestions, prioritySuggestions]);
86
+ const filterSuggestions = (0, react_1.useCallback)((input) => {
87
+ var _a;
88
+ const trimmed = input.trim();
89
+ const lower = trimmed.toLowerCase();
90
+ // No input — return all suggestions split by tier
91
+ if (!trimmed) {
92
+ return {
93
+ priority: prioritySuggestions !== null && prioritySuggestions !== void 0 ? prioritySuggestions : [],
94
+ catalog: suggestions.filter((s) => !priorityIds.has(s.id))
95
+ };
96
+ }
97
+ // Filter by substring match
98
+ const matchesSuggestion = (s) => s.name.toLowerCase().includes(lower) || s.id.toLowerCase().includes(lower);
99
+ const priority = (_a = prioritySuggestions === null || prioritySuggestions === void 0 ? void 0 : prioritySuggestions.filter(matchesSuggestion)) !== null && _a !== void 0 ? _a : [];
100
+ const catalog = suggestions.filter((s) => !priorityIds.has(s.id) && matchesSuggestion(s));
101
+ return { priority, catalog };
102
+ }, [suggestions, prioritySuggestions, priorityIds]);
103
+ return { findExactMatch, resolveOnBlur, filterSuggestions };
104
+ }
105
+ //# sourceMappingURL=useTypeaheadMatch.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Keyboard shortcut packlet - registry, context, and hooks.
3
+ * @packageDocumentation
4
+ */
5
+ export { type IKeyBinding, type IShortcut, type IShortcutRegistration, KeyboardShortcutRegistry, matchesBinding } from './registry';
6
+ export { type IKeyboardShortcutProviderProps, KeyboardShortcutProvider, useKeyboardRegistry, useKeyboardShortcuts } from './useKeyboardShortcuts';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * Keyboard shortcut packlet - registry, context, and hooks.
4
+ * @packageDocumentation
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.useKeyboardShortcuts = exports.useKeyboardRegistry = exports.KeyboardShortcutProvider = exports.matchesBinding = exports.KeyboardShortcutRegistry = void 0;
8
+ var registry_1 = require("./registry");
9
+ Object.defineProperty(exports, "KeyboardShortcutRegistry", { enumerable: true, get: function () { return registry_1.KeyboardShortcutRegistry; } });
10
+ Object.defineProperty(exports, "matchesBinding", { enumerable: true, get: function () { return registry_1.matchesBinding; } });
11
+ var useKeyboardShortcuts_1 = require("./useKeyboardShortcuts");
12
+ Object.defineProperty(exports, "KeyboardShortcutProvider", { enumerable: true, get: function () { return useKeyboardShortcuts_1.KeyboardShortcutProvider; } });
13
+ Object.defineProperty(exports, "useKeyboardRegistry", { enumerable: true, get: function () { return useKeyboardShortcuts_1.useKeyboardRegistry; } });
14
+ Object.defineProperty(exports, "useKeyboardShortcuts", { enumerable: true, get: function () { return useKeyboardShortcuts_1.useKeyboardShortcuts; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Keyboard shortcut registry infrastructure.
3
+ *
4
+ * Provides a centralized registry for keyboard shortcuts with:
5
+ * - Modifier-aware key matching (Cmd/Ctrl, Shift, Alt)
6
+ * - Priority-based handler resolution (higher priority wins)
7
+ * - React hook for component-scoped shortcut registration
8
+ * - Global listener that dispatches to registered handlers
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ /**
13
+ * Describes a keyboard shortcut binding.
14
+ * @public
15
+ */
16
+ export interface IKeyBinding {
17
+ /** The key (e.g., 'k', 'z', 'Escape'). Case-insensitive. */
18
+ readonly key: string;
19
+ /** Require Cmd (Mac) / Ctrl (Windows/Linux) */
20
+ readonly meta?: boolean;
21
+ /** Require Shift */
22
+ readonly shift?: boolean;
23
+ /** Require Alt/Option */
24
+ readonly alt?: boolean;
25
+ }
26
+ /**
27
+ * A registered keyboard shortcut.
28
+ * @public
29
+ */
30
+ export interface IShortcut {
31
+ /** The key binding */
32
+ readonly binding: IKeyBinding;
33
+ /** Human-readable description (for command palette / help) */
34
+ readonly description: string;
35
+ /** Handler function. Return true to indicate the event was handled. */
36
+ readonly handler: () => boolean | void;
37
+ /** Priority (higher wins when multiple shortcuts match). Default: 0 */
38
+ readonly priority?: number;
39
+ }
40
+ /**
41
+ * A registration handle returned when a shortcut is registered.
42
+ * Call `unregister()` to remove the shortcut.
43
+ * @public
44
+ */
45
+ export interface IShortcutRegistration {
46
+ readonly unregister: () => void;
47
+ }
48
+ /**
49
+ * Tests whether a keyboard event matches a key binding.
50
+ * @param event - The keyboard event
51
+ * @param binding - The binding to match against
52
+ * @returns true if the event matches
53
+ * @public
54
+ */
55
+ export declare function matchesBinding(event: KeyboardEvent, binding: IKeyBinding): boolean;
56
+ /**
57
+ * Centralized keyboard shortcut registry.
58
+ *
59
+ * Components register shortcuts via `register()` and receive a handle
60
+ * to unregister when they unmount. The registry attaches a single global
61
+ * keydown listener that dispatches to the highest-priority matching handler.
62
+ *
63
+ * @public
64
+ */
65
+ export declare class KeyboardShortcutRegistry {
66
+ private readonly _shortcuts;
67
+ private _listener;
68
+ private _nextId;
69
+ /**
70
+ * Registers a keyboard shortcut.
71
+ * @param shortcut - The shortcut to register
72
+ * @returns A registration handle with an `unregister()` method
73
+ */
74
+ register(shortcut: IShortcut): IShortcutRegistration;
75
+ /**
76
+ * Returns all currently registered shortcuts (for command palette / help display).
77
+ */
78
+ getAll(): ReadonlyArray<IShortcut>;
79
+ /**
80
+ * Dispatches a keyboard event to the highest-priority matching handler.
81
+ * @param event - The keyboard event
82
+ * @returns true if a handler consumed the event
83
+ */
84
+ dispatch(event: KeyboardEvent): boolean;
85
+ /**
86
+ * Removes all shortcuts and the global listener.
87
+ */
88
+ dispose(): void;
89
+ private _ensureListener;
90
+ private _removeListener;
91
+ }
92
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.KeyboardShortcutRegistry = void 0;
25
+ exports.matchesBinding = matchesBinding;
26
+ // ============================================================================
27
+ // Key Matching
28
+ // ============================================================================
29
+ /**
30
+ * Tests whether a keyboard event matches a key binding.
31
+ * @param event - The keyboard event
32
+ * @param binding - The binding to match against
33
+ * @returns true if the event matches
34
+ * @public
35
+ */
36
+ function matchesBinding(event, binding) {
37
+ var _a, _b, _c;
38
+ const keyMatch = event.key.toLowerCase() === binding.key.toLowerCase();
39
+ const metaMatch = ((_a = binding.meta) !== null && _a !== void 0 ? _a : false) === (event.metaKey || event.ctrlKey);
40
+ const shiftMatch = ((_b = binding.shift) !== null && _b !== void 0 ? _b : false) === event.shiftKey;
41
+ const altMatch = ((_c = binding.alt) !== null && _c !== void 0 ? _c : false) === event.altKey;
42
+ return keyMatch && metaMatch && shiftMatch && altMatch;
43
+ }
44
+ // ============================================================================
45
+ // Registry
46
+ // ============================================================================
47
+ /**
48
+ * Centralized keyboard shortcut registry.
49
+ *
50
+ * Components register shortcuts via `register()` and receive a handle
51
+ * to unregister when they unmount. The registry attaches a single global
52
+ * keydown listener that dispatches to the highest-priority matching handler.
53
+ *
54
+ * @public
55
+ */
56
+ class KeyboardShortcutRegistry {
57
+ constructor() {
58
+ this._shortcuts = new Map();
59
+ this._nextId = 0;
60
+ }
61
+ /**
62
+ * Registers a keyboard shortcut.
63
+ * @param shortcut - The shortcut to register
64
+ * @returns A registration handle with an `unregister()` method
65
+ */
66
+ register(shortcut) {
67
+ const id = `ks-${++this._nextId}`;
68
+ this._shortcuts.set(id, shortcut);
69
+ this._ensureListener();
70
+ return {
71
+ unregister: () => {
72
+ this._shortcuts.delete(id);
73
+ if (this._shortcuts.size === 0) {
74
+ this._removeListener();
75
+ }
76
+ }
77
+ };
78
+ }
79
+ /**
80
+ * Returns all currently registered shortcuts (for command palette / help display).
81
+ */
82
+ getAll() {
83
+ return Array.from(this._shortcuts.values());
84
+ }
85
+ /**
86
+ * Dispatches a keyboard event to the highest-priority matching handler.
87
+ * @param event - The keyboard event
88
+ * @returns true if a handler consumed the event
89
+ */
90
+ dispatch(event) {
91
+ var _a;
92
+ // Skip if the event target is an input/textarea/contenteditable
93
+ const target = event.target;
94
+ if (target) {
95
+ const tagName = (_a = target.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
96
+ if (tagName === 'input' || tagName === 'textarea' || target.isContentEditable) {
97
+ return false;
98
+ }
99
+ }
100
+ // Find all matching shortcuts, sorted by priority (highest first)
101
+ const matches = Array.from(this._shortcuts.values())
102
+ .filter((s) => matchesBinding(event, s.binding))
103
+ .sort((a, b) => { var _a, _b; return ((_a = b.priority) !== null && _a !== void 0 ? _a : 0) - ((_b = a.priority) !== null && _b !== void 0 ? _b : 0); });
104
+ for (const shortcut of matches) {
105
+ const result = shortcut.handler();
106
+ if (result !== false) {
107
+ event.preventDefault();
108
+ event.stopPropagation();
109
+ return true;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+ /**
115
+ * Removes all shortcuts and the global listener.
116
+ */
117
+ dispose() {
118
+ this._shortcuts.clear();
119
+ this._removeListener();
120
+ }
121
+ _ensureListener() {
122
+ if (this._listener) {
123
+ return;
124
+ }
125
+ this._listener = (e) => {
126
+ this.dispatch(e);
127
+ };
128
+ document.addEventListener('keydown', this._listener);
129
+ }
130
+ _removeListener() {
131
+ if (this._listener) {
132
+ document.removeEventListener('keydown', this._listener);
133
+ this._listener = undefined;
134
+ }
135
+ }
136
+ }
137
+ exports.KeyboardShortcutRegistry = KeyboardShortcutRegistry;
138
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * React context and hooks for the keyboard shortcut registry.
3
+ * @packageDocumentation
4
+ */
5
+ import React from 'react';
6
+ import { KeyboardShortcutRegistry, type IShortcut } from './registry';
7
+ /**
8
+ * Props for the KeyboardShortcutProvider.
9
+ * @public
10
+ */
11
+ export interface IKeyboardShortcutProviderProps {
12
+ readonly children: React.ReactNode;
13
+ }
14
+ /**
15
+ * Provides a KeyboardShortcutRegistry to the component tree.
16
+ * Should be mounted once at the app root.
17
+ * @public
18
+ */
19
+ export declare function KeyboardShortcutProvider(props: IKeyboardShortcutProviderProps): React.ReactElement;
20
+ /**
21
+ * Returns the keyboard shortcut registry.
22
+ * Must be used within a KeyboardShortcutProvider.
23
+ * @public
24
+ */
25
+ export declare function useKeyboardRegistry(): KeyboardShortcutRegistry;
26
+ /**
27
+ * Registers one or more keyboard shortcuts for the lifetime of the component.
28
+ * Shortcuts are automatically unregistered on unmount.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * useKeyboardShortcuts([
33
+ * {
34
+ * binding: { key: 'k', meta: true },
35
+ * description: 'Open command palette',
36
+ * handler: () => { setCommandPaletteOpen(true); }
37
+ * },
38
+ * {
39
+ * binding: { key: 'z', meta: true },
40
+ * description: 'Undo',
41
+ * handler: () => { workspace.undo(); }
42
+ * }
43
+ * ]);
44
+ * ```
45
+ *
46
+ * @param shortcuts - Array of shortcuts to register
47
+ * @public
48
+ */
49
+ export declare function useKeyboardShortcuts(shortcuts: ReadonlyArray<IShortcut>): void;
50
+ //# sourceMappingURL=useKeyboardShortcuts.d.ts.map
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.KeyboardShortcutProvider = KeyboardShortcutProvider;
58
+ exports.useKeyboardRegistry = useKeyboardRegistry;
59
+ exports.useKeyboardShortcuts = useKeyboardShortcuts;
60
+ /**
61
+ * React context and hooks for the keyboard shortcut registry.
62
+ * @packageDocumentation
63
+ */
64
+ const react_1 = __importStar(require("react"));
65
+ const registry_1 = require("./registry");
66
+ // ============================================================================
67
+ // Context
68
+ // ============================================================================
69
+ const KeyboardContext = react_1.default.createContext(undefined);
70
+ /**
71
+ * Provides a KeyboardShortcutRegistry to the component tree.
72
+ * Should be mounted once at the app root.
73
+ * @public
74
+ */
75
+ function KeyboardShortcutProvider(props) {
76
+ const registry = (0, react_1.useMemo)(() => new registry_1.KeyboardShortcutRegistry(), []);
77
+ (0, react_1.useEffect)(() => {
78
+ return () => registry.dispose();
79
+ }, [registry]);
80
+ return react_1.default.createElement(KeyboardContext.Provider, { value: registry }, props.children);
81
+ }
82
+ // ============================================================================
83
+ // Hooks
84
+ // ============================================================================
85
+ /**
86
+ * Returns the keyboard shortcut registry.
87
+ * Must be used within a KeyboardShortcutProvider.
88
+ * @public
89
+ */
90
+ function useKeyboardRegistry() {
91
+ const ctx = (0, react_1.useContext)(KeyboardContext);
92
+ if (ctx === undefined) {
93
+ throw new Error('useKeyboardRegistry must be used within a KeyboardShortcutProvider');
94
+ }
95
+ return ctx;
96
+ }
97
+ /**
98
+ * Registers one or more keyboard shortcuts for the lifetime of the component.
99
+ * Shortcuts are automatically unregistered on unmount.
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * useKeyboardShortcuts([
104
+ * {
105
+ * binding: { key: 'k', meta: true },
106
+ * description: 'Open command palette',
107
+ * handler: () => { setCommandPaletteOpen(true); }
108
+ * },
109
+ * {
110
+ * binding: { key: 'z', meta: true },
111
+ * description: 'Undo',
112
+ * handler: () => { workspace.undo(); }
113
+ * }
114
+ * ]);
115
+ * ```
116
+ *
117
+ * @param shortcuts - Array of shortcuts to register
118
+ * @public
119
+ */
120
+ function useKeyboardShortcuts(shortcuts) {
121
+ const registry = useKeyboardRegistry();
122
+ const shortcutsRef = (0, react_1.useRef)(shortcuts);
123
+ shortcutsRef.current = shortcuts;
124
+ (0, react_1.useEffect)(() => {
125
+ // Register stable wrapper shortcuts that delegate to the current ref entries.
126
+ // This means the effect only runs when the registry changes or the number of
127
+ // shortcuts changes — not on every render when the array identity changes.
128
+ const registrations = shortcutsRef.current.map((__item, index) => registry.register({
129
+ get binding() {
130
+ var _a, _b;
131
+ return (_b = (_a = shortcutsRef.current[index]) === null || _a === void 0 ? void 0 : _a.binding) !== null && _b !== void 0 ? _b : { key: '' };
132
+ },
133
+ get description() {
134
+ var _a, _b;
135
+ return (_b = (_a = shortcutsRef.current[index]) === null || _a === void 0 ? void 0 : _a.description) !== null && _b !== void 0 ? _b : '';
136
+ },
137
+ get priority() {
138
+ var _a;
139
+ return (_a = shortcutsRef.current[index]) === null || _a === void 0 ? void 0 : _a.priority;
140
+ },
141
+ handler: () => {
142
+ var _a;
143
+ return (_a = shortcutsRef.current[index]) === null || _a === void 0 ? void 0 : _a.handler();
144
+ }
145
+ }));
146
+ return () => {
147
+ for (const reg of registrations) {
148
+ reg.unregister();
149
+ }
150
+ };
151
+ // Re-register only when the registry instance or the number of shortcuts changes.
152
+ // Handler/binding updates are picked up via the ref without re-registration.
153
+ }, [registry, shortcuts.length]);
154
+ }
155
+ //# sourceMappingURL=useKeyboardShortcuts.js.map
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { IMessage, IMessageAction, MessageSeverity } from './model';
3
+ /**
4
+ * Value provided by the MessagesContext.
5
+ * @public
6
+ */
7
+ export interface IMessagesContextValue {
8
+ /** All messages in the stream (newest last) */
9
+ readonly messages: ReadonlyArray<IMessage>;
10
+ /** Add a message to the stream */
11
+ readonly addMessage: (severity: MessageSeverity, text: string, action?: IMessageAction) => IMessage;
12
+ /** Dismiss a specific message (removes from toast display, keeps in log) */
13
+ readonly dismissMessage: (id: string) => void;
14
+ /** Clear all messages */
15
+ readonly clearMessages: () => void;
16
+ /** Messages currently visible as toasts (not yet dismissed) */
17
+ readonly activeToasts: ReadonlyArray<IMessage>;
18
+ }
19
+ /**
20
+ * Props for the MessagesProvider.
21
+ * @public
22
+ */
23
+ export interface IMessagesProviderProps {
24
+ /** Maximum number of messages to retain in the log */
25
+ readonly maxMessages?: number;
26
+ /** Children */
27
+ readonly children: React.ReactNode;
28
+ }
29
+ /**
30
+ * Provides the global message stream to the component tree.
31
+ * @public
32
+ */
33
+ export declare function MessagesProvider(props: IMessagesProviderProps): React.ReactElement;
34
+ /**
35
+ * Hook to access the messages context.
36
+ * Must be used within a MessagesProvider.
37
+ * @public
38
+ */
39
+ export declare function useMessages(): IMessagesContextValue;
40
+ //# sourceMappingURL=MessagesContext.d.ts.map