@deadragdoll/reactnu 0.1.75 → 0.1.76

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.
package/dist/index.cjs CHANGED
@@ -3124,28 +3124,93 @@ function getNumericStyleValue(value) {
3124
3124
  }
3125
3125
  return void 0;
3126
3126
  }
3127
+ function getWindowStorageKey(windowStoreKey) {
3128
+ const normalizedKey = windowStoreKey?.trim();
3129
+ return normalizedKey ? `reactnu.window.${normalizedKey}` : null;
3130
+ }
3131
+ function readStoredWindowSnapshot(windowStoreKey) {
3132
+ const storageKey = getWindowStorageKey(windowStoreKey);
3133
+ if (!storageKey || typeof window === "undefined") {
3134
+ return void 0;
3135
+ }
3136
+ try {
3137
+ const rawValue = window.localStorage.getItem(storageKey);
3138
+ if (!rawValue) {
3139
+ return void 0;
3140
+ }
3141
+ const parsedValue = JSON.parse(rawValue);
3142
+ if (!parsedValue || typeof parsedValue !== "object") {
3143
+ return void 0;
3144
+ }
3145
+ const storedValue = parsedValue;
3146
+ const snapshot = {};
3147
+ for (const property of ["height", "width"]) {
3148
+ const value = storedValue[property];
3149
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
3150
+ snapshot[property] = value;
3151
+ }
3152
+ }
3153
+ for (const property of ["left", "top"]) {
3154
+ const value = storedValue[property];
3155
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
3156
+ snapshot[property] = value;
3157
+ }
3158
+ }
3159
+ for (const property of ["maximized", "minimized"]) {
3160
+ const value = storedValue[property];
3161
+ if (typeof value === "boolean") {
3162
+ snapshot[property] = value;
3163
+ }
3164
+ }
3165
+ return Object.keys(snapshot).length > 0 ? snapshot : void 0;
3166
+ } catch {
3167
+ return void 0;
3168
+ }
3169
+ }
3170
+ function writeStoredWindowSnapshot(windowStoreKey, snapshot) {
3171
+ const storageKey = getWindowStorageKey(windowStoreKey);
3172
+ if (!storageKey || typeof window === "undefined") {
3173
+ return;
3174
+ }
3175
+ try {
3176
+ window.localStorage.setItem(storageKey, JSON.stringify(snapshot));
3177
+ } catch {
3178
+ }
3179
+ }
3127
3180
  function resolveSavedWindowStyle(definition, mode, ownerCenteredStyle, index) {
3128
- const snapshot = definition.onOpen?.();
3129
- const savedStyle = snapshot ? {
3130
- height: snapshot.height,
3131
- left: snapshot.left,
3132
- top: snapshot.top,
3133
- transform: snapshot.left !== void 0 || snapshot.top !== void 0 ? "none" : void 0,
3134
- width: snapshot.width
3135
- } : void 0;
3181
+ const snapshot = definition.onOpen?.() ?? readStoredWindowSnapshot(definition.windowStoreKey);
3182
+ const savedStyle = {};
3183
+ if (snapshot) {
3184
+ if (snapshot.height !== void 0) {
3185
+ savedStyle.height = snapshot.height;
3186
+ }
3187
+ if (snapshot.left !== void 0) {
3188
+ savedStyle.left = snapshot.left;
3189
+ }
3190
+ if (snapshot.top !== void 0) {
3191
+ savedStyle.top = snapshot.top;
3192
+ }
3193
+ if (snapshot.width !== void 0) {
3194
+ savedStyle.width = snapshot.width;
3195
+ }
3196
+ if (snapshot.left !== void 0 || snapshot.top !== void 0) {
3197
+ savedStyle.transform = "none";
3198
+ }
3199
+ }
3136
3200
  return {
3137
3201
  maximized: snapshot?.maximized === true,
3138
3202
  minimized: snapshot?.minimized === true,
3139
- restoreStyle: snapshot?.maximized === true && savedStyle ? {
3203
+ restoreStyle: snapshot?.maximized ? {
3140
3204
  ...getDefaultWindowStyle(mode, index),
3141
3205
  ...ownerCenteredStyle,
3206
+ ...definition.style,
3142
3207
  ...savedStyle
3143
3208
  } : void 0,
3144
3209
  style: {
3145
3210
  ...getDefaultWindowStyle(mode, index),
3146
3211
  ...ownerCenteredStyle,
3147
- ...savedStyle,
3148
- ...definition.style
3212
+ ...definition.style,
3213
+ ...savedStyle
3149
3214
  }
3150
3215
  };
3151
3216
  }
@@ -3671,6 +3736,17 @@ function NuWindowProvider({
3671
3736
  (0, import_react19.useEffect)(() => {
3672
3737
  onAppModalChange?.(hasAppModal);
3673
3738
  }, [hasAppModal, onAppModalChange]);
3739
+ (0, import_react19.useEffect)(() => {
3740
+ for (const windowEntry of windows) {
3741
+ if (!windowEntry.windowStoreKey) {
3742
+ continue;
3743
+ }
3744
+ writeStoredWindowSnapshot(
3745
+ windowEntry.windowStoreKey,
3746
+ resolveManagedWindowSnapshot(windowEntry, windowBoundsById)
3747
+ );
3748
+ }
3749
+ }, [windowBoundsById, windows]);
3674
3750
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(NuWindowContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: ["nu-window-host", className].filter(Boolean).join(" "), children: [
3675
3751
  children,
3676
3752
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "nu-window-layer", children: renderWindows.map((windowEntry) => {
package/dist/index.js CHANGED
@@ -3072,28 +3072,93 @@ function getNumericStyleValue(value) {
3072
3072
  }
3073
3073
  return void 0;
3074
3074
  }
3075
+ function getWindowStorageKey(windowStoreKey) {
3076
+ const normalizedKey = windowStoreKey?.trim();
3077
+ return normalizedKey ? `reactnu.window.${normalizedKey}` : null;
3078
+ }
3079
+ function readStoredWindowSnapshot(windowStoreKey) {
3080
+ const storageKey = getWindowStorageKey(windowStoreKey);
3081
+ if (!storageKey || typeof window === "undefined") {
3082
+ return void 0;
3083
+ }
3084
+ try {
3085
+ const rawValue = window.localStorage.getItem(storageKey);
3086
+ if (!rawValue) {
3087
+ return void 0;
3088
+ }
3089
+ const parsedValue = JSON.parse(rawValue);
3090
+ if (!parsedValue || typeof parsedValue !== "object") {
3091
+ return void 0;
3092
+ }
3093
+ const storedValue = parsedValue;
3094
+ const snapshot = {};
3095
+ for (const property of ["height", "width"]) {
3096
+ const value = storedValue[property];
3097
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
3098
+ snapshot[property] = value;
3099
+ }
3100
+ }
3101
+ for (const property of ["left", "top"]) {
3102
+ const value = storedValue[property];
3103
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
3104
+ snapshot[property] = value;
3105
+ }
3106
+ }
3107
+ for (const property of ["maximized", "minimized"]) {
3108
+ const value = storedValue[property];
3109
+ if (typeof value === "boolean") {
3110
+ snapshot[property] = value;
3111
+ }
3112
+ }
3113
+ return Object.keys(snapshot).length > 0 ? snapshot : void 0;
3114
+ } catch {
3115
+ return void 0;
3116
+ }
3117
+ }
3118
+ function writeStoredWindowSnapshot(windowStoreKey, snapshot) {
3119
+ const storageKey = getWindowStorageKey(windowStoreKey);
3120
+ if (!storageKey || typeof window === "undefined") {
3121
+ return;
3122
+ }
3123
+ try {
3124
+ window.localStorage.setItem(storageKey, JSON.stringify(snapshot));
3125
+ } catch {
3126
+ }
3127
+ }
3075
3128
  function resolveSavedWindowStyle(definition, mode, ownerCenteredStyle, index) {
3076
- const snapshot = definition.onOpen?.();
3077
- const savedStyle = snapshot ? {
3078
- height: snapshot.height,
3079
- left: snapshot.left,
3080
- top: snapshot.top,
3081
- transform: snapshot.left !== void 0 || snapshot.top !== void 0 ? "none" : void 0,
3082
- width: snapshot.width
3083
- } : void 0;
3129
+ const snapshot = definition.onOpen?.() ?? readStoredWindowSnapshot(definition.windowStoreKey);
3130
+ const savedStyle = {};
3131
+ if (snapshot) {
3132
+ if (snapshot.height !== void 0) {
3133
+ savedStyle.height = snapshot.height;
3134
+ }
3135
+ if (snapshot.left !== void 0) {
3136
+ savedStyle.left = snapshot.left;
3137
+ }
3138
+ if (snapshot.top !== void 0) {
3139
+ savedStyle.top = snapshot.top;
3140
+ }
3141
+ if (snapshot.width !== void 0) {
3142
+ savedStyle.width = snapshot.width;
3143
+ }
3144
+ if (snapshot.left !== void 0 || snapshot.top !== void 0) {
3145
+ savedStyle.transform = "none";
3146
+ }
3147
+ }
3084
3148
  return {
3085
3149
  maximized: snapshot?.maximized === true,
3086
3150
  minimized: snapshot?.minimized === true,
3087
- restoreStyle: snapshot?.maximized === true && savedStyle ? {
3151
+ restoreStyle: snapshot?.maximized ? {
3088
3152
  ...getDefaultWindowStyle(mode, index),
3089
3153
  ...ownerCenteredStyle,
3154
+ ...definition.style,
3090
3155
  ...savedStyle
3091
3156
  } : void 0,
3092
3157
  style: {
3093
3158
  ...getDefaultWindowStyle(mode, index),
3094
3159
  ...ownerCenteredStyle,
3095
- ...savedStyle,
3096
- ...definition.style
3160
+ ...definition.style,
3161
+ ...savedStyle
3097
3162
  }
3098
3163
  };
3099
3164
  }
@@ -3619,6 +3684,17 @@ function NuWindowProvider({
3619
3684
  useEffect6(() => {
3620
3685
  onAppModalChange?.(hasAppModal);
3621
3686
  }, [hasAppModal, onAppModalChange]);
3687
+ useEffect6(() => {
3688
+ for (const windowEntry of windows) {
3689
+ if (!windowEntry.windowStoreKey) {
3690
+ continue;
3691
+ }
3692
+ writeStoredWindowSnapshot(
3693
+ windowEntry.windowStoreKey,
3694
+ resolveManagedWindowSnapshot(windowEntry, windowBoundsById)
3695
+ );
3696
+ }
3697
+ }, [windowBoundsById, windows]);
3622
3698
  return /* @__PURE__ */ jsx29(NuWindowContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs17("div", { className: ["nu-window-host", className].filter(Boolean).join(" "), children: [
3623
3699
  children,
3624
3700
  /* @__PURE__ */ jsx29("div", { className: "nu-window-layer", children: renderWindows.map((windowEntry) => {
@@ -39,6 +39,11 @@ export type NuManagedWindowDefinition = {
39
39
  style?: CSSProperties;
40
40
  titleButtons?: WindowTitleButtonDefinition[];
41
41
  title: string;
42
+ /**
43
+ * Persists this managed window's geometry and minimized/maximized state in
44
+ * localStorage. The key is scoped to `reactnu.window.`.
45
+ */
46
+ windowStoreKey?: string;
42
47
  };
43
48
  export type NuManagedWindowControls = {
44
49
  bringToFront: () => void;
@@ -126,6 +126,8 @@ The host owns:
126
126
 
127
127
  Managed windows can persist geometry with:
128
128
 
129
+ - `windowStoreKey?: string` for automatic browser-local persistence under
130
+ `reactnu.window.<key>`
129
131
  - `onOpen?: () => NuManagedWindowSnapshot | void`
130
132
  - `onClose?: (snapshot) => boolean | void`
131
133
 
@@ -29,7 +29,26 @@ Use `useNuWindowManager()` to access:
29
29
 
30
30
  ## Managed Window Persistence
31
31
 
32
- `NuManagedWindowDefinition` also supports:
32
+ Set `windowStoreKey` to persist a managed window's last position, size, and
33
+ minimized/maximized state automatically. ReactNU stores the snapshot in
34
+ `localStorage` as `reactnu.window.<key>`. Use a stable, unique key for each
35
+ logical window; re-opening that window with the same key restores its saved
36
+ geometry.
37
+
38
+ ```tsx
39
+ windowManager.openWindow({
40
+ content: <Inspector />,
41
+ title: "Inspector",
42
+ windowStoreKey: "workspace.inspector"
43
+ });
44
+ ```
45
+
46
+ Browser storage is optional: malformed or unavailable storage is ignored.
47
+ `onOpen` remains the explicit override for applications that restore state
48
+ from their own storage, and `onClose` still receives the current snapshot.
49
+ Clear `reactnu.window.workspace.inspector` to reset this example.
50
+
51
+ For host-owned persistence, `NuManagedWindowDefinition` also supports:
33
52
 
34
53
  - `onOpen?: () => NuManagedWindowSnapshot | void`
35
54
  - `onClose?: (snapshot: NuManagedWindowSnapshot) => boolean | void`
package/docs/Window.md CHANGED
@@ -56,6 +56,10 @@ to `useNuWindowManager().openWindow(...)`; it is resolved by
56
56
  `NuWindowProvider`, rather than by a standalone `Window` instance. See
57
57
  [NuWindowProvider](./NuWindowProvider.md#linked-activation).
58
58
 
59
+ `windowStoreKey?: string` is also available on a managed window definition.
60
+ It automatically restores and saves that window's geometry in browser storage;
61
+ see [NuWindowProvider](./NuWindowProvider.md#managed-window-persistence).
62
+
59
63
  ## Notes
60
64
 
61
65
  - Drag and resize are implemented with pointer events and local DOM updates for smoother motion.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deadragdoll/reactnu",
3
- "version": "0.1.75",
3
+ "version": "0.1.76",
4
4
  "private": false,
5
5
  "description": "React components with a precise DOS-era utility interface.",
6
6
  "license": "MIT",