@entropy-softworks/ui 2026.8.32 → 2026.8.33

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.
@@ -3,7 +3,7 @@ export { cn } from "./cn";
3
3
  export { getErrorMessage } from "./error";
4
4
  export { darkPalette, getPalette, lightPalette, usePalette, type ColorScheme, type Palette, } from "./palette";
5
5
  export { clearClipboardNow, copyToClipboard } from "./clipboard";
6
- export { loadSetting, saveSetting } from "./settings-storage";
6
+ export { loadSetting, saveSetting, loadJsonSetting, saveJsonSetting } from "./settings-storage";
7
7
  export { debounce } from "./debounce";
8
8
  export { ApiError, extractApiError, extractErrorMessage } from "./http-error";
9
9
  export { generatePassword, type GeneratePasswordOptions } from "./password";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,OAAO,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,OAAO,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC"}
@@ -3,7 +3,7 @@ export { cn } from "./cn";
3
3
  export { getErrorMessage } from "./error";
4
4
  export { darkPalette, getPalette, lightPalette, usePalette, } from "./palette";
5
5
  export { clearClipboardNow, copyToClipboard } from "./clipboard";
6
- export { loadSetting, saveSetting } from "./settings-storage";
6
+ export { loadSetting, saveSetting, loadJsonSetting, saveJsonSetting } from "./settings-storage";
7
7
  export { debounce } from "./debounce";
8
8
  export { ApiError, extractApiError, extractErrorMessage } from "./http-error";
9
9
  export { generatePassword } from "./password";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,GAGX,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAgC,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,GAGjB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,UAAU,GAGX,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAgC,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,GAGjB,MAAM,cAAc,CAAC"}
@@ -1,3 +1,12 @@
1
1
  export declare function loadSetting(key: string, fallback: boolean): Promise<boolean>;
2
2
  export declare function saveSetting(key: string, value: boolean): Promise<void>;
3
+ /**
4
+ * Same storage backend as load/saveSetting (SecureStore native,
5
+ * localStorage web) but for arbitrary JSON-serializable values instead
6
+ * of only booleans — e.g. an ordered list of ids, or a per-id map.
7
+ * `fallback` covers both "never saved" and "saved value doesn't parse"
8
+ * (a corrupt/edited-by-hand entry shouldn't crash the caller).
9
+ */
10
+ export declare function loadJsonSetting<T>(key: string, fallback: T): Promise<T>;
11
+ export declare function saveJsonSetting<T>(key: string, value: T): Promise<void>;
3
12
  //# sourceMappingURL=settings-storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings-storage.d.ts","sourceRoot":"","sources":["../../src/utils/settings-storage.ts"],"names":[],"mappings":"AA+BA,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBlF;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E"}
1
+ {"version":3,"file":"settings-storage.d.ts","sourceRoot":"","sources":["../../src/utils/settings-storage.ts"],"names":[],"mappings":"AA+BA,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBlF;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAkB7E;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB7E"}
@@ -59,4 +59,49 @@ export async function saveSetting(key, value) {
59
59
  }
60
60
  }
61
61
  }
62
+ /**
63
+ * Same storage backend as load/saveSetting (SecureStore native,
64
+ * localStorage web) but for arbitrary JSON-serializable values instead
65
+ * of only booleans — e.g. an ordered list of ids, or a per-id map.
66
+ * `fallback` covers both "never saved" and "saved value doesn't parse"
67
+ * (a corrupt/edited-by-hand entry shouldn't crash the caller).
68
+ */
69
+ export async function loadJsonSetting(key, fallback) {
70
+ try {
71
+ const raw = Platform.OS === "web"
72
+ ? WEB_STORAGE_AVAILABLE
73
+ ? window.localStorage.getItem(key)
74
+ : null
75
+ : await SecureStore.getItemAsync(key);
76
+ if (raw === null) {
77
+ return fallback;
78
+ }
79
+ return JSON.parse(raw);
80
+ }
81
+ catch (e) {
82
+ if (__DEV__) {
83
+ console.warn(`[settings-storage] loadJsonSetting(${key}) threw:`, e);
84
+ }
85
+ return fallback;
86
+ }
87
+ }
88
+ export async function saveJsonSetting(key, value) {
89
+ try {
90
+ const raw = JSON.stringify(value);
91
+ if (Platform.OS === "web") {
92
+ if (!WEB_STORAGE_AVAILABLE) {
93
+ warnNoStorage("save");
94
+ return;
95
+ }
96
+ window.localStorage.setItem(key, raw);
97
+ return;
98
+ }
99
+ await SecureStore.setItemAsync(key, raw);
100
+ }
101
+ catch (e) {
102
+ if (__DEV__) {
103
+ console.warn(`[settings-storage] saveJsonSetting(${key}) threw:`, e);
104
+ }
105
+ }
106
+ }
62
107
  //# sourceMappingURL=settings-storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings-storage.js","sourceRoot":"","sources":["../../src/utils/settings-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;GAQG;AAEH,MAAM,qBAAqB,GACzB,QAAQ,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;AAElF,IAAI,eAAe,GAAG,KAAK,CAAC;AAC5B,SAAS,aAAa,CAAC,MAAuB;IAC5C,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO;IACT,CAAC;IACD,eAAe,GAAG,IAAI,CAAC;IACvB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,OAAO,CAAC,IAAI,CACV,sBAAsB,MAAM,sEAAsE,CACnG,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,QAAiB;IAC9D,IAAI,CAAC;QACH,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBAC3B,aAAa,CAAC,MAAM,CAAC,CAAC;gBACtB,OAAO,QAAQ,CAAC;YAClB,CAAC;YACD,MAAM,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC3C,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC9C,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAC9C,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC9C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,2BAA2B,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,KAAc;IAC3D,IAAI,CAAC;QACH,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBAC3B,aAAa,CAAC,MAAM,CAAC,CAAC;gBACtB,OAAO;YACT,CAAC;YACD,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QACD,MAAM,WAAW,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,2BAA2B,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"settings-storage.js","sourceRoot":"","sources":["../../src/utils/settings-storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entropy-softworks/ui",
3
- "version": "2026.8.32",
3
+ "version": "2026.8.33",
4
4
  "private": false,
5
5
  "description": "Shared React Native UI primitives, auth screens, hooks, and services for Entropy Softworks Expo apps",
6
6
  "license": "MIT",
@@ -1,4 +1,5 @@
1
- import { loadSetting, saveSetting } from "../settings-storage";
1
+ import * as SecureStore from "expo-secure-store";
2
+ import { loadJsonSetting, loadSetting, saveJsonSetting, saveSetting } from "../settings-storage";
2
3
 
3
4
  describe("settings-storage (native path)", () => {
4
5
  it("returns the fallback when nothing is stored", async () => {
@@ -13,3 +14,24 @@ describe("settings-storage (native path)", () => {
13
14
  expect(await loadSetting("vault_setting_alpha", true)).toBe(false);
14
15
  });
15
16
  });
17
+
18
+ describe("loadJsonSetting / saveJsonSetting", () => {
19
+ it("returns the fallback when nothing is stored", async () => {
20
+ expect(await loadJsonSetting("vault_json_unset", ["default"])).toEqual(["default"]);
21
+ });
22
+
23
+ it("round-trips an array via SecureStore", async () => {
24
+ await saveJsonSetting("vault_json_tags", ["tag-1", "tag-2", "tag-3"]);
25
+ expect(await loadJsonSetting("vault_json_tags", [])).toEqual(["tag-1", "tag-2", "tag-3"]);
26
+ });
27
+
28
+ it("round-trips an object via SecureStore", async () => {
29
+ await saveJsonSetting("vault_json_map", { "tag-1": true, "tag-2": false });
30
+ expect(await loadJsonSetting("vault_json_map", {})).toEqual({ "tag-1": true, "tag-2": false });
31
+ });
32
+
33
+ it("falls back on corrupt stored JSON instead of throwing", async () => {
34
+ await SecureStore.setItemAsync("vault_json_corrupt", "{not valid json");
35
+ expect(await loadJsonSetting("vault_json_corrupt", ["fallback"])).toEqual(["fallback"]);
36
+ });
37
+ });
@@ -10,7 +10,7 @@ export {
10
10
  type Palette,
11
11
  } from "./palette";
12
12
  export { clearClipboardNow, copyToClipboard } from "./clipboard";
13
- export { loadSetting, saveSetting } from "./settings-storage";
13
+ export { loadSetting, saveSetting, loadJsonSetting, saveJsonSetting } from "./settings-storage";
14
14
  export { debounce } from "./debounce";
15
15
  export { ApiError, extractApiError, extractErrorMessage } from "./http-error";
16
16
  export { generatePassword, type GeneratePasswordOptions } from "./password";
@@ -66,3 +66,49 @@ export async function saveSetting(key: string, value: boolean): Promise<void> {
66
66
  }
67
67
  }
68
68
  }
69
+
70
+ /**
71
+ * Same storage backend as load/saveSetting (SecureStore native,
72
+ * localStorage web) but for arbitrary JSON-serializable values instead
73
+ * of only booleans — e.g. an ordered list of ids, or a per-id map.
74
+ * `fallback` covers both "never saved" and "saved value doesn't parse"
75
+ * (a corrupt/edited-by-hand entry shouldn't crash the caller).
76
+ */
77
+ export async function loadJsonSetting<T>(key: string, fallback: T): Promise<T> {
78
+ try {
79
+ const raw =
80
+ Platform.OS === "web"
81
+ ? WEB_STORAGE_AVAILABLE
82
+ ? window.localStorage.getItem(key)
83
+ : null
84
+ : await SecureStore.getItemAsync(key);
85
+ if (raw === null) {
86
+ return fallback;
87
+ }
88
+ return JSON.parse(raw) as T;
89
+ } catch (e) {
90
+ if (__DEV__) {
91
+ console.warn(`[settings-storage] loadJsonSetting(${key}) threw:`, e);
92
+ }
93
+ return fallback;
94
+ }
95
+ }
96
+
97
+ export async function saveJsonSetting<T>(key: string, value: T): Promise<void> {
98
+ try {
99
+ const raw = JSON.stringify(value);
100
+ if (Platform.OS === "web") {
101
+ if (!WEB_STORAGE_AVAILABLE) {
102
+ warnNoStorage("save");
103
+ return;
104
+ }
105
+ window.localStorage.setItem(key, raw);
106
+ return;
107
+ }
108
+ await SecureStore.setItemAsync(key, raw);
109
+ } catch (e) {
110
+ if (__DEV__) {
111
+ console.warn(`[settings-storage] saveJsonSetting(${key}) threw:`, e);
112
+ }
113
+ }
114
+ }