@fgv/ts-app-shell 5.1.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +26 -0
  2. package/dist/index.browser.js +3 -0
  3. package/dist/index.js +43 -0
  4. package/dist/packlets/ai-assist/index.js +6 -0
  5. package/dist/packlets/ai-assist/useAiAssist.js +219 -0
  6. package/dist/packlets/cascade/CascadeContainer.js +83 -0
  7. package/dist/packlets/cascade/ComparisonView.js +48 -0
  8. package/dist/packlets/cascade/EntityTabLayout.js +104 -0
  9. package/dist/packlets/cascade/MobileCascadeStack.js +63 -0
  10. package/dist/packlets/cascade/index.js +37 -0
  11. package/dist/packlets/cascade/model.js +30 -0
  12. package/dist/packlets/cascade/useCascadeOps.js +206 -0
  13. package/dist/packlets/cascade/useCascadeTransitions.js +58 -0
  14. package/dist/packlets/detail/DetailHelpers.js +103 -0
  15. package/dist/packlets/detail/index.js +6 -0
  16. package/dist/packlets/drop-zone/JsonDropZone.js +112 -0
  17. package/dist/packlets/drop-zone/index.js +6 -0
  18. package/dist/packlets/editing/EditFieldHelpers.js +130 -0
  19. package/dist/packlets/editing/MultiActionButton.js +73 -0
  20. package/dist/packlets/editing/NumericInput.js +119 -0
  21. package/dist/packlets/editing/TypeaheadInput.js +207 -0
  22. package/dist/packlets/editing/index.js +10 -0
  23. package/dist/packlets/editing/useTypeaheadMatch.js +102 -0
  24. package/dist/packlets/keyboard/index.js +7 -0
  25. package/dist/packlets/keyboard/registry.js +133 -0
  26. package/dist/packlets/keyboard/useKeyboardShortcuts.js +117 -0
  27. package/dist/packlets/messages/MessagesContext.js +76 -0
  28. package/dist/packlets/messages/MessagesLogger.js +103 -0
  29. package/dist/packlets/messages/StatusBar.js +154 -0
  30. package/dist/packlets/messages/Toast.js +68 -0
  31. package/dist/packlets/messages/index.js +11 -0
  32. package/dist/packlets/messages/model.js +56 -0
  33. package/dist/packlets/messages/useLogReporter.js +66 -0
  34. package/dist/packlets/modal/ConfirmDialog.js +78 -0
  35. package/dist/packlets/modal/Modal.js +55 -0
  36. package/dist/packlets/modal/index.js +7 -0
  37. package/dist/packlets/print/PrintEnclosure.js +60 -0
  38. package/dist/packlets/print/index.js +7 -0
  39. package/dist/packlets/print/openPrintWindow.js +112 -0
  40. package/dist/packlets/responsive/ResponsiveProvider.js +56 -0
  41. package/dist/packlets/responsive/index.js +7 -0
  42. package/dist/packlets/responsive/useResponsiveLayout.js +118 -0
  43. package/dist/packlets/selectors/EntityRow.js +276 -0
  44. package/dist/packlets/selectors/PreferredSelector.js +251 -0
  45. package/dist/packlets/selectors/index.js +24 -0
  46. package/dist/packlets/sidebar/CollectionSection.js +107 -0
  47. package/dist/packlets/sidebar/EntityList.js +164 -0
  48. package/dist/packlets/sidebar/FilterBar.js +42 -0
  49. package/dist/packlets/sidebar/FilterRow.js +182 -0
  50. package/dist/packlets/sidebar/GroupedEntityList.js +183 -0
  51. package/dist/packlets/sidebar/SearchBar.js +34 -0
  52. package/dist/packlets/sidebar/SidebarLayout.js +62 -0
  53. package/dist/packlets/sidebar/index.js +12 -0
  54. package/dist/packlets/theme/ThemeProvider.js +141 -0
  55. package/dist/packlets/theme/index.js +6 -0
  56. package/dist/packlets/top-bar/ModeSelector.js +46 -0
  57. package/dist/packlets/top-bar/TabBar.js +37 -0
  58. package/dist/packlets/top-bar/index.js +7 -0
  59. package/dist/packlets/url-sync/index.js +6 -0
  60. package/dist/packlets/url-sync/useUrlSync.js +157 -0
  61. package/eslint.config.js +22 -0
  62. package/lib/index.browser.d.ts +2 -0
  63. package/lib/index.browser.js +19 -0
  64. package/lib/index.d.ts +28 -0
  65. package/lib/index.js +59 -0
  66. package/lib/packlets/ai-assist/index.d.ts +6 -0
  67. package/lib/packlets/ai-assist/index.js +11 -0
  68. package/lib/packlets/ai-assist/useAiAssist.d.ts +77 -0
  69. package/lib/packlets/ai-assist/useAiAssist.js +223 -0
  70. package/lib/packlets/cascade/CascadeContainer.d.ts +44 -0
  71. package/lib/packlets/cascade/CascadeContainer.js +119 -0
  72. package/lib/packlets/cascade/ComparisonView.d.ts +35 -0
  73. package/lib/packlets/cascade/ComparisonView.js +54 -0
  74. package/lib/packlets/cascade/EntityTabLayout.d.ts +47 -0
  75. package/lib/packlets/cascade/EntityTabLayout.js +110 -0
  76. package/lib/packlets/cascade/MobileCascadeStack.d.ts +20 -0
  77. package/lib/packlets/cascade/MobileCascadeStack.js +99 -0
  78. package/lib/packlets/cascade/index.d.ts +12 -0
  79. package/lib/packlets/cascade/index.js +48 -0
  80. package/lib/packlets/cascade/model.d.ts +57 -0
  81. package/lib/packlets/cascade/model.js +33 -0
  82. package/lib/packlets/cascade/useCascadeOps.d.ts +111 -0
  83. package/lib/packlets/cascade/useCascadeOps.js +209 -0
  84. package/lib/packlets/cascade/useCascadeTransitions.d.ts +19 -0
  85. package/lib/packlets/cascade/useCascadeTransitions.js +62 -0
  86. package/lib/packlets/detail/DetailHelpers.d.ts +83 -0
  87. package/lib/packlets/detail/DetailHelpers.js +113 -0
  88. package/lib/packlets/detail/index.d.ts +6 -0
  89. package/lib/packlets/detail/index.js +14 -0
  90. package/lib/packlets/drop-zone/JsonDropZone.d.ts +40 -0
  91. package/lib/packlets/drop-zone/JsonDropZone.js +149 -0
  92. package/lib/packlets/drop-zone/index.d.ts +6 -0
  93. package/lib/packlets/drop-zone/index.js +10 -0
  94. package/lib/packlets/editing/EditFieldHelpers.d.ts +171 -0
  95. package/lib/packlets/editing/EditFieldHelpers.js +144 -0
  96. package/lib/packlets/editing/MultiActionButton.d.ts +45 -0
  97. package/lib/packlets/editing/MultiActionButton.js +109 -0
  98. package/lib/packlets/editing/NumericInput.d.ts +47 -0
  99. package/lib/packlets/editing/NumericInput.js +155 -0
  100. package/lib/packlets/editing/TypeaheadInput.d.ts +46 -0
  101. package/lib/packlets/editing/TypeaheadInput.js +243 -0
  102. package/lib/packlets/editing/index.d.ts +10 -0
  103. package/lib/packlets/editing/index.js +26 -0
  104. package/lib/packlets/editing/useTypeaheadMatch.d.ts +42 -0
  105. package/lib/packlets/editing/useTypeaheadMatch.js +105 -0
  106. package/lib/packlets/keyboard/index.d.ts +7 -0
  107. package/lib/packlets/keyboard/index.js +15 -0
  108. package/lib/packlets/keyboard/registry.d.ts +92 -0
  109. package/lib/packlets/keyboard/registry.js +138 -0
  110. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts +50 -0
  111. package/lib/packlets/keyboard/useKeyboardShortcuts.js +155 -0
  112. package/lib/packlets/messages/MessagesContext.d.ts +40 -0
  113. package/lib/packlets/messages/MessagesContext.js +113 -0
  114. package/lib/packlets/messages/MessagesLogger.d.ts +50 -0
  115. package/lib/packlets/messages/MessagesLogger.js +107 -0
  116. package/lib/packlets/messages/StatusBar.d.ts +22 -0
  117. package/lib/packlets/messages/StatusBar.js +190 -0
  118. package/lib/packlets/messages/Toast.d.ts +31 -0
  119. package/lib/packlets/messages/Toast.js +105 -0
  120. package/lib/packlets/messages/index.d.ts +11 -0
  121. package/lib/packlets/messages/index.js +24 -0
  122. package/lib/packlets/messages/model.d.ts +59 -0
  123. package/lib/packlets/messages/model.js +61 -0
  124. package/lib/packlets/messages/useLogReporter.d.ts +22 -0
  125. package/lib/packlets/messages/useLogReporter.js +69 -0
  126. package/lib/packlets/modal/ConfirmDialog.d.ts +39 -0
  127. package/lib/packlets/modal/ConfirmDialog.js +114 -0
  128. package/lib/packlets/modal/Modal.d.ts +22 -0
  129. package/lib/packlets/modal/Modal.js +91 -0
  130. package/lib/packlets/modal/index.d.ts +7 -0
  131. package/lib/packlets/modal/index.js +12 -0
  132. package/lib/packlets/print/PrintEnclosure.d.ts +33 -0
  133. package/lib/packlets/print/PrintEnclosure.js +96 -0
  134. package/lib/packlets/print/index.d.ts +7 -0
  135. package/lib/packlets/print/index.js +12 -0
  136. package/lib/packlets/print/openPrintWindow.d.ts +35 -0
  137. package/lib/packlets/print/openPrintWindow.js +118 -0
  138. package/lib/packlets/responsive/ResponsiveProvider.d.ts +35 -0
  139. package/lib/packlets/responsive/ResponsiveProvider.js +93 -0
  140. package/lib/packlets/responsive/index.d.ts +7 -0
  141. package/lib/packlets/responsive/index.js +13 -0
  142. package/lib/packlets/responsive/useResponsiveLayout.d.ts +48 -0
  143. package/lib/packlets/responsive/useResponsiveLayout.js +121 -0
  144. package/lib/packlets/selectors/EntityRow.d.ts +45 -0
  145. package/lib/packlets/selectors/EntityRow.js +315 -0
  146. package/lib/packlets/selectors/PreferredSelector.d.ts +50 -0
  147. package/lib/packlets/selectors/PreferredSelector.js +287 -0
  148. package/lib/packlets/selectors/index.d.ts +5 -0
  149. package/lib/packlets/selectors/index.js +29 -0
  150. package/lib/packlets/sidebar/CollectionSection.d.ts +82 -0
  151. package/lib/packlets/sidebar/CollectionSection.js +143 -0
  152. package/lib/packlets/sidebar/EntityList.d.ts +105 -0
  153. package/lib/packlets/sidebar/EntityList.js +200 -0
  154. package/lib/packlets/sidebar/FilterBar.d.ts +26 -0
  155. package/lib/packlets/sidebar/FilterBar.js +48 -0
  156. package/lib/packlets/sidebar/FilterRow.d.ts +42 -0
  157. package/lib/packlets/sidebar/FilterRow.js +218 -0
  158. package/lib/packlets/sidebar/GroupedEntityList.d.ts +59 -0
  159. package/lib/packlets/sidebar/GroupedEntityList.js +219 -0
  160. package/lib/packlets/sidebar/SearchBar.d.ts +19 -0
  161. package/lib/packlets/sidebar/SearchBar.js +40 -0
  162. package/lib/packlets/sidebar/SidebarLayout.d.ts +28 -0
  163. package/lib/packlets/sidebar/SidebarLayout.js +98 -0
  164. package/lib/packlets/sidebar/index.d.ts +12 -0
  165. package/lib/packlets/sidebar/index.js +22 -0
  166. package/lib/packlets/theme/ThemeProvider.d.ts +68 -0
  167. package/lib/packlets/theme/ThemeProvider.js +178 -0
  168. package/lib/packlets/theme/index.d.ts +6 -0
  169. package/lib/packlets/theme/index.js +11 -0
  170. package/lib/packlets/top-bar/ModeSelector.d.ts +38 -0
  171. package/lib/packlets/top-bar/ModeSelector.js +52 -0
  172. package/lib/packlets/top-bar/TabBar.d.ts +31 -0
  173. package/lib/packlets/top-bar/TabBar.js +43 -0
  174. package/lib/packlets/top-bar/index.d.ts +7 -0
  175. package/lib/packlets/top-bar/index.js +12 -0
  176. package/lib/packlets/url-sync/index.d.ts +6 -0
  177. package/lib/packlets/url-sync/index.js +12 -0
  178. package/lib/packlets/url-sync/useUrlSync.d.ts +75 -0
  179. package/lib/packlets/url-sync/useUrlSync.js +162 -0
  180. package/package.json +82 -0
@@ -0,0 +1,78 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import React, { useCallback, useEffect } from 'react';
23
+ import { useResponsive } from '../responsive';
24
+ const severityClasses = {
25
+ danger: 'bg-status-error-btn hover:bg-status-error-btn-hover focus:ring-status-error-accent',
26
+ warning: 'bg-status-warning-btn hover:bg-status-warning-btn-hover focus:ring-status-warning-strong',
27
+ info: 'bg-status-info-btn hover:bg-status-info-btn-hover focus:ring-status-info-icon'
28
+ };
29
+ const severityIconClasses = {
30
+ danger: 'text-status-error-icon',
31
+ warning: 'text-status-warning-icon',
32
+ info: 'text-status-info-icon'
33
+ };
34
+ /**
35
+ * Generic confirmation dialog.
36
+ *
37
+ * Renders a modal overlay with a title, message, and confirm/cancel buttons.
38
+ * Closes on Escape key or backdrop click (treated as cancel).
39
+ *
40
+ * @public
41
+ */
42
+ export function ConfirmDialog(props) {
43
+ const { isOpen, title, message, confirmLabel = 'Confirm', cancelLabel = 'Cancel', severity = 'danger', onConfirm, onCancel } = props;
44
+ const { layoutMode } = useResponsive();
45
+ const handleKeyDown = useCallback((e) => {
46
+ if (e.key === 'Escape') {
47
+ onCancel();
48
+ }
49
+ }, [onCancel]);
50
+ useEffect(() => {
51
+ if (isOpen) {
52
+ document.addEventListener('keydown', handleKeyDown);
53
+ return () => document.removeEventListener('keydown', handleKeyDown);
54
+ }
55
+ return undefined;
56
+ }, [isOpen, handleKeyDown]);
57
+ if (!isOpen) {
58
+ return null;
59
+ }
60
+ return (React.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center" },
61
+ React.createElement("div", { className: "absolute inset-0 bg-backdrop", onClick: onCancel, role: "presentation" }),
62
+ React.createElement("div", { className: `relative bg-surface rounded-lg shadow-xl ${layoutMode === 'mobile' ? 'w-[95vw]' : 'max-w-md w-full mx-4'}`, role: "alertdialog", "aria-modal": "true", "aria-labelledby": "confirm-dialog-title", "aria-describedby": "confirm-dialog-message" },
63
+ React.createElement("div", { className: "p-6" },
64
+ React.createElement("div", { className: "flex items-start gap-4" },
65
+ React.createElement("div", { className: `flex-shrink-0 w-10 h-10 flex items-center justify-center rounded-full ${severity === 'danger'
66
+ ? 'bg-status-error-bg'
67
+ : severity === 'warning'
68
+ ? 'bg-status-warning-bg'
69
+ : 'bg-status-info-bg'}` },
70
+ React.createElement("svg", { className: `w-6 h-6 ${severityIconClasses[severity]}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 }, severity === 'info' ? (React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" })) : (React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" })))),
71
+ React.createElement("div", { className: "flex-1 min-w-0" },
72
+ React.createElement("h3", { id: "confirm-dialog-title", className: "text-base font-semibold text-primary leading-6" }, title),
73
+ React.createElement("div", { id: "confirm-dialog-message", className: "mt-2 text-sm text-secondary" }, typeof message === 'string' ? React.createElement("p", null, message) : message))),
74
+ React.createElement("div", { className: "mt-6 flex justify-end gap-3" },
75
+ React.createElement("button", { type: "button", onClick: onCancel, className: "px-4 py-2 text-sm font-medium text-secondary bg-surface border border-border rounded-md hover:bg-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-focus-ring transition-colors" }, cancelLabel),
76
+ React.createElement("button", { type: "button", onClick: onConfirm, className: `px-4 py-2 text-sm font-medium text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors ${severityClasses[severity]}` }, confirmLabel))))));
77
+ }
78
+ //# sourceMappingURL=ConfirmDialog.js.map
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import React, { useCallback, useEffect } from 'react';
23
+ import { useResponsive } from '../responsive';
24
+ /**
25
+ * Generic modal overlay component.
26
+ * Renders a centered dialog with a backdrop that closes on click or Escape.
27
+ * @public
28
+ */
29
+ export function Modal(props) {
30
+ const { isOpen, onClose, title, children } = props;
31
+ const { layoutMode } = useResponsive();
32
+ const handleKeyDown = useCallback((e) => {
33
+ if (e.key === 'Escape') {
34
+ onClose();
35
+ }
36
+ }, [onClose]);
37
+ useEffect(() => {
38
+ if (isOpen) {
39
+ document.addEventListener('keydown', handleKeyDown);
40
+ return () => document.removeEventListener('keydown', handleKeyDown);
41
+ }
42
+ return undefined;
43
+ }, [isOpen, handleKeyDown]);
44
+ if (!isOpen) {
45
+ return null;
46
+ }
47
+ return (React.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center" },
48
+ React.createElement("div", { className: "absolute inset-0 bg-backdrop", onClick: onClose, role: "presentation" }),
49
+ React.createElement("div", { className: `relative bg-surface rounded-lg shadow-xl flex flex-col ${layoutMode === 'mobile' ? 'w-[95vw] max-h-[95vh]' : 'max-w-lg w-full mx-4 max-h-[80vh]'}`, role: "dialog", "aria-modal": "true", "aria-label": title },
50
+ React.createElement("div", { className: "flex items-center justify-between px-6 py-4 border-b border-border" },
51
+ React.createElement("h2", { className: "text-lg font-semibold text-primary" }, title),
52
+ React.createElement("button", { onClick: onClose, className: "text-muted hover:text-secondary transition-colors", "aria-label": "Close" }, "\u00D7")),
53
+ React.createElement("div", { className: "px-6 py-4 overflow-y-auto flex-1" }, children))));
54
+ }
55
+ //# sourceMappingURL=Modal.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Modal packlet - generic overlay dialog component.
3
+ * @packageDocumentation
4
+ */
5
+ export { Modal } from './Modal';
6
+ export { ConfirmDialog } from './ConfirmDialog';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,60 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ /**
23
+ * Print enclosure component rendered inside the popup window.
24
+ * @packageDocumentation
25
+ */
26
+ import React, { useCallback } from 'react';
27
+ import { PrinterIcon, XMarkIcon } from '@heroicons/react/24/outline';
28
+ /**
29
+ * A container component rendered inside a popup window that provides a
30
+ * toolbar with Print and Close buttons and renders children as the
31
+ * printable content.
32
+ *
33
+ * @remarks
34
+ * The toolbar is hidden when printing via CSS (`print-toolbar` class).
35
+ * Children are rendered at full width with no scroll constraints so
36
+ * multi-page content flows naturally to the printer.
37
+ *
38
+ * @public
39
+ */
40
+ export function PrintEnclosure({ title, toolbarExtras, popupWindow, children }) {
41
+ const handlePrint = useCallback(() => {
42
+ popupWindow.print();
43
+ }, [popupWindow]);
44
+ const handleClose = useCallback(() => {
45
+ popupWindow.close();
46
+ }, [popupWindow]);
47
+ return (React.createElement("div", null,
48
+ React.createElement("div", { className: "print-toolbar sticky top-0 z-10 flex items-center justify-between gap-4 bg-surface border-b border-border px-6 py-3 mb-6 shadow-sm" },
49
+ React.createElement("h1", { className: "text-lg font-semibold text-primary truncate" }, title),
50
+ React.createElement("div", { className: "flex items-center gap-3 shrink-0" },
51
+ toolbarExtras,
52
+ React.createElement("button", { type: "button", onClick: handlePrint, className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-brand-primary hover:bg-brand-primary/90 rounded-md transition-colors" },
53
+ React.createElement(PrinterIcon, { className: "w-4 h-4" }),
54
+ "Print"),
55
+ React.createElement("button", { type: "button", onClick: handleClose, className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-secondary bg-surface-raised hover:bg-surface-raised rounded-md transition-colors" },
56
+ React.createElement(XMarkIcon, { className: "w-4 h-4" }),
57
+ "Close"))),
58
+ React.createElement("div", { className: "max-w-4xl mx-auto px-6" }, children)));
59
+ }
60
+ //# sourceMappingURL=PrintEnclosure.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Print packlet - popup window for print-optimized content rendering.
3
+ * @packageDocumentation
4
+ */
5
+ export { openPrintWindow } from './openPrintWindow';
6
+ export { PrintEnclosure } from './PrintEnclosure';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,112 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ /**
23
+ * Opens a popup window for print-optimized content rendering.
24
+ * @packageDocumentation
25
+ */
26
+ import React from 'react';
27
+ import { createRoot } from 'react-dom/client';
28
+ import { PrintEnclosure } from './PrintEnclosure';
29
+ /**
30
+ * Clones all stylesheet-related elements from the source document head
31
+ * into the target document head so that styles are available in the popup.
32
+ */
33
+ function cloneStyles(source, target) {
34
+ const styles = Array.from(source.querySelectorAll('style'));
35
+ for (const style of styles) {
36
+ target.head.appendChild(style.cloneNode(true));
37
+ }
38
+ const links = Array.from(source.querySelectorAll('link[rel="stylesheet"]'));
39
+ for (const link of links) {
40
+ target.head.appendChild(link.cloneNode(true));
41
+ }
42
+ }
43
+ /**
44
+ * Opens a popup window and renders the given React content inside a
45
+ * {@link PrintEnclosure} with Print and Close toolbar buttons.
46
+ *
47
+ * @remarks
48
+ * Must be called synchronously from a user click handler to avoid
49
+ * popup blockers. If the popup is blocked, returns `null`.
50
+ *
51
+ * Styles are cloned from the parent document so that Tailwind and other
52
+ * CSS frameworks work in the popup. An independent React root is created
53
+ * in the popup (not a portal) so the popup is fully self-contained.
54
+ *
55
+ * @public
56
+ */
57
+ export function openPrintWindow(content, options) {
58
+ const { title, width = 900, height = 700, toolbarExtras } = options;
59
+ const popup = window.open('', '', `width=${width},height=${height}`);
60
+ if (!popup) {
61
+ return null;
62
+ }
63
+ popup.document.write(`<!DOCTYPE html>
64
+ <html>
65
+ <head>
66
+ <meta charset="utf-8" />
67
+ <title>${title.replace(/</g, '&lt;')}</title>
68
+ <style>
69
+ @media screen {
70
+ body {
71
+ background: #f9fafb;
72
+ padding: 2rem;
73
+ margin: 0;
74
+ }
75
+ }
76
+ @media print {
77
+ body {
78
+ margin: 0;
79
+ padding: 0;
80
+ background: white;
81
+ }
82
+ .print-toolbar {
83
+ display: none !important;
84
+ }
85
+ .break-before-page {
86
+ break-before: page;
87
+ }
88
+ .break-inside-avoid {
89
+ break-inside: avoid;
90
+ }
91
+ }
92
+ </style>
93
+ </head>
94
+ <body>
95
+ <div id="print-root"></div>
96
+ </body>
97
+ </html>`);
98
+ popup.document.close();
99
+ cloneStyles(document, popup.document);
100
+ const container = popup.document.getElementById('print-root');
101
+ if (!container) {
102
+ popup.close();
103
+ return null;
104
+ }
105
+ const root = createRoot(container);
106
+ root.render(React.createElement(PrintEnclosure, { title: title, toolbarExtras: toolbarExtras, popupWindow: popup }, content));
107
+ popup.addEventListener('beforeunload', () => {
108
+ root.unmount();
109
+ });
110
+ return popup;
111
+ }
112
+ //# sourceMappingURL=openPrintWindow.js.map
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import React, { createContext, useContext } from 'react';
23
+ import { useResponsiveLayout } from './useResponsiveLayout';
24
+ const ResponsiveContext = createContext(undefined);
25
+ /**
26
+ * Provides responsive layout information to the component tree via context.
27
+ *
28
+ * Wrap your app (or a subtree) with this provider. Descendants use
29
+ * {@link useResponsive} to read the current layout mode, device type, etc.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * <ResponsiveProvider>
34
+ * <App />
35
+ * </ResponsiveProvider>
36
+ * ```
37
+ * @public
38
+ */
39
+ export function ResponsiveProvider({ forceLayoutMode, children }) {
40
+ const layout = useResponsiveLayout(forceLayoutMode);
41
+ return React.createElement(ResponsiveContext.Provider, { value: layout }, children);
42
+ }
43
+ /**
44
+ * Access the current responsive layout information.
45
+ *
46
+ * Must be called within a {@link ResponsiveProvider}.
47
+ * @public
48
+ */
49
+ export function useResponsive() {
50
+ const context = useContext(ResponsiveContext);
51
+ if (!context) {
52
+ throw new Error('useResponsive must be used within a ResponsiveProvider');
53
+ }
54
+ return context;
55
+ }
56
+ //# sourceMappingURL=ResponsiveProvider.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Responsive layout detection and context provider.
3
+ * @packageDocumentation
4
+ */
5
+ export { useResponsiveLayout } from './useResponsiveLayout';
6
+ export { ResponsiveProvider, useResponsive } from './ResponsiveProvider';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,118 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { useState, useEffect } from 'react';
23
+ /**
24
+ * Breakpoints for responsive design (in CSS pixels).
25
+ */
26
+ const BREAKPOINTS = {
27
+ /** At or below this width, use mobile layout */
28
+ mobile: 640,
29
+ /** At or below this width, use compact layout */
30
+ compact: 1024
31
+ };
32
+ /**
33
+ * Detect if the device supports touch input.
34
+ */
35
+ function detectTouchSupport() {
36
+ var _a;
37
+ return ('ontouchstart' in window ||
38
+ navigator.maxTouchPoints > 0 ||
39
+ ((_a = navigator.msMaxTouchPoints) !== null && _a !== void 0 ? _a : 0) > 0);
40
+ }
41
+ /**
42
+ * Determine device type from viewport dimensions and touch capability.
43
+ */
44
+ function determineDeviceType(width, isTouch) {
45
+ if (width <= BREAKPOINTS.mobile) {
46
+ return 'mobile';
47
+ }
48
+ if (width <= BREAKPOINTS.compact && isTouch) {
49
+ return 'tablet';
50
+ }
51
+ return 'desktop';
52
+ }
53
+ /**
54
+ * Determine the structural layout mode from viewport width.
55
+ */
56
+ function determineLayoutMode(width) {
57
+ if (width <= BREAKPOINTS.mobile) {
58
+ return 'mobile';
59
+ }
60
+ if (width <= BREAKPOINTS.compact) {
61
+ return 'compact';
62
+ }
63
+ return 'full';
64
+ }
65
+ /**
66
+ * Compute the full layout info from current viewport state.
67
+ */
68
+ function computeLayout(forceLayoutMode) {
69
+ const width = window.innerWidth;
70
+ const height = window.innerHeight;
71
+ const isTouchDevice = detectTouchSupport();
72
+ const orientation = width > height ? 'landscape' : 'portrait';
73
+ const deviceType = determineDeviceType(width, isTouchDevice);
74
+ const layoutMode = forceLayoutMode !== null && forceLayoutMode !== void 0 ? forceLayoutMode : determineLayoutMode(width);
75
+ return {
76
+ deviceType,
77
+ orientation,
78
+ layoutMode,
79
+ screenWidth: width,
80
+ screenHeight: height,
81
+ isTouchDevice
82
+ };
83
+ }
84
+ /**
85
+ * Hook that tracks responsive layout information for the current viewport.
86
+ *
87
+ * Listens to `resize` and `orientationchange` events and recomputes layout
88
+ * on every change. Use the `layoutMode` field to drive structural rendering
89
+ * decisions (e.g., sidebar as drawer vs. fixed panel).
90
+ *
91
+ * @param forceLayoutMode - Optional override for testing or storybook use.
92
+ * @public
93
+ */
94
+ export function useResponsiveLayout(forceLayoutMode) {
95
+ const [layout, setLayout] = useState(() => computeLayout(forceLayoutMode));
96
+ useEffect(() => {
97
+ if (forceLayoutMode) {
98
+ return;
99
+ }
100
+ function updateLayout() {
101
+ setLayout(computeLayout());
102
+ }
103
+ window.addEventListener('resize', updateLayout);
104
+ const onOrientationChange = () => {
105
+ // Small delay to let the browser update viewport dimensions
106
+ setTimeout(updateLayout, 100);
107
+ };
108
+ window.addEventListener('orientationchange', onOrientationChange);
109
+ // Sync on mount in case viewport changed between SSR and hydration
110
+ updateLayout();
111
+ return () => {
112
+ window.removeEventListener('resize', updateLayout);
113
+ window.removeEventListener('orientationchange', onOrientationChange);
114
+ };
115
+ }, [forceLayoutMode]);
116
+ return layout;
117
+ }
118
+ //# sourceMappingURL=useResponsiveLayout.js.map