@ramesesinc/platform-core 0.1.6 → 0.1.8

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 (87) hide show
  1. package/dist/components/action/LookupPage.js +9 -31
  2. package/dist/components/action/ViewPage.d.ts +2 -0
  3. package/dist/components/action/ViewPage.js +25 -31
  4. package/dist/components/common/UIComponent.js +4 -3
  5. package/dist/components/table/DataList.js +2 -2
  6. package/dist/components/view/PopupView.d.ts +13 -0
  7. package/dist/components/view/PopupView.js +25 -20
  8. package/dist/core/DataContext.d.ts +7 -4
  9. package/dist/core/DataContext.js +16 -4
  10. package/dist/core/Page.js +25 -26
  11. package/dist/core/PageCache.js +7 -7
  12. package/dist/core/PageContext.js +17 -7
  13. package/dist/core/PageViewContext.d.ts +13 -1
  14. package/dist/core/PageViewContext.js +75 -2
  15. package/dist/core/PopupContext.d.ts +49 -0
  16. package/dist/core/PopupContext.js +380 -0
  17. package/dist/core/RowContext.js +1 -1
  18. package/dist/core/WindowContext.d.ts +15 -0
  19. package/dist/core/WindowContext.js +28 -0
  20. package/dist/core/index.d.ts +16 -0
  21. package/dist/index.css +25 -7
  22. package/dist/lib/utils/BeanUtils.js +7 -7
  23. package/dist/templates/DataListTemplate.js +7 -2
  24. package/dist/templates/ExplorerTemplate.js +1 -1
  25. package/package.json +5 -5
  26. package/dist/components/action/AlertMessage.tsx +0 -38
  27. package/dist/components/action/Button.tsx +0 -230
  28. package/dist/components/action/CancelEdit.tsx +0 -40
  29. package/dist/components/action/DeleteData.tsx +0 -73
  30. package/dist/components/action/Edit.tsx +0 -40
  31. package/dist/components/action/LookupPage.tsx +0 -113
  32. package/dist/components/action/ProcessRunner.tsx +0 -337
  33. package/dist/components/action/Refresh.tsx +0 -35
  34. package/dist/components/action/SaveData.tsx +0 -74
  35. package/dist/components/action/SelectData.tsx +0 -47
  36. package/dist/components/action/Undo.tsx +0 -50
  37. package/dist/components/action/UpdateData.tsx +0 -49
  38. package/dist/components/action/UpdateState.tsx +0 -40
  39. package/dist/components/action/ViewBackPage.tsx +0 -46
  40. package/dist/components/action/ViewPage.tsx +0 -141
  41. package/dist/components/common/UIComponent.tsx +0 -86
  42. package/dist/components/common/UIInput.tsx +0 -49
  43. package/dist/components/common/UIMenu.tsx +0 -91
  44. package/dist/components/index.ts +0 -51
  45. package/dist/components/input/CodeEditor.tsx +0 -188
  46. package/dist/components/input/DateField.tsx +0 -274
  47. package/dist/components/input/DayPicker.tsx +0 -5
  48. package/dist/components/input/HtmlCode.tsx +0 -203
  49. package/dist/components/input/JsonCode.tsx +0 -205
  50. package/dist/components/input/MonthPicker.tsx +0 -5
  51. package/dist/components/input/ScriptCode.tsx +0 -195
  52. package/dist/components/input/Select.tsx +0 -78
  53. package/dist/components/input/SqlCode.tsx +0 -162
  54. package/dist/components/input/StringDecision.tsx +0 -64
  55. package/dist/components/input/Text.tsx +0 -57
  56. package/dist/components/input/YearPicker.tsx +0 -81
  57. package/dist/components/list/IconMenu.tsx +0 -115
  58. package/dist/components/list/TabMenu.tsx +0 -127
  59. package/dist/components/list/TreeMenu.tsx +0 -279
  60. package/dist/components/list/TxnTaskList.tsx +0 -198
  61. package/dist/components/output/Label.tsx +0 -50
  62. package/dist/components/table/DataList.tsx +0 -820
  63. package/dist/components/table/DataTable.tsx +0 -572
  64. package/dist/components/table/ListHandler.ts +0 -276
  65. package/dist/components/table/TableContext.tsx +0 -122
  66. package/dist/components/view/ComponentView.tsx +0 -102
  67. package/dist/components/view/FilterView.tsx +0 -21
  68. package/dist/components/view/HtmlForm.tsx +0 -176
  69. package/dist/components/view/HtmlView.tsx +0 -98
  70. package/dist/components/view/IFrameView.tsx +0 -48
  71. package/dist/components/view/Modal.tsx +0 -72
  72. package/dist/components/view/PageView.tsx +0 -131
  73. package/dist/components/view/PopupView.tsx +0 -160
  74. package/dist/components/view/RootView.tsx +0 -109
  75. package/dist/components/view/WizardView.tsx +0 -48
  76. package/dist/lib/layouts/BorderLayout.tsx +0 -31
  77. package/dist/lib/layouts/CardLayout.tsx +0 -73
  78. package/dist/lib/layouts/CenterLayout.tsx +0 -20
  79. package/dist/lib/layouts/GridLayout.tsx +0 -20
  80. package/dist/lib/layouts/HPanel.tsx +0 -31
  81. package/dist/lib/layouts/HorizontalLayout.tsx +0 -29
  82. package/dist/lib/layouts/MainLayout.tsx +0 -16
  83. package/dist/lib/layouts/PageLayout.tsx +0 -29
  84. package/dist/lib/layouts/VPanel.tsx +0 -27
  85. package/dist/lib/layouts/XLayout.tsx +0 -29
  86. package/dist/lib/layouts/YLayout.tsx +0 -29
  87. package/dist/lib/layouts/index.ts +0 -13
@@ -1,14 +1,26 @@
1
1
  import React from "react";
2
- import { EventHandler } from ".";
2
+ import { ChangeEventHandler, EventHandler, VoidCallback } from ".";
3
3
  export type PageOptions = {
4
4
  mode?: "window" | "popup";
5
5
  [key: string]: any;
6
6
  };
7
+ export type PageViewUI = {
8
+ onChange: (handler: ChangeEventHandler) => VoidCallback;
9
+ notifyChange: (name: string | null, value: any) => void;
10
+ set: (name: string, value: any, autoPublish?: boolean) => void;
11
+ get: (name: string | null) => any;
12
+ getTitle: () => string | null;
13
+ setTitle: (value: string | null) => void;
14
+ };
7
15
  export type PageViewContextValue = {
8
16
  uuid: string;
9
17
  parentContext: PageViewContextValue | null;
10
18
  stack: PageViewContextValue[];
11
19
  paths: string[];
20
+ getInfo: () => Record<string, any>;
21
+ getUI: () => PageViewUI;
22
+ getTitle: () => string | null;
23
+ setTitle: (title: string | null) => void;
12
24
  setPage: (page: string | null, options?: PageOptions) => void;
13
25
  setSelectedPage: (page: string | null) => void;
14
26
  getSelectedPage: () => string | null;
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
- import { useApp } from "./AppContext";
5
4
  /* ------------------ Global Stack ------------------ */
6
5
  const contextStack = [];
7
6
  /* ------------------ Context ------------------ */
@@ -10,6 +9,10 @@ const PageViewContext = createContext({
10
9
  parentContext: null,
11
10
  stack: [],
12
11
  paths: [],
12
+ getInfo: () => ({}),
13
+ getUI: () => ({}),
14
+ getTitle: () => null,
15
+ setTitle: () => { },
13
16
  setPage: () => { },
14
17
  getSelectedPage: () => null,
15
18
  pushPage: () => { },
@@ -22,10 +25,59 @@ const PageViewContext = createContext({
22
25
  getOriginalLocationInfo: () => null,
23
26
  getEventHandler: () => null,
24
27
  });
28
+ const usePageViewUI = () => {
29
+ const propsRef = useRef({});
30
+ const changeHandlersRef = useRef([]);
31
+ const onChange = (handler) => {
32
+ if (handler != null && changeHandlersRef.current.indexOf(handler) < 0) {
33
+ changeHandlersRef.current.push(handler);
34
+ }
35
+ return () => {
36
+ if (handler == null)
37
+ return;
38
+ const list = changeHandlersRef.current;
39
+ const index = list.indexOf(handler);
40
+ if (index >= 0)
41
+ list.splice(index, 1);
42
+ };
43
+ };
44
+ const notifyChange = (name, value) => {
45
+ const e = { name, value };
46
+ const list = [...changeHandlersRef.current];
47
+ list.forEach((handler) => {
48
+ try {
49
+ handler(e);
50
+ }
51
+ catch (err) {
52
+ // do nothing: let the handler handle its own error
53
+ }
54
+ });
55
+ };
56
+ const set = (name, value, autoPublish) => {
57
+ if (name == null)
58
+ return;
59
+ propsRef.current[name] = value;
60
+ if (String(autoPublish) === 'true') {
61
+ notifyChange(name, value);
62
+ }
63
+ };
64
+ const get = (name) => {
65
+ var _a;
66
+ const value = name == null ? null : (_a = propsRef.current[name]) !== null && _a !== void 0 ? _a : null;
67
+ return value;
68
+ };
69
+ const getTitle = () => {
70
+ return get("title");
71
+ };
72
+ const setTitle = (value) => {
73
+ set("title", value, false);
74
+ };
75
+ const result = { onChange, notifyChange, set, get, getTitle, setTitle };
76
+ return result;
77
+ };
25
78
  export const PageViewProvider = ({ paths, handle, children, prefix = "page", eventHandler = {}, }) => {
26
79
  const parentContext = useContext(PageViewContext);
27
80
  const [uuid] = useState(() => `${prefix}-${Math.random().toString(36).slice(2)}`);
28
- const app = useApp();
29
81
  const [hasSelectionHandling, setSelectionHandling] = useState(false);
30
82
  const isStandalone = useCallback(() => {
31
83
  return handle == null ? true : handle.isStandalone();
@@ -111,6 +163,7 @@ export const PageViewProvider = ({ paths, handle, children, prefix = "page", eve
111
163
  }
112
164
  return result;
113
165
  });
166
+ const titleRef = useRef(null);
114
167
  const handleRef = useRef(handle);
115
168
  useLayoutEffect(() => {
116
169
  handleRef.current = handle;
@@ -143,6 +196,7 @@ export const PageViewProvider = ({ paths, handle, children, prefix = "page", eve
143
196
  const { current: currentChain, previous: previousChain } = pageChain;
144
197
  const newChainInfo = { path: "", base: "", page: "", options: pageOptions };
145
198
  const newPageChain = { current: newChainInfo, previous: null };
199
+ // console.log("setPage mode => ", { page, pageOptions });
146
200
  if (mode != null && mode === "window") {
147
201
  newChainInfo.page = page;
148
202
  newChainInfo.base = locInfo.root;
@@ -156,6 +210,7 @@ export const PageViewProvider = ({ paths, handle, children, prefix = "page", eve
156
210
  }
157
211
  setPageChain((prev) => (Object.assign({}, newPageChain)));
158
212
  if (!standalone) {
213
+ // console.log("setPage push state => ", { newChainInfo });
159
214
  history.pushState(null, "", newChainInfo.path);
160
215
  }
161
216
  (_a = handleRef.current) === null || _a === void 0 ? void 0 : _a.renderPage(page);
@@ -241,11 +296,29 @@ export const PageViewProvider = ({ paths, handle, children, prefix = "page", eve
241
296
  const { path } = previous.current;
242
297
  return path != null && path !== "";
243
298
  };
299
+ const getInfo = () => {
300
+ var _a;
301
+ return {
302
+ uuid,
303
+ prefix,
304
+ isStandalone: isStandalone(),
305
+ hasBackPage: hasBackPage(),
306
+ current: pageChain.current,
307
+ previous: (_a = pageChain.previous) === null || _a === void 0 ? void 0 : _a.current,
308
+ };
309
+ };
310
+ const ui = usePageViewUI();
244
311
  const values = {
245
312
  parentContext,
246
313
  uuid,
247
314
  stack: [...((parentContext === null || parentContext === void 0 ? void 0 : parentContext.stack) || [])],
248
315
  paths,
316
+ getInfo,
317
+ getUI: () => ui,
318
+ getTitle: () => titleRef.current,
319
+ setTitle: (title) => {
320
+ titleRef.current = title;
321
+ },
249
322
  setPage,
250
323
  setSelectedPage,
251
324
  getSelectedPage: () => {
@@ -0,0 +1,49 @@
1
+ import { ReactNode } from "react";
2
+ export type PopupOptions = {
3
+ modal?: boolean;
4
+ draggable?: boolean;
5
+ showCloseButton?: boolean;
6
+ showTitle?: boolean;
7
+ minWidth?: number | string;
8
+ minHeight?: number | string;
9
+ width?: number | string;
10
+ height?: number | string;
11
+ className?: string;
12
+ title?: string | null;
13
+ };
14
+ export type PopupState = PopupOptions & {
15
+ id: string;
16
+ counter: number;
17
+ visible: boolean;
18
+ content: ReactNode | null;
19
+ options: PopupOptions;
20
+ };
21
+ export interface PopupInstance {
22
+ id: string;
23
+ setContent: (content: ReactNode) => void;
24
+ show: () => void;
25
+ hide: () => void;
26
+ close: () => void;
27
+ }
28
+ export interface PopupManager {
29
+ create: (options?: Partial<PopupOptions>) => PopupInstance;
30
+ closeAll: () => void;
31
+ }
32
+ declare const popupMgr: PopupManager;
33
+ export declare function PopupManagerSlot(): import("react/jsx-runtime").JSX.Element;
34
+ export declare function PopupManagerProvider({ children }: {
35
+ children: ReactNode;
36
+ }): import("react/jsx-runtime").JSX.Element;
37
+ export declare function usePopupManager(): PopupManager;
38
+ export type PopupSlotContextValue = {
39
+ getId: () => string;
40
+ close: () => void;
41
+ setTitle: (title: string | null) => void;
42
+ };
43
+ export declare function usePopupContext(): PopupSlotContextValue | null;
44
+ type PopupSlotProviderProps = {
45
+ data: PopupState;
46
+ children: ReactNode;
47
+ };
48
+ export declare function PopupSlotProvider(props: PopupSlotProviderProps): import("react/jsx-runtime").JSX.Element;
49
+ export { popupMgr };
@@ -0,0 +1,380 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // src/core/PageContext.tsx
3
+ import { mergeCN } from "@ramesesinc/client";
4
+ import { createContext, useContext, useRef, useEffect, useLayoutEffect, useState, useSyncExternalStore, useMemo, } from "react";
5
+ //
6
+ // CONTEXT
7
+ //
8
+ const PopupManagerContext = createContext(null);
9
+ function createPopupStore() {
10
+ let popups = [];
11
+ const listeners = new Set();
12
+ function emit() {
13
+ listeners.forEach((l) => {
14
+ // console.log("emitting listener =>", l);
15
+ l();
16
+ });
17
+ }
18
+ function subscribe(listener) {
19
+ listeners.add(listener);
20
+ // console.log("subscribers", listeners);
21
+ return () => listeners.delete(listener);
22
+ }
23
+ function getSnapshot() {
24
+ return popups;
25
+ }
26
+ function addPopup(state) {
27
+ popups = [...popups, state];
28
+ emit();
29
+ }
30
+ function updatePopup(id, patch) {
31
+ if (patch == null || Object.keys(patch).length === 0)
32
+ return;
33
+ let state_names = ["visible", "content"];
34
+ const popupState_patch = {};
35
+ Object.keys(patch).forEach((key) => {
36
+ if (state_names.indexOf(key) >= 0) {
37
+ popupState_patch[key] = patch[key];
38
+ }
39
+ });
40
+ state_names = [
41
+ "modal", "draggable", "showCloseButton",
42
+ "minWidth", "minHeight", "className", "title",
43
+ "width", "height"
44
+ ];
45
+ const popupOptions_patch = {};
46
+ Object.keys(patch).forEach((key) => {
47
+ if (state_names.indexOf(key) >= 0) {
48
+ popupOptions_patch[key] = patch[key];
49
+ }
50
+ });
51
+ // console.log("updatePopup", id, popupState_patch, popupOptions_patch);
52
+ popups = popups.map((p) => {
53
+ var _a;
54
+ if (p.id === id) {
55
+ return Object.assign(Object.assign(Object.assign({}, p), popupState_patch), { options: Object.assign(Object.assign({}, ((_a = p.options) !== null && _a !== void 0 ? _a : {})), popupOptions_patch) });
56
+ }
57
+ return p;
58
+ });
59
+ emit();
60
+ }
61
+ function removePopup(id) {
62
+ popups = popups.filter((p) => p.id !== id);
63
+ emit();
64
+ }
65
+ function removeAll() {
66
+ popups = [];
67
+ emit();
68
+ }
69
+ return {
70
+ subscribe,
71
+ getSnapshot,
72
+ addPopup,
73
+ updatePopup,
74
+ removePopup,
75
+ removeAll,
76
+ };
77
+ }
78
+ // Singleton store
79
+ const store = createPopupStore();
80
+ //
81
+ // POPUP MANAGER
82
+ //
83
+ const counterRef = { current: 0 };
84
+ function createPopupManager() {
85
+ const create = (initialOptions) => {
86
+ // console.log("create initialOptions => ", initialOptions);
87
+ const counter = ++counterRef.current;
88
+ const id = `popup-${counter}`;
89
+ const options = Object.assign({ modal: true, draggable: true, showCloseButton: true, minWidth: 400, minHeight: 200, className: "" }, (initialOptions !== null && initialOptions !== void 0 ? initialOptions : {}));
90
+ const state = {
91
+ id,
92
+ counter,
93
+ content: null,
94
+ visible: false,
95
+ options
96
+ };
97
+ store.addPopup(state);
98
+ return {
99
+ id,
100
+ setContent(content) {
101
+ store.updatePopup(id, { content });
102
+ },
103
+ show() {
104
+ store.updatePopup(id, { visible: true });
105
+ },
106
+ hide() {
107
+ // store.updatePopup(id, { visible: false });
108
+ store.removePopup(id);
109
+ },
110
+ close() {
111
+ store.removePopup(id);
112
+ },
113
+ };
114
+ };
115
+ const closeAll = () => {
116
+ store.removeAll();
117
+ };
118
+ return { create, closeAll };
119
+ }
120
+ // Singleton manager
121
+ const popupMgr = createPopupManager();
122
+ //
123
+ // POPUP OVERLAY COMPONENT
124
+ //
125
+ function PopupContentLayout({ data, style = null, children, }) {
126
+ const { options = {} } = data !== null && data !== void 0 ? data : {};
127
+ const { showCloseButton = true, showTitle = false, title, className = "" } = options;
128
+ const preferredStyles = Object.assign({}, (style !== null && style !== void 0 ? style : {}));
129
+ const layoutContentClass = mergeCN("min-w-[200px] min-h-[100px] overflow-auto p-0", className);
130
+ const getTitleStyles = () => {
131
+ const cssProps = {};
132
+ if ((title !== null && title !== void 0 ? title : "").trim() === "" && !showCloseButton) {
133
+ cssProps.paddingTop = '8px';
134
+ }
135
+ return cssProps;
136
+ };
137
+ const handleClose = () => {
138
+ store.removePopup(String(data.id));
139
+ };
140
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-row gap-2", children: [_jsx("label", { id: "layout-title", className: "popup-drag-handle flex-1 p-0 text-sm font-semibold text-gray-800 truncate min-w-[100px]", style: Object.assign({ paddingLeft: "8px", cursor: 'grab', userSelect: 'none' }, getTitleStyles()), children: showTitle ? (title !== null && title !== void 0 ? title : "") : "" }), showCloseButton && (_jsx("button", { id: "layout-button", onClick: handleClose, className: "flex flex-shrink-1 justify-center items-center p-0 m-0 w-6 h-6 rounded-md font-bold text-gray-400 hover:text-gray-700 hover:bg-gray-100", style: { fontSize: "14px" }, children: "\u2715" }))] }), _jsx("div", { id: "layout-content", className: layoutContentClass, style: Object.assign({ padding: "10px", paddingTop: "4px" }, preferredStyles), children: children !== null && children !== void 0 ? children : null })] }));
141
+ }
142
+ function PopupOverlay({ popup }) {
143
+ var _a;
144
+ const modalRef = useRef(null);
145
+ const [layout, setLayout] = useState({ opacity: 0, top: 0, left: 0 });
146
+ const [isDragging, setIsDragging] = useState(false);
147
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
148
+ const [windowStart, setWindowStart] = useState({ x: 0, y: 0 });
149
+ const { visible: isVisible = false, options: popupOptions = {} } = popup;
150
+ const { modal: isModal = true, draggable: isDraggable = true, showCloseButton: allowShowCloseButton = true, minWidth = 300, minHeight = 150, width, height, className = "", } = popupOptions;
151
+ // Get all visible popups to determine backdrop count
152
+ const allPopups = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
153
+ const visibleModalPopups = allPopups.filter(p => p.visible && isModal);
154
+ const isLastModalPopup = ((_a = visibleModalPopups.at(-1)) === null || _a === void 0 ? void 0 : _a.id) === popup.id;
155
+ // --- LOGIC: DRAG FUNCTIONALITY ---
156
+ // Handle mouse events for dragging the popup window
157
+ useEffect(() => {
158
+ if (!isVisible)
159
+ return;
160
+ const handleMouseMove = (e) => {
161
+ if (!isDraggable)
162
+ return;
163
+ if (!isDragging)
164
+ return;
165
+ const deltaX = e.clientX - dragStart.x;
166
+ const deltaY = e.clientY - dragStart.y;
167
+ setLayout(prev => (Object.assign(Object.assign({}, prev), { top: Math.max(0, windowStart.y + deltaY), left: Math.max(0, windowStart.x + deltaX) })));
168
+ };
169
+ const handleMouseUp = () => {
170
+ setIsDragging(false);
171
+ };
172
+ if (isDragging) {
173
+ document.addEventListener('mousemove', handleMouseMove);
174
+ document.addEventListener('mouseup', handleMouseUp);
175
+ document.body.style.userSelect = 'none';
176
+ document.body.style.cursor = 'grabbing';
177
+ }
178
+ return () => {
179
+ document.removeEventListener('mousemove', handleMouseMove);
180
+ document.removeEventListener('mouseup', handleMouseUp);
181
+ document.body.style.userSelect = '';
182
+ document.body.style.cursor = '';
183
+ };
184
+ }, [isDragging, dragStart, windowStart, isVisible]);
185
+ const handleMouseDown = (e) => {
186
+ if (!isDraggable)
187
+ return;
188
+ // Allow dragging when clicking on the popup container or drag handle
189
+ const target = e.target;
190
+ const isDragHandle = target.classList.contains('popup-drag-handle') ||
191
+ target.closest('.popup-drag-handle');
192
+ if (target !== modalRef.current && !isDragHandle)
193
+ return;
194
+ setIsDragging(true);
195
+ setDragStart({ x: e.clientX, y: e.clientY });
196
+ setWindowStart({ x: layout.left, y: layout.top });
197
+ };
198
+ // --- LOGIC: SCROLL LOCK ---
199
+ // Prevents background body from scrolling while a MODAL is open
200
+ useEffect(() => {
201
+ if (isVisible && isModal) {
202
+ const originalStyle = window.getComputedStyle(document.body).overflow;
203
+ document.body.style.overflow = "hidden";
204
+ return () => {
205
+ document.body.style.overflow = originalStyle;
206
+ };
207
+ }
208
+ }, [isVisible, isModal]);
209
+ // --- LOGIC: FOCUS TRAP ---
210
+ // Keeps keyboard navigation inside the modal
211
+ useEffect(() => {
212
+ if (!isVisible || !isModal)
213
+ return;
214
+ const element = modalRef.current;
215
+ if (!element)
216
+ return;
217
+ // A. Focus the modal container immediately
218
+ element.focus();
219
+ // B. Handle Tab Key & Escape Key
220
+ const handleKeyDown = (e) => {
221
+ if (e.key === "Tab") {
222
+ const focusable = element.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
223
+ if (focusable.length === 0) {
224
+ e.preventDefault();
225
+ return;
226
+ }
227
+ const first = focusable[0];
228
+ const last = focusable[focusable.length - 1];
229
+ // Shift + Tab: Loop to last
230
+ if (e.shiftKey && document.activeElement === first) {
231
+ e.preventDefault();
232
+ last.focus();
233
+ }
234
+ // Tab: Loop to first
235
+ else if (!e.shiftKey && document.activeElement === last) {
236
+ e.preventDefault();
237
+ first.focus();
238
+ }
239
+ }
240
+ // Escape Key: Don't close automatically - let user handle via close button
241
+ // if (e.key === "Escape") {
242
+ // store.removePopup(popup.id);
243
+ // }
244
+ };
245
+ document.addEventListener("keydown", handleKeyDown);
246
+ return () => {
247
+ document.removeEventListener("keydown", handleKeyDown);
248
+ };
249
+ }, [isVisible, isModal, popup.id]);
250
+ const padding = { top: 4, right: 4, bottom: 4, left: 4 };
251
+ // --- LOGIC: POSITIONING ---
252
+ // Measures content size before showing to center it perfectly
253
+ useLayoutEffect(() => {
254
+ if (!isVisible)
255
+ return;
256
+ const element = modalRef.current;
257
+ if (!element)
258
+ return;
259
+ const measureAndCenter = () => {
260
+ var _a, _b, _c, _d;
261
+ let { width, height } = element.getBoundingClientRect();
262
+ width = Math.trunc(width);
263
+ height = Math.trunc(height);
264
+ const newlayout = { opacity: 1, top: 0, left: 0 };
265
+ const winWidth = Math.trunc(window.innerWidth);
266
+ const winHeight = Math.trunc(window.innerHeight);
267
+ if (width > winWidth) {
268
+ width = winWidth;
269
+ newlayout.width = width;
270
+ }
271
+ if (height > winHeight) {
272
+ height = winHeight;
273
+ newlayout.height = height;
274
+ }
275
+ newlayout.top = Math.max(0, (winHeight - height) / 2);
276
+ newlayout.left = Math.max(0, (winWidth - width) / 2);
277
+ newlayout.top = Math.max(0, newlayout.top - padding.top);
278
+ newlayout.left = Math.max(0, newlayout.left - padding.left);
279
+ // console.log("measure dimensions", { width, height, winWidth, winHeight, newlayout });
280
+ const oldlayoutStr = `top=${layout.top}, left=${layout.left}, width=${(_a = layout.width) !== null && _a !== void 0 ? _a : null}, height=${(_b = layout.height) !== null && _b !== void 0 ? _b : null}`;
281
+ const newlayoutStr = `top=${newlayout.top}, left=${newlayout.left}, width=${(_c = newlayout.width) !== null && _c !== void 0 ? _c : null}, height=${(_d = newlayout.height) !== null && _d !== void 0 ? _d : null}`;
282
+ const hasChanged = (oldlayoutStr !== newlayoutStr);
283
+ // console.log("layout change", { hasChanged, old: oldlayoutStr, new: newlayoutStr });
284
+ if (hasChanged)
285
+ setLayout(newlayout);
286
+ };
287
+ // initial computation
288
+ measureAndCenter();
289
+ // Watch for size changes
290
+ const resizeObserver = new ResizeObserver(() => {
291
+ measureAndCenter();
292
+ });
293
+ resizeObserver.observe(element);
294
+ return () => {
295
+ resizeObserver.disconnect();
296
+ };
297
+ }, [isVisible]);
298
+ // Reset layout when hidden
299
+ useEffect(() => {
300
+ if (!isVisible) {
301
+ setLayout({ opacity: 0, top: 0, left: 0, width: 0, height: 0 });
302
+ }
303
+ }, [isVisible]);
304
+ // --- RENDER ---
305
+ // If hidden, render nothing (unmounts children)
306
+ if (!isVisible)
307
+ return null;
308
+ const zIndex = 10000 + (popup.counter * 25);
309
+ const layoutStyles = { top: layout.top, left: layout.left };
310
+ if (width != null && String(width).trim() !== '') {
311
+ let sval = String(width).trim().toLowerCase();
312
+ if (!sval.endsWith("%") && !sval.endsWith("px")) {
313
+ sval = `${sval}px`;
314
+ }
315
+ layoutStyles.width = sval;
316
+ }
317
+ if (height != null && String(height).trim() !== '') {
318
+ let sval = String(height).trim().toLowerCase();
319
+ if (!sval.endsWith("%") && !sval.endsWith("px")) {
320
+ sval = `${sval}px`;
321
+ }
322
+ layoutStyles.height = sval;
323
+ }
324
+ return (_jsxs(_Fragment, { children: [isModal && (_jsx("div", {
325
+ // Backdrop click handler removed - don't close automatically
326
+ style: {
327
+ position: "fixed",
328
+ inset: 0,
329
+ backgroundColor: isLastModalPopup ? "rgba(0,0,0,0.5)" : "transparent",
330
+ zIndex: zIndex - 4,
331
+ pointerEvents: "auto",
332
+ cursor: "default",
333
+ }, "aria-hidden": "true" })), _jsx("div", { ref: modalRef, className: `popup-manager-window ${className}`, "data-popup-id": popup.id, role: "dialog", "aria-modal": isModal ? "true" : "false", tabIndex: -1, onMouseDown: handleMouseDown, style: Object.assign({ position: "fixed", zIndex: zIndex, minWidth,
334
+ minHeight, opacity: layout.opacity, backgroundColor: "white", boxShadow: "0 4px 20px rgba(0,0,0,0.3)", borderRadius: "8px", padding: "4px", boxSizing: "border-box", outline: "none", pointerEvents: "auto" }, layoutStyles), children: _jsx(PopupSlotProvider, { data: popup, children: _jsx(PopupContentLayout, { data: popup, children: popup.content }) }) })] }));
335
+ }
336
+ //
337
+ // PUBLIC COMPONENTS
338
+ //
339
+ export function PopupManagerSlot() {
340
+ const popups = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
341
+ return (_jsx(PopupManagerContext.Provider, { value: popupMgr, children: popups.map((p) => {
342
+ return _jsx(PopupOverlay, { popup: p }, p.id);
343
+ }) }));
344
+ }
345
+ export function PopupManagerProvider({ children }) {
346
+ return (_jsx(PopupManagerContext.Provider, { value: popupMgr, children: children }));
347
+ }
348
+ //
349
+ // HOOK
350
+ //
351
+ export function usePopupManager() {
352
+ const ctx = useContext(PopupManagerContext);
353
+ if (!ctx) {
354
+ throw new Error("usePopupManager must be used within PopupManagerProvider or PopupManagerSlot");
355
+ }
356
+ return ctx;
357
+ }
358
+ const PopupSlotContext = createContext(null);
359
+ export function usePopupContext() {
360
+ const ctx = useContext(PopupSlotContext);
361
+ return ctx;
362
+ }
363
+ export function PopupSlotProvider(props) {
364
+ const { data, children } = props !== null && props !== void 0 ? props : {};
365
+ const { id: popupId } = data !== null && data !== void 0 ? data : {};
366
+ const value = useMemo(() => ({
367
+ getId: () => String(popupId),
368
+ close: () => {
369
+ store.removePopup(String(popupId));
370
+ },
371
+ setTitle: (title) => {
372
+ store.updatePopup(String(popupId), { title });
373
+ }
374
+ }), [data]);
375
+ return (_jsx(PopupSlotContext.Provider, { value: value, children: children }));
376
+ }
377
+ //
378
+ // EXPORTS
379
+ //
380
+ export { popupMgr };
@@ -7,7 +7,7 @@ export const useRowContext = () => {
7
7
  return context;
8
8
  };
9
9
  export const RowProvider = ({ data, children }) => {
10
- const dataWrapper = useCreateContextValue(data !== null && data !== void 0 ? data : {});
10
+ const dataWrapper = useCreateContextValue(data !== null && data !== void 0 ? data : {}, "row");
11
11
  const values = {
12
12
  getData: () => dataWrapper.getData(),
13
13
  };
@@ -0,0 +1,15 @@
1
+ import { PageDef } from ".";
2
+ export type WindowContextValue = {
3
+ getPageDef: () => PageDef | null;
4
+ setPageDef: (def: PageDef | null) => void;
5
+ getTitle: () => string | null;
6
+ setTitle: (title: string | null) => void;
7
+ };
8
+ export declare const useWindowContext: () => WindowContextValue;
9
+ export type WindowHandleRef = {};
10
+ type WindowContextProviderProps = {
11
+ handle?: WindowHandleRef;
12
+ children: React.ReactNode;
13
+ };
14
+ export declare const WindowContextProvider: (props: WindowContextProviderProps) => import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo, useRef } from "react";
3
+ const createDefaultValue = () => ({
4
+ getPageDef: () => null,
5
+ setPageDef: () => { },
6
+ getTitle: () => null,
7
+ setTitle: () => { },
8
+ });
9
+ const WindowContext = createContext(createDefaultValue());
10
+ export const useWindowContext = () => {
11
+ const context = useContext(WindowContext);
12
+ return context;
13
+ };
14
+ export const WindowContextProvider = (props) => {
15
+ const { handle, children } = props !== null && props !== void 0 ? props : {};
16
+ const dataRef = useRef({ def: null, title: null });
17
+ const value = useMemo(() => ({
18
+ getPageDef: () => dataRef.current.def,
19
+ setPageDef: (def) => {
20
+ dataRef.current.def = def;
21
+ },
22
+ getTitle: () => { var _a, _b; return (_b = (_a = dataRef.current) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : null; },
23
+ setTitle: (title) => {
24
+ dataRef.current.title = title;
25
+ },
26
+ }), [handle]);
27
+ return (_jsx(WindowContext.Provider, { value: value, children: children !== null && children !== void 0 ? children : null }));
28
+ };
@@ -1,5 +1,21 @@
1
1
  export * from "./auth";
2
2
  export { verifyToken, getUser, buildSessionHeader } from "./auth/session";
3
+ export type ChangeEvent = Record<string, any> & {
4
+ name: string | null;
5
+ value: any;
6
+ };
7
+ export type ChangeEventHandler = (e: ChangeEvent) => void;
8
+ export type VoidCallback = () => void;
3
9
  export type EventHandler = {
4
10
  [key: string]: (...args: any[]) => any;
5
11
  };
12
+ export type PageDef = {
13
+ _id: string;
14
+ title?: string;
15
+ template: string;
16
+ data?: {
17
+ api: string;
18
+ params?: Record<string, any>;
19
+ };
20
+ [key: string]: any;
21
+ };