@fgv/ts-app-shell 5.1.0-4 → 5.1.0-40

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 (225) hide show
  1. package/README.md +182 -0
  2. package/dist/index.browser.js.map +1 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/packlets/ai-assist/index.js.map +1 -0
  5. package/dist/packlets/ai-assist/useAiAssist.js +225 -18
  6. package/dist/packlets/ai-assist/useAiAssist.js.map +1 -0
  7. package/dist/packlets/badge/index.js +23 -0
  8. package/dist/packlets/badge/index.js.map +1 -0
  9. package/dist/packlets/badge/renderStatusBadge.js +44 -0
  10. package/dist/packlets/badge/renderStatusBadge.js.map +1 -0
  11. package/dist/packlets/cascade/CascadeContainer.js.map +1 -0
  12. package/dist/packlets/cascade/ComparisonView.js.map +1 -0
  13. package/dist/packlets/cascade/EntityTabLayout.js.map +1 -0
  14. package/dist/packlets/cascade/MobileCascadeStack.js.map +1 -0
  15. package/dist/packlets/cascade/index.js.map +1 -0
  16. package/dist/packlets/cascade/model.js.map +1 -0
  17. package/dist/packlets/cascade/useCascadeOps.js.map +1 -0
  18. package/dist/packlets/cascade/useCascadeTransitions.js.map +1 -0
  19. package/dist/packlets/detail/DetailHelpers.js.map +1 -0
  20. package/dist/packlets/detail/index.js.map +1 -0
  21. package/dist/packlets/drop-zone/JsonDropZone.js.map +1 -0
  22. package/dist/packlets/drop-zone/index.js.map +1 -0
  23. package/dist/packlets/editing/EditFieldHelpers.js +4 -4
  24. package/dist/packlets/editing/EditFieldHelpers.js.map +1 -0
  25. package/dist/packlets/editing/MultiActionButton.js.map +1 -0
  26. package/dist/packlets/editing/NumericInput.js.map +1 -0
  27. package/dist/packlets/editing/TypeaheadInput.js.map +1 -0
  28. package/dist/packlets/editing/index.js.map +1 -0
  29. package/dist/packlets/editing/useTypeaheadMatch.js.map +1 -0
  30. package/dist/packlets/keyboard/index.js.map +1 -0
  31. package/dist/packlets/keyboard/registry.js.map +1 -0
  32. package/dist/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  33. package/dist/packlets/messages/MessagesContext.js +79 -3
  34. package/dist/packlets/messages/MessagesContext.js.map +1 -0
  35. package/dist/packlets/messages/MessagesLogger.js +10 -31
  36. package/dist/packlets/messages/MessagesLogger.js.map +1 -0
  37. package/dist/packlets/messages/StatusBar.js +53 -28
  38. package/dist/packlets/messages/StatusBar.js.map +1 -0
  39. package/dist/packlets/messages/Toast.js +7 -4
  40. package/dist/packlets/messages/Toast.js.map +1 -0
  41. package/dist/packlets/messages/index.js +2 -2
  42. package/dist/packlets/messages/index.js.map +1 -0
  43. package/dist/packlets/messages/model.js +31 -4
  44. package/dist/packlets/messages/model.js.map +1 -0
  45. package/dist/packlets/messages/useLogReporter.js.map +1 -0
  46. package/dist/packlets/modal/ConfirmDialog.js.map +1 -0
  47. package/dist/packlets/modal/Modal.js.map +1 -0
  48. package/dist/packlets/modal/index.js.map +1 -0
  49. package/dist/packlets/print/PrintEnclosure.js.map +1 -0
  50. package/dist/packlets/print/index.js.map +1 -0
  51. package/dist/packlets/print/openPrintWindow.js.map +1 -0
  52. package/dist/packlets/responsive/ResponsiveProvider.js.map +1 -0
  53. package/dist/packlets/responsive/index.js.map +1 -0
  54. package/dist/packlets/responsive/useResponsiveLayout.js.map +1 -0
  55. package/dist/packlets/selectors/EntityRow.js.map +1 -0
  56. package/dist/packlets/selectors/PreferredSelector.js.map +1 -0
  57. package/dist/packlets/selectors/index.js.map +1 -0
  58. package/dist/packlets/sidebar/CollectionSection.js +158 -43
  59. package/dist/packlets/sidebar/CollectionSection.js.map +1 -0
  60. package/dist/packlets/sidebar/EntityList.js.map +1 -0
  61. package/dist/packlets/sidebar/FilterBar.js.map +1 -0
  62. package/dist/packlets/sidebar/FilterRow.js.map +1 -0
  63. package/dist/packlets/sidebar/GroupedEntityList.js.map +1 -0
  64. package/dist/packlets/sidebar/SearchBar.js.map +1 -0
  65. package/dist/packlets/sidebar/SidebarLayout.js.map +1 -0
  66. package/dist/packlets/sidebar/index.js.map +1 -0
  67. package/dist/packlets/theme/ThemeProvider.js.map +1 -0
  68. package/dist/packlets/theme/index.js.map +1 -0
  69. package/dist/packlets/top-bar/ModeSelector.js.map +1 -0
  70. package/dist/packlets/top-bar/TabBar.js +4 -1
  71. package/dist/packlets/top-bar/TabBar.js.map +1 -0
  72. package/dist/packlets/top-bar/index.js.map +1 -0
  73. package/dist/packlets/url-sync/index.js.map +1 -0
  74. package/dist/packlets/url-sync/useUrlSync.js.map +1 -0
  75. package/dist/styles/theme.css +194 -0
  76. package/lib/index.browser.d.ts.map +1 -0
  77. package/lib/index.browser.js.map +1 -0
  78. package/lib/index.d.ts.map +1 -0
  79. package/lib/index.js.map +1 -0
  80. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  81. package/lib/packlets/ai-assist/index.js.map +1 -0
  82. package/lib/packlets/ai-assist/useAiAssist.d.ts +26 -0
  83. package/lib/packlets/ai-assist/useAiAssist.d.ts.map +1 -0
  84. package/lib/packlets/ai-assist/useAiAssist.js +225 -18
  85. package/lib/packlets/ai-assist/useAiAssist.js.map +1 -0
  86. package/lib/packlets/badge/index.d.ts +2 -0
  87. package/lib/packlets/badge/index.d.ts.map +1 -0
  88. package/lib/packlets/badge/index.js +27 -0
  89. package/lib/packlets/badge/index.js.map +1 -0
  90. package/lib/packlets/badge/renderStatusBadge.d.ts +11 -0
  91. package/lib/packlets/badge/renderStatusBadge.d.ts.map +1 -0
  92. package/lib/packlets/badge/renderStatusBadge.js +50 -0
  93. package/lib/packlets/badge/renderStatusBadge.js.map +1 -0
  94. package/lib/packlets/cascade/CascadeContainer.d.ts.map +1 -0
  95. package/lib/packlets/cascade/CascadeContainer.js.map +1 -0
  96. package/lib/packlets/cascade/ComparisonView.d.ts.map +1 -0
  97. package/lib/packlets/cascade/ComparisonView.js.map +1 -0
  98. package/lib/packlets/cascade/EntityTabLayout.d.ts.map +1 -0
  99. package/lib/packlets/cascade/EntityTabLayout.js.map +1 -0
  100. package/lib/packlets/cascade/MobileCascadeStack.d.ts.map +1 -0
  101. package/lib/packlets/cascade/MobileCascadeStack.js.map +1 -0
  102. package/lib/packlets/cascade/index.d.ts.map +1 -0
  103. package/lib/packlets/cascade/index.js.map +1 -0
  104. package/lib/packlets/cascade/model.d.ts.map +1 -0
  105. package/lib/packlets/cascade/model.js.map +1 -0
  106. package/lib/packlets/cascade/useCascadeOps.d.ts.map +1 -0
  107. package/lib/packlets/cascade/useCascadeOps.js.map +1 -0
  108. package/lib/packlets/cascade/useCascadeTransitions.d.ts.map +1 -0
  109. package/lib/packlets/cascade/useCascadeTransitions.js.map +1 -0
  110. package/lib/packlets/detail/DetailHelpers.d.ts.map +1 -0
  111. package/lib/packlets/detail/DetailHelpers.js.map +1 -0
  112. package/lib/packlets/detail/index.d.ts.map +1 -0
  113. package/lib/packlets/detail/index.js.map +1 -0
  114. package/lib/packlets/drop-zone/JsonDropZone.d.ts.map +1 -0
  115. package/lib/packlets/drop-zone/JsonDropZone.js.map +1 -0
  116. package/lib/packlets/drop-zone/index.d.ts.map +1 -0
  117. package/lib/packlets/drop-zone/index.js.map +1 -0
  118. package/lib/packlets/editing/EditFieldHelpers.d.ts +6 -2
  119. package/lib/packlets/editing/EditFieldHelpers.d.ts.map +1 -0
  120. package/lib/packlets/editing/EditFieldHelpers.js +4 -4
  121. package/lib/packlets/editing/EditFieldHelpers.js.map +1 -0
  122. package/lib/packlets/editing/MultiActionButton.d.ts.map +1 -0
  123. package/lib/packlets/editing/MultiActionButton.js.map +1 -0
  124. package/lib/packlets/editing/NumericInput.d.ts.map +1 -0
  125. package/lib/packlets/editing/NumericInput.js.map +1 -0
  126. package/lib/packlets/editing/TypeaheadInput.d.ts.map +1 -0
  127. package/lib/packlets/editing/TypeaheadInput.js.map +1 -0
  128. package/lib/packlets/editing/index.d.ts.map +1 -0
  129. package/lib/packlets/editing/index.js.map +1 -0
  130. package/lib/packlets/editing/useTypeaheadMatch.d.ts.map +1 -0
  131. package/lib/packlets/editing/useTypeaheadMatch.js.map +1 -0
  132. package/lib/packlets/keyboard/index.d.ts.map +1 -0
  133. package/lib/packlets/keyboard/index.js.map +1 -0
  134. package/lib/packlets/keyboard/registry.d.ts.map +1 -0
  135. package/lib/packlets/keyboard/registry.js.map +1 -0
  136. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts.map +1 -0
  137. package/lib/packlets/keyboard/useKeyboardShortcuts.js.map +1 -0
  138. package/lib/packlets/messages/MessagesContext.d.ts +47 -3
  139. package/lib/packlets/messages/MessagesContext.d.ts.map +1 -0
  140. package/lib/packlets/messages/MessagesContext.js +79 -2
  141. package/lib/packlets/messages/MessagesContext.js.map +1 -0
  142. package/lib/packlets/messages/MessagesLogger.d.ts +8 -3
  143. package/lib/packlets/messages/MessagesLogger.d.ts.map +1 -0
  144. package/lib/packlets/messages/MessagesLogger.js +10 -31
  145. package/lib/packlets/messages/MessagesLogger.js.map +1 -0
  146. package/lib/packlets/messages/StatusBar.d.ts +10 -1
  147. package/lib/packlets/messages/StatusBar.d.ts.map +1 -0
  148. package/lib/packlets/messages/StatusBar.js +52 -27
  149. package/lib/packlets/messages/StatusBar.js.map +1 -0
  150. package/lib/packlets/messages/Toast.d.ts.map +1 -0
  151. package/lib/packlets/messages/Toast.js +6 -3
  152. package/lib/packlets/messages/Toast.js.map +1 -0
  153. package/lib/packlets/messages/index.d.ts +2 -2
  154. package/lib/packlets/messages/index.d.ts.map +1 -0
  155. package/lib/packlets/messages/index.js +6 -1
  156. package/lib/packlets/messages/index.js.map +1 -0
  157. package/lib/packlets/messages/model.d.ts +74 -8
  158. package/lib/packlets/messages/model.d.ts.map +1 -0
  159. package/lib/packlets/messages/model.js +32 -4
  160. package/lib/packlets/messages/model.js.map +1 -0
  161. package/lib/packlets/messages/useLogReporter.d.ts.map +1 -0
  162. package/lib/packlets/messages/useLogReporter.js.map +1 -0
  163. package/lib/packlets/modal/ConfirmDialog.d.ts.map +1 -0
  164. package/lib/packlets/modal/ConfirmDialog.js.map +1 -0
  165. package/lib/packlets/modal/Modal.d.ts.map +1 -0
  166. package/lib/packlets/modal/Modal.js.map +1 -0
  167. package/lib/packlets/modal/index.d.ts.map +1 -0
  168. package/lib/packlets/modal/index.js.map +1 -0
  169. package/lib/packlets/print/PrintEnclosure.d.ts.map +1 -0
  170. package/lib/packlets/print/PrintEnclosure.js.map +1 -0
  171. package/lib/packlets/print/index.d.ts.map +1 -0
  172. package/lib/packlets/print/index.js.map +1 -0
  173. package/lib/packlets/print/openPrintWindow.d.ts.map +1 -0
  174. package/lib/packlets/print/openPrintWindow.js.map +1 -0
  175. package/lib/packlets/responsive/ResponsiveProvider.d.ts.map +1 -0
  176. package/lib/packlets/responsive/ResponsiveProvider.js.map +1 -0
  177. package/lib/packlets/responsive/index.d.ts.map +1 -0
  178. package/lib/packlets/responsive/index.js.map +1 -0
  179. package/lib/packlets/responsive/useResponsiveLayout.d.ts.map +1 -0
  180. package/lib/packlets/responsive/useResponsiveLayout.js.map +1 -0
  181. package/lib/packlets/selectors/EntityRow.d.ts.map +1 -0
  182. package/lib/packlets/selectors/EntityRow.js.map +1 -0
  183. package/lib/packlets/selectors/PreferredSelector.d.ts.map +1 -0
  184. package/lib/packlets/selectors/PreferredSelector.js.map +1 -0
  185. package/lib/packlets/selectors/index.d.ts.map +1 -0
  186. package/lib/packlets/selectors/index.js.map +1 -0
  187. package/lib/packlets/sidebar/CollectionSection.d.ts +40 -2
  188. package/lib/packlets/sidebar/CollectionSection.d.ts.map +1 -0
  189. package/lib/packlets/sidebar/CollectionSection.js +157 -42
  190. package/lib/packlets/sidebar/CollectionSection.js.map +1 -0
  191. package/lib/packlets/sidebar/EntityList.d.ts.map +1 -0
  192. package/lib/packlets/sidebar/EntityList.js.map +1 -0
  193. package/lib/packlets/sidebar/FilterBar.d.ts.map +1 -0
  194. package/lib/packlets/sidebar/FilterBar.js.map +1 -0
  195. package/lib/packlets/sidebar/FilterRow.d.ts.map +1 -0
  196. package/lib/packlets/sidebar/FilterRow.js.map +1 -0
  197. package/lib/packlets/sidebar/GroupedEntityList.d.ts.map +1 -0
  198. package/lib/packlets/sidebar/GroupedEntityList.js.map +1 -0
  199. package/lib/packlets/sidebar/SearchBar.d.ts.map +1 -0
  200. package/lib/packlets/sidebar/SearchBar.js.map +1 -0
  201. package/lib/packlets/sidebar/SidebarLayout.d.ts.map +1 -0
  202. package/lib/packlets/sidebar/SidebarLayout.js.map +1 -0
  203. package/lib/packlets/sidebar/index.d.ts +1 -1
  204. package/lib/packlets/sidebar/index.d.ts.map +1 -0
  205. package/lib/packlets/sidebar/index.js.map +1 -0
  206. package/lib/packlets/theme/ThemeProvider.d.ts.map +1 -0
  207. package/lib/packlets/theme/ThemeProvider.js.map +1 -0
  208. package/lib/packlets/theme/index.d.ts.map +1 -0
  209. package/lib/packlets/theme/index.js.map +1 -0
  210. package/lib/packlets/top-bar/ModeSelector.d.ts.map +1 -0
  211. package/lib/packlets/top-bar/ModeSelector.js.map +1 -0
  212. package/lib/packlets/top-bar/TabBar.d.ts +12 -0
  213. package/lib/packlets/top-bar/TabBar.d.ts.map +1 -0
  214. package/lib/packlets/top-bar/TabBar.js +4 -1
  215. package/lib/packlets/top-bar/TabBar.js.map +1 -0
  216. package/lib/packlets/top-bar/index.d.ts +1 -1
  217. package/lib/packlets/top-bar/index.d.ts.map +1 -0
  218. package/lib/packlets/top-bar/index.js.map +1 -0
  219. package/lib/packlets/url-sync/index.d.ts.map +1 -0
  220. package/lib/packlets/url-sync/index.js.map +1 -0
  221. package/lib/packlets/url-sync/useUrlSync.d.ts.map +1 -0
  222. package/lib/packlets/url-sync/useUrlSync.js.map +1 -0
  223. package/lib/styles/theme.css +194 -0
  224. package/package.json +11 -7
  225. package/tailwind-preset.cjs +102 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditFieldHelpers.js","sourceRoot":"","sources":["../../../src/packlets/editing/EditFieldHelpers.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Generic edit field helper components for entity editors.\n *\n * Provides reusable form field primitives: text inputs, number inputs,\n * select dropdowns, tag editors, checkboxes, and layout wrappers.\n * These components are domain-agnostic and can be used by any entity editor.\n *\n * @packageDocumentation\n */\n\nimport React from 'react';\n\n// ============================================================================\n// Layout Helpers\n// ============================================================================\n\n/**\n * Props for the EditField component.\n * @public\n */\nexport interface IEditFieldProps {\n /** Label text displayed to the left of the field */\n readonly label: string;\n /** Optional tooltip shown on hover over the label */\n readonly tooltip?: string;\n /** The input control(s) to render */\n readonly children: React.ReactNode;\n}\n\n/**\n * Horizontal label + field layout for a single edit field.\n * @public\n */\nexport function EditField({ label, tooltip, children }: IEditFieldProps): React.ReactElement {\n return (\n <div className=\"flex items-baseline gap-2 py-1\">\n <label className=\"text-xs text-muted w-32 shrink-0\" title={tooltip}>\n {label}\n </label>\n <div className=\"flex-1\">{children}</div>\n </div>\n );\n}\n\n/**\n * Props for the EditSection component.\n * @public\n */\nexport interface IEditSectionProps {\n /** Section heading text */\n readonly title: string;\n /** Optional tooltip shown on hover over the section heading */\n readonly tooltip?: string;\n /** Section content (typically EditField components) */\n readonly children: React.ReactNode;\n}\n\n/**\n * Titled section wrapper for grouping related edit fields.\n * @public\n */\nexport function EditSection({ title, tooltip, children }: IEditSectionProps): React.ReactElement {\n return (\n <div className=\"mb-4\">\n <h4 className=\"text-xs font-medium text-muted uppercase tracking-wider mb-1.5\" title={tooltip}>\n {title}\n </h4>\n {children}\n </div>\n );\n}\n\n// ============================================================================\n// Text Inputs\n// ============================================================================\n\n/**\n * Props for the TextInput component.\n * @public\n */\nexport interface ITextInputProps {\n /** Current value */\n readonly value: string;\n /** Called when value changes */\n readonly onChange: (value: string) => void;\n /** Placeholder text */\n readonly placeholder?: string;\n}\n\n/**\n * Single-line text input for required string fields.\n * @public\n */\nexport function TextInput({ value, onChange, placeholder }: ITextInputProps): React.ReactElement {\n return (\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n className=\"w-full px-2 py-1 text-sm border border-border rounded focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring\"\n />\n );\n}\n\n/**\n * Props for the OptionalTextInput component.\n * @public\n */\nexport interface IOptionalTextInputProps {\n /** Current value (undefined means empty) */\n readonly value: string | undefined;\n /** Called when value changes (empty string → undefined) */\n readonly onChange: (value: string | undefined) => void;\n /** Placeholder text */\n readonly placeholder?: string;\n}\n\n/**\n * Single-line text input for optional string fields.\n * Empty input produces `undefined`.\n * @public\n */\nexport function OptionalTextInput({\n value,\n onChange,\n placeholder\n}: IOptionalTextInputProps): React.ReactElement {\n return (\n <input\n type=\"text\"\n value={value ?? ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n placeholder={placeholder}\n className=\"w-full px-2 py-1 text-sm border border-border rounded focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring\"\n />\n );\n}\n\n/**\n * Props for the TextAreaInput component.\n * @public\n */\nexport interface ITextAreaInputProps {\n /** Current value (undefined means empty) */\n readonly value: string | undefined;\n /** Called when value changes (empty string → undefined) */\n readonly onChange: (value: string | undefined) => void;\n /** Placeholder text */\n readonly placeholder?: string;\n}\n\n/**\n * Multi-line text input for optional string fields.\n * Empty input produces `undefined`.\n * @public\n */\nexport function TextAreaInput({ value, onChange, placeholder }: ITextAreaInputProps): React.ReactElement {\n return (\n <textarea\n value={value ?? ''}\n onChange={(e) => onChange(e.target.value || undefined)}\n placeholder={placeholder}\n rows={3}\n className=\"w-full px-2 py-1 text-sm border border-border rounded focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring resize-y\"\n />\n );\n}\n\n// ============================================================================\n// Number Input\n// ============================================================================\n\n/**\n * Props for the NumberInput component.\n * @public\n */\nexport interface INumberInputProps {\n /** Current value (undefined means empty) */\n readonly value: number | undefined;\n /** Called when value changes (empty input → undefined) */\n readonly onChange: (value: number | undefined) => void;\n /** Accessible label for the input */\n readonly label: string;\n /** Minimum allowed value */\n readonly min?: number;\n /** Maximum allowed value */\n readonly max?: number;\n /** Step increment */\n readonly step?: number;\n}\n\n/**\n * Numeric input for optional number fields.\n * Empty input produces `undefined`.\n * @public\n */\nexport function NumberInput({\n value,\n onChange,\n label,\n min,\n max,\n step\n}: INumberInputProps): React.ReactElement {\n return (\n <input\n type=\"number\"\n value={value ?? ''}\n onChange={(e) => {\n const raw = e.target.value;\n if (!raw.trim()) {\n onChange(undefined);\n } else {\n const num = parseFloat(raw);\n if (!isNaN(num)) {\n onChange(num);\n }\n }\n }}\n min={min}\n max={max}\n step={step ?? 0.1}\n className=\"w-24 px-2 py-1 text-sm border border-border rounded focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring text-right\"\n aria-label={label}\n />\n );\n}\n\n// ============================================================================\n// Select Input\n// ============================================================================\n\n/**\n * Props for the SelectInput component.\n * @public\n */\nexport interface ISelectInputProps<T extends string> {\n /** Currently selected value */\n readonly value: T;\n /** Available options */\n readonly options: ReadonlyArray<T>;\n /** Called when selection changes */\n readonly onChange: (value: T) => void;\n}\n\n/**\n * Dropdown select for enumerated string values.\n * @public\n */\nexport function SelectInput<T extends string>({\n value,\n options,\n onChange\n}: ISelectInputProps<T>): React.ReactElement {\n return (\n <select\n value={value}\n onChange={(e) => onChange(e.target.value as T)}\n className=\"px-2 py-1 text-sm border border-border rounded focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring\"\n >\n {options.map((opt) => (\n <option key={opt} value={opt}>\n {opt}\n </option>\n ))}\n </select>\n );\n}\n\n// ============================================================================\n// Tags Input\n// ============================================================================\n\n/**\n * Props for the TagsInput component.\n * @public\n */\nexport interface ITagsInputProps {\n /** Current tags (undefined means no tags) */\n readonly value: ReadonlyArray<string> | undefined;\n /** Called when tags change (empty → undefined) */\n readonly onChange: (value: ReadonlyArray<string> | undefined) => void;\n /** Placeholder text */\n readonly placeholder?: string;\n}\n\n/**\n * Comma-separated tag editor for string arrays.\n * Empty input produces `undefined`.\n * @public\n */\nexport function TagsInput({ value, onChange, placeholder }: ITagsInputProps): React.ReactElement {\n const text = value?.join(', ') ?? '';\n return (\n <input\n type=\"text\"\n value={text}\n onChange={(e) => {\n const raw = e.target.value;\n if (!raw.trim()) {\n onChange(undefined);\n } else {\n onChange(\n raw\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n );\n }\n }}\n placeholder={placeholder ?? 'comma-separated values'}\n className=\"w-full px-2 py-1 text-sm border border-border rounded focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring\"\n />\n );\n}\n\n// ============================================================================\n// Checkbox Input\n// ============================================================================\n\n/**\n * Props for the CheckboxInput component.\n * @public\n */\nexport interface ICheckboxInputProps {\n /** Current value (undefined treated as false) */\n readonly value: boolean | undefined;\n /** Called when value changes (false → undefined) */\n readonly onChange: (value: boolean | undefined) => void;\n /** Label text displayed next to the checkbox */\n readonly label: string;\n}\n\n/**\n * Checkbox input for optional boolean fields.\n * Unchecked produces `undefined`.\n * @public\n */\nexport function CheckboxInput({ value, onChange, label }: ICheckboxInputProps): React.ReactElement {\n return (\n <label className=\"flex items-center gap-2 text-sm text-secondary cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={value ?? false}\n onChange={(e) => onChange(e.target.checked || undefined)}\n className=\"rounded border-border text-brand-primary focus:ring-focus-ring\"\n />\n {label}\n </label>\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiActionButton.d.ts","sourceRoot":"","sources":["../../../src/packlets/editing/MultiActionButton.tsx"],"names":[],"mappings":"AAsBA;;;GAGG;AAEH,OAAO,KAAsC,MAAM,OAAO,CAAC;AAO3D;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,wCAAwC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,QAAQ,CAAC,aAAa,EAAE,wBAAwB,CAAC;IACjD,sDAAsD;IACtD,QAAQ,CAAC,kBAAkB,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACjE,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACpD,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,GAAG,KAAK,CAAC,YAAY,CAoFpF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MultiActionButton.js","sourceRoot":"","sources":["../../../src/packlets/editing/MultiActionButton.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Multi-action button component with dropdown for alternative actions.\n * @packageDocumentation\n */\n\nimport React, { useState, useRef, useEffect } from 'react';\nimport { ChevronDownIcon } from '@heroicons/react/20/solid';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Action definition for multi-action button.\n * @public\n */\nexport interface IMultiActionButtonAction {\n /** Unique identifier for the action. */\n readonly id: string;\n /** Display label for the action. */\n readonly label: string;\n /** Optional icon element to display before the label. */\n readonly icon?: React.ReactNode;\n /** Callback when this action is selected. */\n readonly onSelect: () => void;\n}\n\n/**\n * Props for MultiActionButton component.\n * @public\n */\nexport interface IMultiActionButtonProps {\n /** Primary action that appears on the main button. */\n readonly primaryAction: IMultiActionButtonAction;\n /** Alternative actions shown in the dropdown menu. */\n readonly alternativeActions: readonly IMultiActionButtonAction[];\n /** Optional variant for button styling. */\n readonly variant?: 'primary' | 'default' | 'danger';\n /** If true, the primary action button is disabled. */\n readonly disabled?: boolean;\n /** If true, the dropdown toggle is disabled. Defaults to `disabled`. */\n readonly dropdownDisabled?: boolean;\n /** Optional CSS class name. */\n readonly className?: string;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Multi-action button with dropdown for alternative actions.\n * Similar to Git commit button with \"Commit\", \"Commit & Push\", etc.\n *\n * @public\n */\nexport function MultiActionButton(props: IMultiActionButtonProps): React.ReactElement {\n const {\n primaryAction,\n alternativeActions,\n variant = 'primary',\n disabled = false,\n dropdownDisabled,\n className = ''\n } = props;\n const isDropdownDisabled = dropdownDisabled ?? disabled;\n const [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent): void => {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n if (isOpen) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [isOpen]);\n\n const variantClasses: Record<string, string> = {\n primary: 'bg-brand-primary hover:bg-brand-primary/90 text-white',\n default: 'bg-surface-raised hover:bg-surface-raised text-secondary',\n danger: 'bg-status-error-btn hover:bg-status-error-btn-hover text-white'\n };\n\n const baseClasses =\n 'inline-flex items-center text-xs font-medium rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed';\n\n return (\n <div className={`relative inline-flex ${className}`} ref={dropdownRef}>\n {/* Primary action button */}\n <button\n type=\"button\"\n onClick={primaryAction.onSelect}\n disabled={disabled}\n className={`${baseClasses} ${variantClasses[variant]} px-2.5 py-1 ${\n alternativeActions.length > 0 ? 'rounded-r-none' : 'rounded'\n }`}\n >\n {primaryAction.icon}\n <span>{primaryAction.label}</span>\n </button>\n\n {/* Dropdown toggle button — only shown when there are alternative actions */}\n {alternativeActions.length > 0 && (\n <button\n type=\"button\"\n onClick={(): void => setIsOpen(!isOpen)}\n disabled={isDropdownDisabled}\n className={`${baseClasses} ${variantClasses[variant]} px-1 py-1 rounded-l-none border-l border-white/20`}\n >\n <ChevronDownIcon className=\"h-3.5 w-3.5\" />\n </button>\n )}\n\n {/* Dropdown menu */}\n {isOpen && alternativeActions.length > 0 && (\n <div className=\"absolute top-full right-0 mt-1 w-48 bg-surface rounded-md shadow-lg border border-border py-1 z-50\">\n {alternativeActions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n onClick={(): void => {\n action.onSelect();\n setIsOpen(false);\n }}\n className=\"w-full text-left px-3 py-2 text-xs text-secondary hover:bg-surface-raised flex items-center gap-2\"\n >\n {action.icon}\n <span>{action.label}</span>\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NumericInput.d.ts","sourceRoot":"","sources":["../../../src/packlets/editing/NumericInput.tsx"],"names":[],"mappings":"AAsBA;;;;;;;;GAQG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAexE;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACvD,+CAA+C;IAC/C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qCAAqC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,sBAAsB;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAyBD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,KAAK,CAAC,YAAY,CA8E1E"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NumericInput.js","sourceRoot":"","sources":["../../../src/packlets/editing/NumericInput.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Numeric input with select-all-on-focus and empty-string support.\n *\n * Uses `type=\"text\"` with `inputMode=\"decimal\"` to avoid native number-input\n * quirks (spinner buttons, empty-string rejection). Manages a string internally\n * but exposes `number | undefined` to callers.\n *\n * @packageDocumentation\n */\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst NUMERIC_PATTERN: RegExp = /^-?\\d*\\.?\\d*$/;\n\nconst DEFAULT_CLASS: string =\n 'text-sm border border-border rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-focus-ring focus:border-focus-ring';\n\n// ============================================================================\n// Props\n// ============================================================================\n\n/**\n * Props for the {@link NumericInput} component.\n * @public\n */\nexport interface INumericInputProps {\n /** Current numeric value (`undefined` = empty field). */\n readonly value: number | undefined;\n /** Called with the parsed number on blur, or `undefined` if the field is empty. */\n readonly onChange: (value: number | undefined) => void;\n /** Accessible label (maps to `aria-label`). */\n readonly label?: string;\n /** Minimum allowed value (clamped on blur). */\n readonly min?: number;\n /** Maximum allowed value (clamped on blur). */\n readonly max?: number;\n /** Step for arrow-key increment/decrement. Defaults to 1. */\n readonly step?: number;\n /** Additional or replacement CSS classes. */\n readonly className?: string;\n /** Placeholder text shown when the field is empty. */\n readonly placeholder?: string;\n /** Auto-focus the input on mount. */\n readonly autoFocus?: boolean;\n /** Disabled state. */\n readonly disabled?: boolean;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction formatValue(value: number | undefined): string {\n return value !== undefined ? String(value) : '';\n}\n\nfunction clamp(value: number, min: number | undefined, max: number | undefined): number {\n let result = value;\n if (min !== undefined && result < min) {\n result = min;\n }\n if (max !== undefined && result > max) {\n result = max;\n }\n return result;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Numeric input that selects all on focus and allows empty values.\n *\n * Internally manages a string so the user can freely type, backspace to empty,\n * and replace the entire value. On blur, the string is parsed to a number\n * (clamped to min/max) and reported to the parent via `onChange`.\n *\n * @public\n */\nexport function NumericInput(props: INumericInputProps): React.ReactElement {\n const { value, onChange, label, min, max, step, className, placeholder, autoFocus, disabled } = props;\n\n const [displayValue, setDisplayValue] = useState(() => formatValue(value));\n const focusedRef = useRef(false);\n\n // Sync display when value prop changes externally (but not while focused)\n useEffect(() => {\n if (!focusedRef.current) {\n setDisplayValue(formatValue(value));\n }\n }, [value]);\n\n const handleFocus = useCallback((e: React.FocusEvent<HTMLInputElement>): void => {\n focusedRef.current = true;\n e.target.select();\n }, []);\n\n const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>): void => {\n const raw = e.target.value;\n if (raw === '' || NUMERIC_PATTERN.test(raw)) {\n setDisplayValue(raw);\n }\n }, []);\n\n const handleBlur = useCallback((): void => {\n focusedRef.current = false;\n const trimmed = displayValue.trim();\n if (trimmed === '' || trimmed === '-' || trimmed === '.') {\n setDisplayValue('');\n onChange(undefined);\n return;\n }\n const parsed = parseFloat(trimmed);\n if (isNaN(parsed)) {\n setDisplayValue('');\n onChange(undefined);\n return;\n }\n const clamped = clamp(parsed, min, max);\n setDisplayValue(String(clamped));\n onChange(clamped);\n }, [displayValue, onChange, min, max]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>): void => {\n if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') {\n return;\n }\n e.preventDefault();\n const effectiveStep = step ?? 1;\n const current = parseFloat(displayValue) || 0;\n const next = e.key === 'ArrowUp' ? current + effectiveStep : current - effectiveStep;\n const clamped = clamp(next, min, max);\n // Round to avoid floating-point drift\n const rounded = parseFloat(clamped.toFixed(10));\n setDisplayValue(String(rounded));\n onChange(rounded);\n },\n [displayValue, onChange, min, max, step]\n );\n\n return (\n <input\n type=\"text\"\n inputMode=\"decimal\"\n value={displayValue}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n className={className ?? DEFAULT_CLASS}\n aria-label={label}\n placeholder={placeholder}\n autoFocus={autoFocus}\n disabled={disabled}\n />\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypeaheadInput.d.ts","sourceRoot":"","sources":["../../../src/packlets/editing/TypeaheadInput.tsx"],"names":[],"mappings":"AAsBA;;;;GAIG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAAE,KAAK,oBAAoB,EAAqB,MAAM,qBAAqB,CAAC;AAMnF;;;GAGG;AACH,MAAM,WAAW,oBAAoB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM;IAC/D,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,0EAA0E;IAC1E,QAAQ,CAAC,mBAAmB,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,oBAAoB,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;IACnE,qFAAqF;IACrF,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,sCAAsC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAMD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EACxD,KAAK,EAAE,oBAAoB,CAAC,GAAG,CAAC,GAC/B,KAAK,CAAC,YAAY,CAiRpB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypeaheadInput.js","sourceRoot":"","sources":["../../../src/packlets/editing/TypeaheadInput.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * TypeaheadInput — text input with custom autocomplete dropdown supporting\n * tiered suggestions and built-in blur resolution.\n * @packageDocumentation\n */\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { type ITypeaheadSuggestion, useTypeaheadMatch } from './useTypeaheadMatch';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the TypeaheadInput component.\n * @public\n */\nexport interface ITypeaheadInputProps<TId extends string = string> {\n /** Current text value (controlled). */\n readonly value: string;\n /** Called on every keystroke. */\n readonly onChange: (value: string) => void;\n /** Full catalog of suggestions (lower priority). */\n readonly suggestions: ReadonlyArray<ITypeaheadSuggestion<TId>>;\n /** Optional priority suggestions shown first (e.g. recipe alternates). */\n readonly prioritySuggestions?: ReadonlyArray<ITypeaheadSuggestion<TId>>;\n /** Called when a suggestion is definitively selected (click, Enter, blur auto-resolve). */\n readonly onSelect: (suggestion: ITypeaheadSuggestion<TId>) => void;\n /** Called on blur/Enter/Tab when input cannot be resolved to a single suggestion. */\n readonly onUnresolved?: (text: string) => void;\n /** Placeholder text for the input. */\n readonly placeholder?: string;\n /** Additional CSS class for the input element. */\n readonly className?: string;\n /** If true, the input is disabled. */\n readonly disabled?: boolean;\n /** Maximum dropdown height in pixels. Default: 240. */\n readonly maxHeight?: number;\n /** If true, the input is focused on mount. */\n readonly autoFocus?: boolean;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Text input with a custom autocomplete dropdown supporting tiered suggestions.\n *\n * Priority suggestions (e.g. recipe alternates) appear first, visually separated\n * from the full catalog. On blur, applies resolution logic: exact match → auto-select,\n * single partial match → auto-select, else fires `onUnresolved`.\n *\n * @public\n */\nexport function TypeaheadInput<TId extends string = string>(\n props: ITypeaheadInputProps<TId>\n): React.ReactElement {\n const {\n value,\n onChange,\n suggestions,\n prioritySuggestions,\n onSelect,\n onUnresolved,\n placeholder,\n className,\n disabled,\n maxHeight = 240,\n autoFocus\n } = props;\n\n const [isOpen, setIsOpen] = useState(false);\n const [focusIndex, setFocusIndex] = useState(-1);\n const inputRef = useRef<HTMLInputElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);\n const mouseDownOnDropdownRef = useRef(false);\n\n const matcher = useTypeaheadMatch(suggestions, prioritySuggestions);\n\n // ---- Filtered suggestions ----\n\n const filtered = useMemo(() => matcher.filterSuggestions(value), [matcher, value]);\n\n const flatItems = useMemo(\n () => [...filtered.priority, ...filtered.catalog],\n [filtered.priority, filtered.catalog]\n );\n\n const hasPriorityItems = filtered.priority.length > 0;\n const hasCatalogItems = filtered.catalog.length > 0;\n const hasItems = flatItems.length > 0;\n\n // Reset focus index when suggestions change\n useEffect(() => {\n setFocusIndex(-1);\n }, [value]);\n\n // Scroll focused item into view\n useEffect(() => {\n if (isOpen && focusIndex >= 0 && itemRefs.current[focusIndex]) {\n itemRefs.current[focusIndex]?.scrollIntoView({ block: 'nearest' });\n }\n }, [isOpen, focusIndex]);\n\n // Close dropdown on outside click\n useEffect(() => {\n if (!isOpen) return;\n const handler = (e: MouseEvent): void => {\n if (\n dropdownRef.current &&\n !dropdownRef.current.contains(e.target as Node) &&\n inputRef.current &&\n !inputRef.current.contains(e.target as Node)\n ) {\n setIsOpen(false);\n }\n };\n document.addEventListener('mousedown', handler);\n return (): void => {\n document.removeEventListener('mousedown', handler);\n };\n }, [isOpen]);\n\n // ---- Selection ----\n\n const handleSelect = useCallback(\n (item: ITypeaheadSuggestion<TId>): void => {\n onSelect(item);\n setIsOpen(false);\n setFocusIndex(-1);\n },\n [onSelect]\n );\n\n const handleResolve = useCallback(\n (text: string): void => {\n const trimmed = text.trim();\n if (!trimmed) {\n setIsOpen(false);\n return;\n }\n const match = matcher.resolveOnBlur(trimmed);\n if (match) {\n handleSelect(match);\n } else if (onUnresolved) {\n onUnresolved(trimmed);\n setIsOpen(false);\n } else {\n setIsOpen(false);\n }\n },\n [matcher, handleSelect, onUnresolved]\n );\n\n // ---- Input handlers ----\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>): void => {\n onChange(e.target.value);\n if (!isOpen) {\n setIsOpen(true);\n }\n },\n [onChange, isOpen]\n );\n\n const handleFocus = useCallback((): void => {\n inputRef.current?.select();\n if (hasItems) {\n setIsOpen(true);\n }\n }, [hasItems]);\n\n const handleBlur = useCallback((): void => {\n // Guard: don't resolve if user is clicking an item in the dropdown\n if (mouseDownOnDropdownRef.current) {\n mouseDownOnDropdownRef.current = false;\n return;\n }\n handleResolve(value);\n }, [value, handleResolve]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent): void => {\n if (!isOpen) {\n if (e.key === 'ArrowDown' && hasItems) {\n e.preventDefault();\n setIsOpen(true);\n setFocusIndex(0);\n }\n return;\n }\n\n switch (e.key) {\n case 'Escape': {\n e.preventDefault();\n e.stopPropagation();\n setIsOpen(false);\n setFocusIndex(-1);\n break;\n }\n case 'ArrowDown': {\n e.preventDefault();\n setFocusIndex((prev) => (prev < flatItems.length - 1 ? prev + 1 : 0));\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n setFocusIndex((prev) => (prev > 0 ? prev - 1 : flatItems.length - 1));\n break;\n }\n case 'Enter': {\n e.preventDefault();\n if (focusIndex >= 0 && focusIndex < flatItems.length) {\n handleSelect(flatItems[focusIndex]);\n } else {\n handleResolve(value);\n }\n break;\n }\n case 'Tab': {\n // Allow default tab behavior but resolve first\n handleResolve(value);\n break;\n }\n default:\n break;\n }\n },\n [isOpen, hasItems, flatItems, focusIndex, handleSelect, handleResolve, value]\n );\n\n // ---- Dropdown mouse guard ----\n\n const handleDropdownMouseDown = useCallback((): void => {\n mouseDownOnDropdownRef.current = true;\n }, []);\n\n // ---- Render ----\n\n const priorityEndIndex = filtered.priority.length;\n\n return (\n <div className=\"relative\">\n <input\n ref={inputRef}\n type=\"text\"\n value={value}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled}\n autoFocus={autoFocus}\n className={\n className ??\n 'w-full px-2 py-1 text-sm border border-border rounded focus:outline-none focus:ring-1 focus:ring-focus-ring focus:border-focus-ring'\n }\n role=\"combobox\"\n aria-expanded={isOpen}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n />\n\n {isOpen && hasItems && (\n <div\n ref={dropdownRef}\n className=\"absolute z-50 mt-1 w-full bg-surface border border-border rounded-lg shadow-lg overflow-hidden\"\n style={{ maxHeight }}\n role=\"listbox\"\n onMouseDown={handleDropdownMouseDown}\n >\n <div className=\"overflow-y-auto\" style={{ maxHeight }}>\n {/* Priority items */}\n {hasPriorityItems &&\n filtered.priority.map((item, index) => {\n const isFocused = index === focusIndex;\n return (\n <button\n key={item.id}\n ref={(el): void => {\n itemRefs.current[index] = el;\n }}\n role=\"option\"\n aria-selected={isFocused}\n onClick={(): void => handleSelect(item)}\n className={`flex items-center w-full px-2.5 py-1.5 text-left text-sm transition-colors border-l-2 ${\n isFocused\n ? 'bg-surface-alt text-primary border-brand-primary/50'\n : 'text-secondary hover:bg-hover border-brand-primary/20'\n }`}\n >\n <span className=\"flex-1 min-w-0 truncate\">{item.name}</span>\n </button>\n );\n })}\n\n {/* Separator */}\n {hasPriorityItems && hasCatalogItems && <div className=\"border-t border-border my-0.5\" />}\n\n {/* Catalog items */}\n {hasCatalogItems &&\n filtered.catalog.map((item, catalogIndex) => {\n const flatIndex = priorityEndIndex + catalogIndex;\n const isFocused = flatIndex === focusIndex;\n return (\n <button\n key={item.id}\n ref={(el): void => {\n itemRefs.current[flatIndex] = el;\n }}\n role=\"option\"\n aria-selected={isFocused}\n onClick={(): void => handleSelect(item)}\n className={`flex items-center w-full px-2.5 py-1.5 text-left text-sm transition-colors ${\n isFocused ? 'bg-surface-alt text-primary' : 'text-secondary hover:bg-hover'\n }`}\n >\n <span className=\"flex-1 min-w-0 truncate\">{item.name}</span>\n </button>\n );\n })}\n </div>\n </div>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/editing/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,SAAS,EACT,KAAK,eAAe,EACpB,WAAW,EACX,KAAK,iBAAiB,EACtB,SAAS,EACT,KAAK,eAAe,EACpB,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,aAAa,EACb,KAAK,mBAAmB,EACxB,WAAW,EACX,KAAK,iBAAiB,EACtB,WAAW,EACX,KAAK,iBAAiB,EACtB,SAAS,EACT,KAAK,eAAe,EACpB,aAAa,EACb,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7E,OAAO,EACL,iBAAiB,EACjB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/editing/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,uDAmB4B;AAlB1B,6GAAA,SAAS,OAAA;AAET,+GAAA,WAAW,OAAA;AAEX,6GAAA,SAAS,OAAA;AAET,qHAAA,iBAAiB,OAAA;AAEjB,iHAAA,aAAa,OAAA;AAEb,+GAAA,WAAW,OAAA;AAEX,+GAAA,WAAW,OAAA;AAEX,6GAAA,SAAS,OAAA;AAET,iHAAA,aAAa,OAAA;AAIf,yDAI6B;AAH3B,sHAAA,iBAAiB,OAAA;AAKnB,+CAAuE;AAA9D,4GAAA,YAAY,OAAA;AAErB,mDAA6E;AAApE,gHAAA,cAAc,OAAA;AAEvB,yDAK6B;AAJ3B,sHAAA,iBAAiB,OAAA","sourcesContent":["/**\n * Editing packlet - generic form field primitives for entity editors.\n * @packageDocumentation\n */\n\nexport {\n EditField,\n type IEditFieldProps,\n EditSection,\n type IEditSectionProps,\n TextInput,\n type ITextInputProps,\n OptionalTextInput,\n type IOptionalTextInputProps,\n TextAreaInput,\n type ITextAreaInputProps,\n NumberInput,\n type INumberInputProps,\n SelectInput,\n type ISelectInputProps,\n TagsInput,\n type ITagsInputProps,\n CheckboxInput,\n type ICheckboxInputProps\n} from './EditFieldHelpers';\n\nexport {\n MultiActionButton,\n type IMultiActionButtonProps,\n type IMultiActionButtonAction\n} from './MultiActionButton';\n\nexport { NumericInput, type INumericInputProps } from './NumericInput';\n\nexport { TypeaheadInput, type ITypeaheadInputProps } from './TypeaheadInput';\n\nexport {\n useTypeaheadMatch,\n type ITypeaheadSuggestion,\n type ITypeaheadMatchResult,\n type IFilteredSuggestions\n} from './useTypeaheadMatch';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTypeaheadMatch.d.ts","sourceRoot":"","sources":["../../../src/packlets/editing/useTypeaheadMatch.ts"],"names":[],"mappings":"AAiCA;;;GAGG;AACH,MAAM,WAAW,oBAAoB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM;IAC/D,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB,CAAC,GAAG,SAAS,MAAM;IACtD,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;CAC5D;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB,CAAC,GAAG,SAAS,MAAM;IACvD,iFAAiF;IACjF,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,oBAAoB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACzE,sFAAsF;IACtF,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,oBAAoB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACxE,yFAAyF;IACzF,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,oBAAoB,CAAC,GAAG,CAAC,CAAC;CACjE;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,SAAS,MAAM,EAClD,WAAW,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,EACrD,mBAAmB,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAC7D,qBAAqB,CAAC,GAAG,CAAC,CAmF5B"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTypeaheadMatch.js","sourceRoot":"","sources":["../../../src/packlets/editing/useTypeaheadMatch.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;AA4DH,8CAsFC;AAhJD;;;GAGG;AAEH,iCAA6C;AAqC7C,+EAA+E;AAC/E,OAAO;AACP,+EAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,SAAgB,iBAAiB,CAC/B,WAAqD,EACrD,mBAA8D;IAE9D,+DAA+D;IAC/D,MAAM,WAAW,GAAG,IAAA,eAAO,EACzB,GAAG,EAAE,WAAC,OAAA,IAAI,GAAG,CAAC,MAAA,mBAAmB,aAAnB,mBAAmB,uBAAnB,mBAAmB,CAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,mCAAI,EAAE,CAAC,CAAA,EAAA,EAC1D,CAAC,mBAAmB,CAAC,CACtB,CAAC;IAEF,MAAM,cAAc,GAAG,IAAA,mBAAW,EAChC,CAAC,KAAa,EAAyC,EAAE;QACvD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;YAClG,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;QACD,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;IACrF,CAAC,EACD,CAAC,WAAW,EAAE,mBAAmB,CAAC,CACnC,CAAC;IAEF,MAAM,aAAa,GAAG,IAAA,mBAAW,EAC/B,CAAC,KAAa,EAAyC,EAAE;QACvD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QAEpC,6CAA6C;QAC7C,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,CAC5C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAC1D,CAAC;YACF,IAAI,aAAa;gBAAE,OAAO,aAAa,CAAC;QAC1C,CAAC;QAED,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;QAC1F,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QAExB,kDAAkD;QAClD,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,MAAM,CACjD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAClF,CAAC;YACF,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;QAED,gDAAgD;QAChD,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAClF,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;QAE9C,OAAO,SAAS,CAAC;IACnB,CAAC,EACD,CAAC,WAAW,EAAE,mBAAmB,CAAC,CACnC,CAAC;IAEF,MAAM,iBAAiB,GAAG,IAAA,mBAAW,EACnC,CAAC,KAAa,EAA6B,EAAE;;QAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QAEpC,kDAAkD;QAClD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,QAAQ,EAAE,mBAAmB,aAAnB,mBAAmB,cAAnB,mBAAmB,GAAI,EAAE;gBACnC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;aAC3D,CAAC;QACJ,CAAC;QAED,4BAA4B;QAC5B,MAAM,iBAAiB,GAAG,CAAC,CAA4B,EAAW,EAAE,CAClE,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAE7E,MAAM,QAAQ,GAAG,MAAA,mBAAmB,aAAnB,mBAAmB,uBAAnB,mBAAmB,CAAE,MAAM,CAAC,iBAAiB,CAAC,mCAAI,EAAE,CAAC;QACtE,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;QAE1F,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC/B,CAAC,EACD,CAAC,WAAW,EAAE,mBAAmB,EAAE,WAAW,CAAC,CAChD,CAAC;IAEF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,CAAC;AAC9D,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Reusable typeahead matching hook with tiered priority support.\n * @packageDocumentation\n */\n\nimport { useCallback, useMemo } from 'react';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A suggestion entry for typeahead matching.\n * @public\n */\nexport interface ITypeaheadSuggestion<TId extends string = string> {\n readonly id: TId;\n readonly name: string;\n}\n\n/**\n * Filtered suggestions split by tier.\n * @public\n */\nexport interface IFilteredSuggestions<TId extends string> {\n readonly priority: ReadonlyArray<ITypeaheadSuggestion<TId>>;\n readonly catalog: ReadonlyArray<ITypeaheadSuggestion<TId>>;\n}\n\n/**\n * Result of the useTypeaheadMatch hook.\n * @public\n */\nexport interface ITypeaheadMatchResult<TId extends string> {\n /** Find an exact match by id or case-insensitive name. Checks priority first. */\n findExactMatch: (input: string) => ITypeaheadSuggestion<TId> | undefined;\n /** Resolve on blur: exact match → use it; single partial → use it; else undefined. */\n resolveOnBlur: (input: string) => ITypeaheadSuggestion<TId> | undefined;\n /** Filter suggestions by input text, returning priority and catalog tiers separately. */\n filterSuggestions: (input: string) => IFilteredSuggestions<TId>;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Hook that provides typeahead matching and filtering with tiered priority support.\n *\n * `findExactMatch` matches by exact id or case-insensitive name (checks priority first).\n * `resolveOnBlur` tries exact match, then single partial match (priority first).\n * `filterSuggestions` returns suggestions split by tier, filtered by substring match.\n *\n * @param suggestions - The full catalog of suggestions\n * @param prioritySuggestions - Optional priority suggestions shown first (e.g. recipe alternates)\n * @returns Match and filter functions\n * @public\n */\nexport function useTypeaheadMatch<TId extends string>(\n suggestions: ReadonlyArray<ITypeaheadSuggestion<TId>>,\n prioritySuggestions?: ReadonlyArray<ITypeaheadSuggestion<TId>>\n): ITypeaheadMatchResult<TId> {\n // Pre-compute priority IDs for efficient catalog deduplication\n const priorityIds = useMemo(\n () => new Set(prioritySuggestions?.map((s) => s.id) ?? []),\n [prioritySuggestions]\n );\n\n const findExactMatch = useCallback(\n (input: string): ITypeaheadSuggestion<TId> | undefined => {\n const trimmed = input.trim();\n if (!trimmed) return undefined;\n const lower = trimmed.toLowerCase();\n if (prioritySuggestions) {\n const match = prioritySuggestions.find((s) => s.id === trimmed || s.name.toLowerCase() === lower);\n if (match) return match;\n }\n return suggestions.find((s) => s.id === trimmed || s.name.toLowerCase() === lower);\n },\n [suggestions, prioritySuggestions]\n );\n\n const resolveOnBlur = useCallback(\n (input: string): ITypeaheadSuggestion<TId> | undefined => {\n const trimmed = input.trim();\n if (!trimmed) return undefined;\n const lower = trimmed.toLowerCase();\n\n // Try exact match (priority first, then all)\n if (prioritySuggestions) {\n const priorityExact = prioritySuggestions.find(\n (s) => s.id === trimmed || s.name.toLowerCase() === lower\n );\n if (priorityExact) return priorityExact;\n }\n\n const exact = suggestions.find((s) => s.id === trimmed || s.name.toLowerCase() === lower);\n if (exact) return exact;\n\n // Try partial match in priority suggestions first\n if (prioritySuggestions) {\n const priorityPartials = prioritySuggestions.filter(\n (s) => s.name.toLowerCase().includes(lower) || s.id.toLowerCase().includes(lower)\n );\n if (priorityPartials.length === 1) return priorityPartials[0];\n }\n\n // Fall back to partial match in all suggestions\n const partials = suggestions.filter(\n (s) => s.name.toLowerCase().includes(lower) || s.id.toLowerCase().includes(lower)\n );\n if (partials.length === 1) return partials[0];\n\n return undefined;\n },\n [suggestions, prioritySuggestions]\n );\n\n const filterSuggestions = useCallback(\n (input: string): IFilteredSuggestions<TId> => {\n const trimmed = input.trim();\n const lower = trimmed.toLowerCase();\n\n // No input — return all suggestions split by tier\n if (!trimmed) {\n return {\n priority: prioritySuggestions ?? [],\n catalog: suggestions.filter((s) => !priorityIds.has(s.id))\n };\n }\n\n // Filter by substring match\n const matchesSuggestion = (s: ITypeaheadSuggestion<TId>): boolean =>\n s.name.toLowerCase().includes(lower) || s.id.toLowerCase().includes(lower);\n\n const priority = prioritySuggestions?.filter(matchesSuggestion) ?? [];\n const catalog = suggestions.filter((s) => !priorityIds.has(s.id) && matchesSuggestion(s));\n\n return { priority, catalog };\n },\n [suggestions, prioritySuggestions, priorityIds]\n );\n\n return { findExactMatch, resolveOnBlur, filterSuggestions };\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/keyboard/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,qBAAqB,EAC1B,wBAAwB,EACxB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,KAAK,8BAA8B,EACnC,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/keyboard/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,uCAMoB;AAFlB,oHAAA,wBAAwB,OAAA;AACxB,0GAAA,cAAc,OAAA;AAGhB,+DAKgC;AAH9B,gIAAA,wBAAwB,OAAA;AACxB,2HAAA,mBAAmB,OAAA;AACnB,4HAAA,oBAAoB,OAAA","sourcesContent":["/**\n * Keyboard shortcut packlet - registry, context, and hooks.\n * @packageDocumentation\n */\n\nexport {\n type IKeyBinding,\n type IShortcut,\n type IShortcutRegistration,\n KeyboardShortcutRegistry,\n matchesBinding\n} from './registry';\n\nexport {\n type IKeyboardShortcutProviderProps,\n KeyboardShortcutProvider,\n useKeyboardRegistry,\n useKeyboardShortcuts\n} from './useKeyboardShortcuts';\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/packlets/keyboard/registry.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;GAUG;AAMH;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB;IACpB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,yBAAyB;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,sBAAsB;IACtB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACvC,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC;CACjC;AAMD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAMlF;AAMD;;;;;;;;GAQG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqC;IAChE,OAAO,CAAC,SAAS,CAA2C;IAC5D,OAAO,CAAC,OAAO,CAAa;IAE5B;;;;OAIG;IACI,QAAQ,CAAC,QAAQ,EAAE,SAAS,GAAG,qBAAqB;IAe3D;;OAEG;IACI,MAAM,IAAI,aAAa,CAAC,SAAS,CAAC;IAIzC;;;;OAIG;IACI,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO;IA2B9C;;OAEG;IACI,OAAO,IAAI,IAAI;IAKtB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,eAAe;CAMxB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/packlets/keyboard/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Keyboard shortcut registry infrastructure.\n *\n * Provides a centralized registry for keyboard shortcuts with:\n * - Modifier-aware key matching (Cmd/Ctrl, Shift, Alt)\n * - Priority-based handler resolution (higher priority wins)\n * - React hook for component-scoped shortcut registration\n * - Global listener that dispatches to registered handlers\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Describes a keyboard shortcut binding.\n * @public\n */\nexport interface IKeyBinding {\n /** The key (e.g., 'k', 'z', 'Escape'). Case-insensitive. */\n readonly key: string;\n /** Require Cmd (Mac) / Ctrl (Windows/Linux) */\n readonly meta?: boolean;\n /** Require Shift */\n readonly shift?: boolean;\n /** Require Alt/Option */\n readonly alt?: boolean;\n}\n\n/**\n * A registered keyboard shortcut.\n * @public\n */\nexport interface IShortcut {\n /** The key binding */\n readonly binding: IKeyBinding;\n /** Human-readable description (for command palette / help) */\n readonly description: string;\n /** Handler function. Return true to indicate the event was handled. */\n readonly handler: () => boolean | void;\n /** Priority (higher wins when multiple shortcuts match). Default: 0 */\n readonly priority?: number;\n}\n\n/**\n * A registration handle returned when a shortcut is registered.\n * Call `unregister()` to remove the shortcut.\n * @public\n */\nexport interface IShortcutRegistration {\n readonly unregister: () => void;\n}\n\n// ============================================================================\n// Key Matching\n// ============================================================================\n\n/**\n * Tests whether a keyboard event matches a key binding.\n * @param event - The keyboard event\n * @param binding - The binding to match against\n * @returns true if the event matches\n * @public\n */\nexport function matchesBinding(event: KeyboardEvent, binding: IKeyBinding): boolean {\n const keyMatch = event.key.toLowerCase() === binding.key.toLowerCase();\n const metaMatch = (binding.meta ?? false) === (event.metaKey || event.ctrlKey);\n const shiftMatch = (binding.shift ?? false) === event.shiftKey;\n const altMatch = (binding.alt ?? false) === event.altKey;\n return keyMatch && metaMatch && shiftMatch && altMatch;\n}\n\n// ============================================================================\n// Registry\n// ============================================================================\n\n/**\n * Centralized keyboard shortcut registry.\n *\n * Components register shortcuts via `register()` and receive a handle\n * to unregister when they unmount. The registry attaches a single global\n * keydown listener that dispatches to the highest-priority matching handler.\n *\n * @public\n */\nexport class KeyboardShortcutRegistry {\n private readonly _shortcuts: Map<string, IShortcut> = new Map();\n private _listener: ((e: KeyboardEvent) => void) | undefined;\n private _nextId: number = 0;\n\n /**\n * Registers a keyboard shortcut.\n * @param shortcut - The shortcut to register\n * @returns A registration handle with an `unregister()` method\n */\n public register(shortcut: IShortcut): IShortcutRegistration {\n const id = `ks-${++this._nextId}`;\n this._shortcuts.set(id, shortcut);\n this._ensureListener();\n\n return {\n unregister: (): void => {\n this._shortcuts.delete(id);\n if (this._shortcuts.size === 0) {\n this._removeListener();\n }\n }\n };\n }\n\n /**\n * Returns all currently registered shortcuts (for command palette / help display).\n */\n public getAll(): ReadonlyArray<IShortcut> {\n return Array.from(this._shortcuts.values());\n }\n\n /**\n * Dispatches a keyboard event to the highest-priority matching handler.\n * @param event - The keyboard event\n * @returns true if a handler consumed the event\n */\n public dispatch(event: KeyboardEvent): boolean {\n // Skip if the event target is an input/textarea/contenteditable\n const target = event.target as HTMLElement | null;\n if (target) {\n const tagName = target.tagName?.toLowerCase();\n if (tagName === 'input' || tagName === 'textarea' || target.isContentEditable) {\n return false;\n }\n }\n\n // Find all matching shortcuts, sorted by priority (highest first)\n const matches = Array.from(this._shortcuts.values())\n .filter((s) => matchesBinding(event, s.binding))\n .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));\n\n for (const shortcut of matches) {\n const result = shortcut.handler();\n if (result !== false) {\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Removes all shortcuts and the global listener.\n */\n public dispose(): void {\n this._shortcuts.clear();\n this._removeListener();\n }\n\n private _ensureListener(): void {\n if (this._listener) {\n return;\n }\n this._listener = (e: KeyboardEvent): void => {\n this.dispatch(e);\n };\n document.addEventListener('keydown', this._listener);\n }\n\n private _removeListener(): void {\n if (this._listener) {\n document.removeEventListener('keydown', this._listener);\n this._listener = undefined;\n }\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useKeyboardShortcuts.d.ts","sourceRoot":"","sources":["../../../src/packlets/keyboard/useKeyboardShortcuts.tsx"],"names":[],"mappings":"AAsBA;;;GAGG;AAEH,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,EAAE,wBAAwB,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AActE;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,8BAA8B,GAAG,KAAK,CAAC,YAAY,CAQlG;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,wBAAwB,CAM9D;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,IAAI,CAiC9E"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useKeyboardShortcuts.js","sourceRoot":"","sources":["../../../src/packlets/keyboard/useKeyboardShortcuts.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCH,4DAQC;AAWD,kDAMC;AAyBD,oDAiCC;AArHD;;;GAGG;AAEH,+CAAsE;AAEtE,yCAAsE;AAEtE,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,MAAM,eAAe,GAAwD,eAAK,CAAC,aAAa,CAE9F,SAAS,CAAC,CAAC;AAcb;;;;GAIG;AACH,SAAgB,wBAAwB,CAAC,KAAqC;IAC5E,MAAM,QAAQ,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,IAAI,mCAAwB,EAAE,EAAE,EAAE,CAAC,CAAC;IAEnE,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,OAAO,GAAS,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;IACxC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,OAAO,8BAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,IAAG,KAAK,CAAC,QAAQ,CAA4B,CAAC;AAChG,CAAC;AAED,+EAA+E;AAC/E,QAAQ;AACR,+EAA+E;AAE/E;;;;GAIG;AACH,SAAgB,mBAAmB;IACjC,MAAM,GAAG,GAAG,IAAA,kBAAU,EAAC,eAAe,CAAC,CAAC;IACxC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAgB,oBAAoB,CAAC,SAAmC;IACtE,MAAM,QAAQ,GAAG,mBAAmB,EAAE,CAAC;IACvC,MAAM,YAAY,GAAG,IAAA,cAAM,EAAC,SAAS,CAAC,CAAC;IACvC,YAAY,CAAC,OAAO,GAAG,SAAS,CAAC;IAEjC,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,8EAA8E;QAC9E,6EAA6E;QAC7E,2EAA2E;QAC3E,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAC/D,QAAQ,CAAC,QAAQ,CAAC;YAChB,IAAI,OAAO;;gBACT,OAAO,MAAA,MAAA,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,0CAAE,OAAO,mCAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;YAC7D,CAAC;YACD,IAAI,WAAW;;gBACb,OAAO,MAAA,MAAA,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,0CAAE,WAAW,mCAAI,EAAE,CAAC;YACxD,CAAC;YACD,IAAI,QAAQ;;gBACV,OAAO,MAAA,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,0CAAE,QAAQ,CAAC;YAC/C,CAAC;YACD,OAAO,EAAE,GAAmB,EAAE;;gBAC5B,OAAO,MAAA,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,0CAAE,OAAO,EAAE,CAAC;YAChD,CAAC;SACF,CAAC,CACH,CAAC;QACF,OAAO,GAAS,EAAE;YAChB,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;gBAChC,GAAG,CAAC,UAAU,EAAE,CAAC;YACnB,CAAC;QACH,CAAC,CAAC;QACF,kFAAkF;QAClF,6EAA6E;IAC/E,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * React context and hooks for the keyboard shortcut registry.\n * @packageDocumentation\n */\n\nimport React, { useContext, useEffect, useMemo, useRef } from 'react';\n\nimport { KeyboardShortcutRegistry, type IShortcut } from './registry';\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst KeyboardContext: React.Context<KeyboardShortcutRegistry | undefined> = React.createContext<\n KeyboardShortcutRegistry | undefined\n>(undefined);\n\n// ============================================================================\n// Provider\n// ============================================================================\n\n/**\n * Props for the KeyboardShortcutProvider.\n * @public\n */\nexport interface IKeyboardShortcutProviderProps {\n readonly children: React.ReactNode;\n}\n\n/**\n * Provides a KeyboardShortcutRegistry to the component tree.\n * Should be mounted once at the app root.\n * @public\n */\nexport function KeyboardShortcutProvider(props: IKeyboardShortcutProviderProps): React.ReactElement {\n const registry = useMemo(() => new KeyboardShortcutRegistry(), []);\n\n useEffect(() => {\n return (): void => registry.dispose();\n }, [registry]);\n\n return <KeyboardContext.Provider value={registry}>{props.children}</KeyboardContext.Provider>;\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\n/**\n * Returns the keyboard shortcut registry.\n * Must be used within a KeyboardShortcutProvider.\n * @public\n */\nexport function useKeyboardRegistry(): KeyboardShortcutRegistry {\n const ctx = useContext(KeyboardContext);\n if (ctx === undefined) {\n throw new Error('useKeyboardRegistry must be used within a KeyboardShortcutProvider');\n }\n return ctx;\n}\n\n/**\n * Registers one or more keyboard shortcuts for the lifetime of the component.\n * Shortcuts are automatically unregistered on unmount.\n *\n * @example\n * ```tsx\n * useKeyboardShortcuts([\n * {\n * binding: { key: 'k', meta: true },\n * description: 'Open command palette',\n * handler: () => { setCommandPaletteOpen(true); }\n * },\n * {\n * binding: { key: 'z', meta: true },\n * description: 'Undo',\n * handler: () => { workspace.undo(); }\n * }\n * ]);\n * ```\n *\n * @param shortcuts - Array of shortcuts to register\n * @public\n */\nexport function useKeyboardShortcuts(shortcuts: ReadonlyArray<IShortcut>): void {\n const registry = useKeyboardRegistry();\n const shortcutsRef = useRef(shortcuts);\n shortcutsRef.current = shortcuts;\n\n useEffect(() => {\n // Register stable wrapper shortcuts that delegate to the current ref entries.\n // This means the effect only runs when the registry changes or the number of\n // shortcuts changes — not on every render when the array identity changes.\n const registrations = shortcutsRef.current.map((__item, index) =>\n registry.register({\n get binding() {\n return shortcutsRef.current[index]?.binding ?? { key: '' };\n },\n get description() {\n return shortcutsRef.current[index]?.description ?? '';\n },\n get priority() {\n return shortcutsRef.current[index]?.priority;\n },\n handler: (): boolean | void => {\n return shortcutsRef.current[index]?.handler();\n }\n })\n );\n return (): void => {\n for (const reg of registrations) {\n reg.unregister();\n }\n };\n // Re-register only when the registry instance or the number of shortcuts changes.\n // Handler/binding updates are picked up via the ref without re-registration.\n }, [registry, shortcuts.length]);\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { IMessage, IMessageAction, MessageSeverity } from './model';
2
+ import { type MessageLogLevel } from '@fgv/ts-utils';
3
+ import { ICreateMessageOptions, IMessage } from './model';
3
4
  /**
4
5
  * Value provided by the MessagesContext.
5
6
  * @public
@@ -7,8 +8,11 @@ import { IMessage, IMessageAction, MessageSeverity } from './model';
7
8
  export interface IMessagesContextValue {
8
9
  /** All messages in the stream (newest last) */
9
10
  readonly messages: ReadonlyArray<IMessage>;
10
- /** Add a message to the stream */
11
- readonly addMessage: (severity: MessageSeverity, text: string, action?: IMessageAction) => IMessage;
11
+ /**
12
+ * Add a message to the stream. The `level` drives filtering; pass `options.severity`
13
+ * only to override display styling (e.g. `'success'`).
14
+ */
15
+ readonly addMessage: (level: MessageLogLevel, text: string, options?: ICreateMessageOptions) => IMessage;
12
16
  /** Dismiss a specific message (removes from toast display, keeps in log) */
13
17
  readonly dismissMessage: (id: string) => void;
14
18
  /** Clear all messages */
@@ -26,8 +30,48 @@ export interface IMessagesProviderProps {
26
30
  /** Children */
27
31
  readonly children: React.ReactNode;
28
32
  }
33
+ /**
34
+ * Sentinel Tailwind class used by the probe in {@link MessagesProvider}.
35
+ *
36
+ * This is an arbitrary-value height utility (`h-[7.3215px]`) chosen to be
37
+ * vanishingly unlikely to appear in any consumer's source code: a common
38
+ * utility like `h-3.5` could be generated by Tailwind scanning the consumer's
39
+ * own files, which would mask the missing `@fgv/ts-app-shell` content path.
40
+ * Tailwind JIT regex-scans the package's built JS, finds this literal string,
41
+ * and generates the rule — only when the consumer's `content` array includes
42
+ * the `@fgv/ts-app-shell/lib` glob (see the README "Setup" section).
43
+ * @internal
44
+ */
45
+ export declare const TAILWIND_PROBE_CLASS: string;
46
+ /**
47
+ * Expected probe height in pixels (matches {@link TAILWIND_PROBE_CLASS}).
48
+ * @internal
49
+ */
50
+ export declare const TAILWIND_PROBE_HEIGHT_PX: number;
51
+ /**
52
+ * URL of the setup-instructions anchor surfaced by the Tailwind-content-path
53
+ * diagnostic message when the probe detects that ts-app-shell's geometry utilities
54
+ * are not being generated.
55
+ * @internal
56
+ */
57
+ export declare const TAILWIND_SETUP_DOCS_URL: string;
58
+ /**
59
+ * Text of the diagnostic message emitted by the Tailwind-content-path probe.
60
+ * @internal
61
+ */
62
+ export declare const TAILWIND_PROBE_MESSAGE_TEXT: string;
29
63
  /**
30
64
  * Provides the global message stream to the component tree.
65
+ *
66
+ * On mount, runs a one-shot probe that detects whether the consuming app's Tailwind
67
+ * configuration is missing the `@fgv/ts-app-shell` content path. The probe applies a
68
+ * sentinel arbitrary-value class ({@link TAILWIND_PROBE_CLASS}) that is vanishingly
69
+ * unlikely to appear in any consumer's source — Tailwind will only generate the rule
70
+ * when it scans the package's built JS via the content path. If the probe finds the
71
+ * element unsized, a `'warning'` message is appended to the stream with a link to the
72
+ * setup documentation. The probe runs exactly once per provider lifecycle (idempotent
73
+ * under React.StrictMode double-render and HMR) and uses inline styles so it is not
74
+ * affected by the failure mode it detects.
31
75
  * @public
32
76
  */
33
77
  export declare function MessagesProvider(props: IMessagesProviderProps): React.ReactElement;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessagesContext.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesContext.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAE7F,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAiB,qBAAqB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMzE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,KAAK,QAAQ,CAAC;IACzG,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,yBAAyB;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CAChD;AAcD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe;IACf,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAuB,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAe,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAC0C,CAAC;AAEjF;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,MAEsB,CAAC;AAEjE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,sBAAsB,GAAG,KAAK,CAAC,YAAY,CAkFlF;AAMD;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,qBAAqB,CAMnD"}
@@ -54,6 +54,7 @@ var __importStar = (this && this.__importStar) || (function () {
54
54
  };
55
55
  })();
56
56
  Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.TAILWIND_PROBE_MESSAGE_TEXT = exports.TAILWIND_SETUP_DOCS_URL = exports.TAILWIND_PROBE_HEIGHT_PX = exports.TAILWIND_PROBE_CLASS = void 0;
57
58
  exports.MessagesProvider = MessagesProvider;
58
59
  exports.useMessages = useMessages;
59
60
  const react_1 = __importStar(require("react"));
@@ -62,16 +63,58 @@ const model_1 = require("./model");
62
63
  // Context
63
64
  // ============================================================================
64
65
  const MessagesContext = react_1.default.createContext(undefined);
66
+ /**
67
+ * Sentinel Tailwind class used by the probe in {@link MessagesProvider}.
68
+ *
69
+ * This is an arbitrary-value height utility (`h-[7.3215px]`) chosen to be
70
+ * vanishingly unlikely to appear in any consumer's source code: a common
71
+ * utility like `h-3.5` could be generated by Tailwind scanning the consumer's
72
+ * own files, which would mask the missing `@fgv/ts-app-shell` content path.
73
+ * Tailwind JIT regex-scans the package's built JS, finds this literal string,
74
+ * and generates the rule — only when the consumer's `content` array includes
75
+ * the `@fgv/ts-app-shell/lib` glob (see the README "Setup" section).
76
+ * @internal
77
+ */
78
+ exports.TAILWIND_PROBE_CLASS = 'h-[7.3215px]';
79
+ /**
80
+ * Expected probe height in pixels (matches {@link TAILWIND_PROBE_CLASS}).
81
+ * @internal
82
+ */
83
+ exports.TAILWIND_PROBE_HEIGHT_PX = 7.3215;
84
+ /**
85
+ * URL of the setup-instructions anchor surfaced by the Tailwind-content-path
86
+ * diagnostic message when the probe detects that ts-app-shell's geometry utilities
87
+ * are not being generated.
88
+ * @internal
89
+ */
90
+ exports.TAILWIND_SETUP_DOCS_URL = 'https://github.com/ErikFortune/fgv/tree/release/libraries/ts-app-shell#setup';
91
+ /**
92
+ * Text of the diagnostic message emitted by the Tailwind-content-path probe.
93
+ * @internal
94
+ */
95
+ exports.TAILWIND_PROBE_MESSAGE_TEXT = '@fgv/ts-app-shell styles are not fully loaded. Some icons may not render correctly. ' +
96
+ 'See setup docs for the required Tailwind content-path entry.';
65
97
  /**
66
98
  * Provides the global message stream to the component tree.
99
+ *
100
+ * On mount, runs a one-shot probe that detects whether the consuming app's Tailwind
101
+ * configuration is missing the `@fgv/ts-app-shell` content path. The probe applies a
102
+ * sentinel arbitrary-value class ({@link TAILWIND_PROBE_CLASS}) that is vanishingly
103
+ * unlikely to appear in any consumer's source — Tailwind will only generate the rule
104
+ * when it scans the package's built JS via the content path. If the probe finds the
105
+ * element unsized, a `'warning'` message is appended to the stream with a link to the
106
+ * setup documentation. The probe runs exactly once per provider lifecycle (idempotent
107
+ * under React.StrictMode double-render and HMR) and uses inline styles so it is not
108
+ * affected by the failure mode it detects.
67
109
  * @public
68
110
  */
69
111
  function MessagesProvider(props) {
70
112
  const { maxMessages = 200, children } = props;
71
113
  const [messages, setMessages] = (0, react_1.useState)([]);
72
114
  const [dismissedIds, setDismissedIds] = (0, react_1.useState)(new Set());
73
- const addMessage = (0, react_1.useCallback)((severity, text, action) => {
74
- const msg = (0, model_1.createMessage)(severity, text, action);
115
+ const probeHasRun = (0, react_1.useRef)(false);
116
+ const addMessage = (0, react_1.useCallback)((level, text, options) => {
117
+ const msg = (0, model_1.createMessage)(level, text, options);
75
118
  setMessages((prev) => {
76
119
  const next = [...prev, msg];
77
120
  return next.length > maxMessages ? next.slice(next.length - maxMessages) : next;
@@ -86,6 +129,40 @@ function MessagesProvider(props) {
86
129
  setDismissedIds(new Set());
87
130
  }, []);
88
131
  const activeToasts = (0, react_1.useMemo)(() => messages.filter((m) => !dismissedIds.has(m.id)), [messages, dismissedIds]);
132
+ (0, react_1.useEffect)(() => {
133
+ if (probeHasRun.current) {
134
+ return;
135
+ }
136
+ const probe = document.createElement('div');
137
+ probe.className = exports.TAILWIND_PROBE_CLASS;
138
+ probe.setAttribute('aria-hidden', 'true');
139
+ probe.style.position = 'absolute';
140
+ probe.style.visibility = 'hidden';
141
+ probe.style.pointerEvents = 'none';
142
+ document.body.appendChild(probe);
143
+ const frameId = requestAnimationFrame(() => {
144
+ // Mark the guard only once the measurement has actually run; otherwise a
145
+ // StrictMode cleanup-before-rAF would leave `probeHasRun = true` and the
146
+ // second setup would short-circuit, suppressing the diagnostic entirely.
147
+ probeHasRun.current = true;
148
+ const height = probe.getBoundingClientRect().height;
149
+ probe.remove();
150
+ if (height === 0) {
151
+ addMessage('warning', exports.TAILWIND_PROBE_MESSAGE_TEXT, {
152
+ action: {
153
+ label: 'Open setup docs',
154
+ href: exports.TAILWIND_SETUP_DOCS_URL
155
+ }
156
+ });
157
+ }
158
+ });
159
+ return () => {
160
+ cancelAnimationFrame(frameId);
161
+ if (probe.parentNode !== null) {
162
+ probe.remove();
163
+ }
164
+ };
165
+ }, [addMessage]);
89
166
  const value = (0, react_1.useMemo)(() => ({
90
167
  messages,
91
168
  addMessage,
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessagesContext.js","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesContext.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { type MessageLogLevel } from '@fgv/ts-utils';\n\nimport { createMessage, ICreateMessageOptions, IMessage } from './model';\n\n// ============================================================================\n// Context Value\n// ============================================================================\n\n/**\n * Value provided by the MessagesContext.\n * @public\n */\nexport interface IMessagesContextValue {\n /** All messages in the stream (newest last) */\n readonly messages: ReadonlyArray<IMessage>;\n /**\n * Add a message to the stream. The `level` drives filtering; pass `options.severity`\n * only to override display styling (e.g. `'success'`).\n */\n readonly addMessage: (level: MessageLogLevel, text: string, options?: ICreateMessageOptions) => IMessage;\n /** Dismiss a specific message (removes from toast display, keeps in log) */\n readonly dismissMessage: (id: string) => void;\n /** Clear all messages */\n readonly clearMessages: () => void;\n /** Messages currently visible as toasts (not yet dismissed) */\n readonly activeToasts: ReadonlyArray<IMessage>;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst MessagesContext: React.Context<IMessagesContextValue | undefined> = React.createContext<\n IMessagesContextValue | undefined\n>(undefined);\n\n// ============================================================================\n// Provider\n// ============================================================================\n\n/**\n * Props for the MessagesProvider.\n * @public\n */\nexport interface IMessagesProviderProps {\n /** Maximum number of messages to retain in the log */\n readonly maxMessages?: number;\n /** Children */\n readonly children: React.ReactNode;\n}\n\n/**\n * Sentinel Tailwind class used by the probe in {@link MessagesProvider}.\n *\n * This is an arbitrary-value height utility (`h-[7.3215px]`) chosen to be\n * vanishingly unlikely to appear in any consumer's source code: a common\n * utility like `h-3.5` could be generated by Tailwind scanning the consumer's\n * own files, which would mask the missing `@fgv/ts-app-shell` content path.\n * Tailwind JIT regex-scans the package's built JS, finds this literal string,\n * and generates the rule — only when the consumer's `content` array includes\n * the `@fgv/ts-app-shell/lib` glob (see the README \"Setup\" section).\n * @internal\n */\nexport const TAILWIND_PROBE_CLASS: string = 'h-[7.3215px]';\n\n/**\n * Expected probe height in pixels (matches {@link TAILWIND_PROBE_CLASS}).\n * @internal\n */\nexport const TAILWIND_PROBE_HEIGHT_PX: number = 7.3215;\n\n/**\n * URL of the setup-instructions anchor surfaced by the Tailwind-content-path\n * diagnostic message when the probe detects that ts-app-shell's geometry utilities\n * are not being generated.\n * @internal\n */\nexport const TAILWIND_SETUP_DOCS_URL: string =\n 'https://github.com/ErikFortune/fgv/tree/release/libraries/ts-app-shell#setup';\n\n/**\n * Text of the diagnostic message emitted by the Tailwind-content-path probe.\n * @internal\n */\nexport const TAILWIND_PROBE_MESSAGE_TEXT: string =\n '@fgv/ts-app-shell styles are not fully loaded. Some icons may not render correctly. ' +\n 'See setup docs for the required Tailwind content-path entry.';\n\n/**\n * Provides the global message stream to the component tree.\n *\n * On mount, runs a one-shot probe that detects whether the consuming app's Tailwind\n * configuration is missing the `@fgv/ts-app-shell` content path. The probe applies a\n * sentinel arbitrary-value class ({@link TAILWIND_PROBE_CLASS}) that is vanishingly\n * unlikely to appear in any consumer's source — Tailwind will only generate the rule\n * when it scans the package's built JS via the content path. If the probe finds the\n * element unsized, a `'warning'` message is appended to the stream with a link to the\n * setup documentation. The probe runs exactly once per provider lifecycle (idempotent\n * under React.StrictMode double-render and HMR) and uses inline styles so it is not\n * affected by the failure mode it detects.\n * @public\n */\nexport function MessagesProvider(props: IMessagesProviderProps): React.ReactElement {\n const { maxMessages = 200, children } = props;\n const [messages, setMessages] = useState<IMessage[]>([]);\n const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());\n const probeHasRun: React.MutableRefObject<boolean> = useRef<boolean>(false);\n\n const addMessage = useCallback(\n (level: MessageLogLevel, text: string, options?: ICreateMessageOptions): IMessage => {\n const msg = createMessage(level, text, options);\n setMessages((prev) => {\n const next = [...prev, msg];\n return next.length > maxMessages ? next.slice(next.length - maxMessages) : next;\n });\n return msg;\n },\n [maxMessages]\n );\n\n const dismissMessage = useCallback((id: string): void => {\n setDismissedIds((prev) => new Set(prev).add(id));\n }, []);\n\n const clearMessages = useCallback((): void => {\n setMessages([]);\n setDismissedIds(new Set());\n }, []);\n\n const activeToasts = useMemo(\n () => messages.filter((m) => !dismissedIds.has(m.id)),\n [messages, dismissedIds]\n );\n\n useEffect(() => {\n if (probeHasRun.current) {\n return;\n }\n\n const probe: HTMLDivElement = document.createElement('div');\n probe.className = TAILWIND_PROBE_CLASS;\n probe.setAttribute('aria-hidden', 'true');\n probe.style.position = 'absolute';\n probe.style.visibility = 'hidden';\n probe.style.pointerEvents = 'none';\n document.body.appendChild(probe);\n\n const frameId: number = requestAnimationFrame(() => {\n // Mark the guard only once the measurement has actually run; otherwise a\n // StrictMode cleanup-before-rAF would leave `probeHasRun = true` and the\n // second setup would short-circuit, suppressing the diagnostic entirely.\n probeHasRun.current = true;\n const height: number = probe.getBoundingClientRect().height;\n probe.remove();\n if (height === 0) {\n addMessage('warning', TAILWIND_PROBE_MESSAGE_TEXT, {\n action: {\n label: 'Open setup docs',\n href: TAILWIND_SETUP_DOCS_URL\n }\n });\n }\n });\n\n return (): void => {\n cancelAnimationFrame(frameId);\n if (probe.parentNode !== null) {\n probe.remove();\n }\n };\n }, [addMessage]);\n\n const value = useMemo<IMessagesContextValue>(\n () => ({\n messages,\n addMessage,\n dismissMessage,\n clearMessages,\n activeToasts\n }),\n [messages, addMessage, dismissMessage, clearMessages, activeToasts]\n );\n\n return <MessagesContext.Provider value={value}>{children}</MessagesContext.Provider>;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Hook to access the messages context.\n * Must be used within a MessagesProvider.\n * @public\n */\nexport function useMessages(): IMessagesContextValue {\n const ctx = useContext(MessagesContext);\n if (ctx === undefined) {\n throw new Error('useMessages must be used within a MessagesProvider');\n }\n return ctx;\n}\n"]}
@@ -39,10 +39,15 @@ export declare class MessagesLogger extends Logging.LoggerBase {
39
39
  */
40
40
  constructor(addMessage: IMessagesContextValue['addMessage'], logLevel?: Logging.ReporterLogLevel, defaultAction?: IMessageAction);
41
41
  /**
42
- * Routes a formatted log message into the MessagesContext.
42
+ * Routes a formatted log message into the MessagesContext, setting the message `level`
43
+ * straight through from the log call (lossless). Display styling `severity` is left
44
+ * undefined so it is derived from the level for display.
45
+ *
46
+ * Suppression by threshold is handled upstream by {@link @fgv/ts-utils#Logging.LoggerBase | LoggerBase}
47
+ * (`shouldLog` against `logLevel`) before this method is invoked.
43
48
  * @param message - The formatted message string
44
- * @param level - The log level
45
- * @returns Success with the message if it was logged, or Success with undefined if suppressed
49
+ * @param level - The {@link @fgv/ts-utils#MessageLogLevel | log level}
50
+ * @returns Success with the message that was logged
46
51
  * @internal
47
52
  */
48
53
  protected _log(message: string, level: MessageLogLevel): Success<string | undefined>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessagesLogger.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesLogger.ts"],"names":[],"mappings":"AAsBA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,eAAe,EAAE,OAAO,EAAW,MAAM,eAAe,CAAC;AAEhF,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAM/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,cAAe,SAAQ,OAAO,CAAC,UAAU;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAE5D;;;;;OAKG;gBAED,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,EAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,EACnC,aAAa,CAAC,EAAE,cAAc;IAOhC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAIrF"}
@@ -32,28 +32,6 @@ exports.MessagesLogger = void 0;
32
32
  */
33
33
  const ts_utils_1 = require("@fgv/ts-utils");
34
34
  // ============================================================================
35
- // Level Mapping
36
- // ============================================================================
37
- /**
38
- * Maps ts-utils MessageLogLevel to our MessageSeverity.
39
- * `detail` maps to `'info'` (shown when logLevel is 'detail' or 'all').
40
- * `quiet` is always suppressed (only shown when reporter level is 'all', handled upstream).
41
- * @internal
42
- */
43
- function mapLogLevel(level) {
44
- switch (level) {
45
- case 'detail':
46
- case 'info':
47
- return 'info';
48
- case 'warning':
49
- return 'warning';
50
- case 'error':
51
- return 'error';
52
- case 'quiet':
53
- return undefined;
54
- }
55
- }
56
- // ============================================================================
57
35
  // MessagesLogger
58
36
  // ============================================================================
59
37
  /**
@@ -88,19 +66,20 @@ class MessagesLogger extends ts_utils_1.Logging.LoggerBase {
88
66
  this._defaultAction = defaultAction;
89
67
  }
90
68
  /**
91
- * Routes a formatted log message into the MessagesContext.
69
+ * Routes a formatted log message into the MessagesContext, setting the message `level`
70
+ * straight through from the log call (lossless). Display styling `severity` is left
71
+ * undefined so it is derived from the level for display.
72
+ *
73
+ * Suppression by threshold is handled upstream by {@link @fgv/ts-utils#Logging.LoggerBase | LoggerBase}
74
+ * (`shouldLog` against `logLevel`) before this method is invoked.
92
75
  * @param message - The formatted message string
93
- * @param level - The log level
94
- * @returns Success with the message if it was logged, or Success with undefined if suppressed
76
+ * @param level - The {@link @fgv/ts-utils#MessageLogLevel | log level}
77
+ * @returns Success with the message that was logged
95
78
  * @internal
96
79
  */
97
80
  _log(message, level) {
98
- const severity = mapLogLevel(level);
99
- if (severity !== undefined) {
100
- this._addMessage(severity, message, this._defaultAction);
101
- return (0, ts_utils_1.succeed)(message);
102
- }
103
- return (0, ts_utils_1.succeed)(undefined);
81
+ this._addMessage(level, message, { action: this._defaultAction });
82
+ return (0, ts_utils_1.succeed)(message);
104
83
  }
105
84
  }
106
85
  exports.MessagesLogger = MessagesLogger;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessagesLogger.js","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesLogger.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAEH;;;;;;;GAOG;AAEH,4CAAgF;AAKhF,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,cAAe,SAAQ,kBAAO,CAAC,UAAU;IAIpD;;;;;OAKG;IACH,YACE,UAA+C,EAC/C,QAAmC,EACnC,aAA8B;QAE9B,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;IACtC,CAAC;IAED;;;;;;;;;;;OAWG;IACO,IAAI,CAAC,OAAe,EAAE,KAAsB;QACpD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QAClE,OAAO,IAAA,kBAAO,EAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;CACF;AApCD,wCAoCC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Bridge between the MessagesContext and the ts-utils ILogger/LogReporter system.\n *\n * Provides an ILogger implementation that routes log messages into the\n * MessagesContext, enabling Result.report() integration in React components.\n *\n * @packageDocumentation\n */\n\nimport { Logging, type MessageLogLevel, Success, succeed } from '@fgv/ts-utils';\n\nimport { type IMessageAction } from './model';\nimport { type IMessagesContextValue } from './MessagesContext';\n\n// ============================================================================\n// MessagesLogger\n// ============================================================================\n\n/**\n * An {@link @fgv/ts-utils#Logging.ILogger | ILogger} implementation that routes\n * messages into a {@link IMessagesContextValue | MessagesContext}.\n *\n * Use this as the `logger` parameter when creating a `LogReporter` to get\n * full `Result.report()` integration that feeds into the app's toast/log system.\n *\n * @example\n * ```typescript\n * const { addMessage } = useMessages();\n * const logger = new MessagesLogger(addMessage);\n * const reporter = new LogReporter<unknown>({ logger });\n *\n * // Now Result.report() flows into toasts + log panel\n * someResult.report(reporter, { success: 'info', failure: 'error' });\n * ```\n *\n * @public\n */\nexport class MessagesLogger extends Logging.LoggerBase {\n private readonly _addMessage: IMessagesContextValue['addMessage'];\n private readonly _defaultAction: IMessageAction | undefined;\n\n /**\n * Creates a new MessagesLogger.\n * @param addMessage - The addMessage function from MessagesContext\n * @param logLevel - The minimum log level to display (default: 'info')\n * @param defaultAction - Optional default action to attach to all messages\n */\n public constructor(\n addMessage: IMessagesContextValue['addMessage'],\n logLevel?: Logging.ReporterLogLevel,\n defaultAction?: IMessageAction\n ) {\n super(logLevel);\n this._addMessage = addMessage;\n this._defaultAction = defaultAction;\n }\n\n /**\n * Routes a formatted log message into the MessagesContext, setting the message `level`\n * straight through from the log call (lossless). Display styling `severity` is left\n * undefined so it is derived from the level for display.\n *\n * Suppression by threshold is handled upstream by {@link @fgv/ts-utils#Logging.LoggerBase | LoggerBase}\n * (`shouldLog` against `logLevel`) before this method is invoked.\n * @param message - The formatted message string\n * @param level - The {@link @fgv/ts-utils#MessageLogLevel | log level}\n * @returns Success with the message that was logged\n * @internal\n */\n protected _log(message: string, level: MessageLogLevel): Success<string | undefined> {\n this._addMessage(level, message, { action: this._defaultAction });\n return succeed(message);\n }\n}\n"]}
@@ -10,12 +10,21 @@ export interface IStatusBarProps {
10
10
  readonly onClear: () => void;
11
11
  /** Initial filter level for the log panel. Defaults to 'all'. */
12
12
  readonly initialFilterLevel?: Logging.ReporterLogLevel;
13
+ /**
14
+ * Maximum height of the expanded panel as a CSS length (e.g. `'40vh'`, `'320px'`).
15
+ * The panel is bounded to this height on both desktop and mobile; the message list
16
+ * scrolls within the bound while the header and filter controls stay fixed. Defaults to `'40vh'`.
17
+ */
18
+ readonly maxExpandedHeight?: string;
19
+ /** Whether the panel is expanded on mount. Defaults to `false` (collapsed). */
20
+ readonly defaultExpanded?: boolean;
13
21
  }
14
22
  /**
15
23
  * Collapsible status bar / log panel at the bottom of the application.
16
24
  *
17
25
  * Collapsed: shows severity counts.
18
- * Expanded: filterable, searchable, copyable log of all messages.
26
+ * Expanded: filterable, searchable, copyable log of all messages, bounded to
27
+ * {@link IStatusBarProps.maxExpandedHeight} with the list scrolling within the bound.
19
28
  * @public
20
29
  */
21
30
  export declare function StatusBar(props: IStatusBarProps): React.ReactElement;