@excom/provider-storage 0.1.0

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 (32) hide show
  1. package/.rush/temp/chunked-rush-logs/provider-storage.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/provider-storage.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/provider-storage.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +5 -0
  15. package/index.ts +18 -0
  16. package/package.json +45 -0
  17. package/provider-storage.ts +138 -0
  18. package/rush-logs/provider-storage.apply-exports.cache.log +1 -0
  19. package/rush-logs/provider-storage.apply-exports.log +1 -0
  20. package/rush-logs/provider-storage.build_docs.cache.log +1 -0
  21. package/rush-logs/provider-storage.build_docs.log +1 -0
  22. package/rush-logs/provider-storage.build_package-metas.cache.log +1 -0
  23. package/rush-logs/provider-storage.build_package-metas.log +1 -0
  24. package/support/custom-elements.json +167 -0
  25. package/support/demos/simple.html +18 -0
  26. package/support/dist-docs/provider-storage.md +118 -0
  27. package/support/docs/README.md +55 -0
  28. package/support/package-meta.json +99 -0
  29. package/support/tests/__snapshots__/simple.view.test.ts.snap +23 -0
  30. package/support/tests/provider-storage.test.ts +520 -0
  31. package/support/tests/simple.view.test.ts +44 -0
  32. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 34.84205700000007
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 43.99502499999994
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 101.22544199999993
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "../../packages/provider-storage": "../../packages/provider-storage:rrFX5s3WCBVNkmNhA28lT/w/Ce6jrCJLmfP50wp4Hns=:"
3
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
+ "rigPackageName": "@excom/heft-rig",
4
+ "rigProfile": "default"
5
+ }
package/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { ProviderStorage } from "./provider-storage";
2
+
3
+ ProviderStorage.define();
4
+
5
+ export { ProviderStorage };
6
+ export type { ProviderStorageChangedEvent } from "./provider-storage";
7
+
8
+ type T_HTMLProviderStorageElement = typeof ProviderStorage.CustomElement;
9
+ declare global {
10
+ interface HTMLProviderStorageElement extends T_HTMLProviderStorageElement {}
11
+ interface Window {
12
+ HTMLProviderStorageElement: HTMLProviderStorageElement;
13
+ }
14
+ interface HTMLElementTagNameMap {
15
+ "provider-storage": HTMLProviderStorageElement;
16
+ }
17
+ }
18
+ export type { HTMLProviderStorageElement };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@excom/provider-storage",
3
+ "version": "0.1.0",
4
+ "description": "<provider-storage> custom element",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@excom/kit-logger": "^0.1.0",
12
+ "@excom/neutron": "^0.1.0"
13
+ },
14
+ "peerDependencies": {},
15
+ "devDependencies": {
16
+ "@excom/heft-rig": "^0.1.0"
17
+ },
18
+ "repository": {
19
+ "url": "excom-dev/nucleus",
20
+ "directory": "packages/provider-storage"
21
+ },
22
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/provider-storage/support/docs/README.md",
23
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
24
+ "keywords": [
25
+ "provider-storage",
26
+ "neutron",
27
+ "custom-elements",
28
+ "localstorage",
29
+ "sessionstorage"
30
+ ],
31
+ "excom": {
32
+ "packageType": "kit-element"
33
+ },
34
+ "scripts": {
35
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
36
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
37
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
38
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
39
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
40
+ "dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
41
+ "preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
42
+ "build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
43
+ "build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
44
+ }
45
+ }
@@ -0,0 +1,138 @@
1
+ import { KitLogger } from "@excom/kit-logger";
2
+ import { Neutron, TEvent } from "@excom/neutron";
3
+
4
+ export type ProviderStorageChangedEvent = TEvent & {
5
+ type: "provider-storage-changed";
6
+ detail: {
7
+ /** The `key-name` whose stored value changed. */
8
+ keyName: string;
9
+ /** `provision` before the re-read (`null` when nothing was read). */
10
+ oldValue: unknown;
11
+ /** `provision` after the re-read (`null` when absent / not valid JSON). */
12
+ newValue: unknown;
13
+ };
14
+ };
15
+
16
+ type StoreProps = { keyName?: string | null; storeName?: string | null };
17
+
18
+ const getStore = (storeName?: string | null) =>
19
+ storeName === "session" ? sessionStorage : localStorage;
20
+
21
+ /** Read `key-name` out of `store-name` as JSON → provision / state effect. */
22
+ const readStore = ({ keyName, storeName }: StoreProps) => {
23
+ try {
24
+ return {
25
+ provision: JSON.parse(getStore(storeName).getItem(keyName!) || "null"),
26
+ isSuccess: true,
27
+ isError: false,
28
+ };
29
+ } catch (e) {
30
+ KitLogger.error("provider-storage", e);
31
+ return { provision: null, isSuccess: false, isError: true };
32
+ }
33
+ };
34
+
35
+ /**
36
+ * Reads a JSON value out of `localStorage` / `sessionStorage` under
37
+ * `key-name` into `provision`. Read-only: it never writes. It re-reads
38
+ * whenever `key-name` / `store-name` is set or changes, and — for
39
+ * `localStorage` — whenever another tab writes the same key (the
40
+ * browser's `storage` event). Same-tab writes do not fire `storage`,
41
+ * so after your own `setItem` re-set `key-name` to force a read.
42
+ *
43
+ * @fires provider-storage-changed - Dispatched after a `storage` event
44
+ * from another tab for this element's `store-name` + `key-name` (or a
45
+ * `clear()` of that store) has been re-read into `provision` — so
46
+ * `provision`, `is-success` / `is-error` are already updated when it
47
+ * fires. The read also publishes `neutron-provision`, as usual.
48
+ * @type ProviderStorageChangedEvent
49
+ */
50
+ export const ProviderStorage = Neutron({
51
+ tag: "provider-storage",
52
+ props: {
53
+ // options
54
+ /**
55
+ * @option
56
+ * Storage key to read. Setting or changing it re-reads immediately
57
+ * (parsed as JSON; `null` if absent). Removing it clears
58
+ * `provision` and both states.
59
+ * @values <storage key>
60
+ */
61
+ keyName: String,
62
+ /**
63
+ * @option
64
+ * Which Web Storage area to read. `local` survives the tab / browser
65
+ * closing and syncs across tabs; `session` is per-tab and gone when
66
+ * the tab closes. Changing it re-reads `key-name` from the new store.
67
+ * @values local, session
68
+ * @default local
69
+ */
70
+ storeName: String,
71
+ // state
72
+ /**
73
+ * @provision
74
+ * `JSON.parse(store.getItem(keyName))`, or `null` if the key is
75
+ * absent or the read failed. Not reflected as an attribute.
76
+ */
77
+ provision: Object,
78
+ /**
79
+ * @state
80
+ * The last read (parse) succeeded — including a legitimately absent
81
+ * key (`provision` is `null`, not an error).
82
+ */
83
+ isSuccess: Boolean,
84
+ /**
85
+ * @state
86
+ * `JSON.parse` threw on the last read (the stored value isn't valid
87
+ * JSON). `provision` is `null`.
88
+ */
89
+ isError: Boolean,
90
+ },
91
+ })
92
+ .defineMethods({
93
+ // `storage` fires in *other* tabs only, for every key of every store.
94
+ _handleStorage: (
95
+ { keyName, storeName, provision }: StoreProps & { provision: unknown },
96
+ e: StorageEvent
97
+ ) => {
98
+ if (!keyName || e.storageArea !== getStore(storeName)) return null;
99
+ // `key === null` is `clear()`; it took this key with it
100
+ if (e.key !== null && e.key !== keyName) return null;
101
+ const next = readStore({ keyName, storeName });
102
+ return {
103
+ ...next,
104
+ emit: [
105
+ "provider-storage-changed",
106
+ {
107
+ detail: {
108
+ keyName,
109
+ oldValue: provision,
110
+ newValue: next.provision,
111
+ },
112
+ },
113
+ ],
114
+ };
115
+ },
116
+ })
117
+ // Tracked listener: Neutron drops it on disconnect and restores it on
118
+ // reconnect / move, so no `isMoving` bookkeeping here.
119
+ .onConnected(
120
+ // @ts-ignore TODO defineMethods
121
+ ({ _handleStorage }) => ({
122
+ addListener: ["storage", _handleStorage, { target: window }],
123
+ })
124
+ )
125
+ // One read per batch, whichever of the two changed (including first mount).
126
+ .onEffect(["keyName", "storeName"], ({ keyName, storeName }, previous) =>
127
+ keyName
128
+ ? readStore({ keyName, storeName })
129
+ : // `key-name` removed: nothing to show
130
+ "keyName" in previous && {
131
+ provision: null,
132
+ isSuccess: false,
133
+ isError: false,
134
+ }
135
+ );
136
+
137
+ // <provider-storage key-name="results"></provider-storage>
138
+ // <provider-storage key-name="draft" store-name="session"></provider-storage>
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1,167 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "modules": [
4
+ {
5
+ "kind": "javascript-module",
6
+ "path": "provider-storage.ts",
7
+ "declarations": [
8
+ {
9
+ "kind": "class",
10
+ "name": "ProviderStorage",
11
+ "customElement": true,
12
+ "tagName": "provider-storage",
13
+ "description": "The `key-name` whose stored value changed.",
14
+ "attributes": [
15
+ {
16
+ "name": "key-name",
17
+ "type": {
18
+ "text": "string"
19
+ },
20
+ "description": "Storage key to read. Setting or changing it re-reads immediately (parsed as JSON; `null` if absent). Removing it clears `provision` and both states.",
21
+ "fieldName": "keyName",
22
+ "values": [
23
+ "<storage key>"
24
+ ]
25
+ },
26
+ {
27
+ "name": "store-name",
28
+ "type": {
29
+ "text": "string"
30
+ },
31
+ "description": "Which Web Storage area to read. `local` survives the tab / browser closing and syncs across tabs; `session` is per-tab and gone when the tab closes. Changing it re-reads `key-name` from the new store.",
32
+ "fieldName": "storeName",
33
+ "default": "local",
34
+ "values": [
35
+ "local, session"
36
+ ]
37
+ },
38
+ {
39
+ "name": "is-success",
40
+ "type": {
41
+ "text": "boolean"
42
+ },
43
+ "description": "The last read (parse) succeeded — including a legitimately absent key (`provision` is `null`, not an error).",
44
+ "fieldName": "isSuccess"
45
+ },
46
+ {
47
+ "name": "is-error",
48
+ "type": {
49
+ "text": "boolean"
50
+ },
51
+ "description": "`JSON.parse` threw on the last read (the stored value isn't valid JSON). `provision` is `null`.",
52
+ "fieldName": "isError"
53
+ }
54
+ ],
55
+ "members": [
56
+ {
57
+ "kind": "field",
58
+ "name": "keyName",
59
+ "type": {
60
+ "text": "string"
61
+ },
62
+ "privacy": "public",
63
+ "readonly": false,
64
+ "description": "Storage key to read. Setting or changing it re-reads immediately (parsed as JSON; `null` if absent). Removing it clears `provision` and both states.",
65
+ "_neutron": {
66
+ "surface": "option"
67
+ }
68
+ },
69
+ {
70
+ "kind": "field",
71
+ "name": "storeName",
72
+ "type": {
73
+ "text": "string"
74
+ },
75
+ "privacy": "public",
76
+ "readonly": false,
77
+ "description": "Which Web Storage area to read. `local` survives the tab / browser closing and syncs across tabs; `session` is per-tab and gone when the tab closes. Changing it re-reads `key-name` from the new store.",
78
+ "default": "local",
79
+ "_neutron": {
80
+ "surface": "option"
81
+ }
82
+ },
83
+ {
84
+ "kind": "field",
85
+ "name": "provision",
86
+ "type": {
87
+ "text": "object"
88
+ },
89
+ "privacy": "public",
90
+ "readonly": false,
91
+ "description": "`JSON.parse(store.getItem(keyName))`, or `null` if the key is absent or the read failed. Not reflected as an attribute.",
92
+ "_neutron": {
93
+ "surface": "option"
94
+ }
95
+ },
96
+ {
97
+ "kind": "field",
98
+ "name": "isSuccess",
99
+ "type": {
100
+ "text": "boolean"
101
+ },
102
+ "privacy": "public",
103
+ "readonly": true,
104
+ "description": "The last read (parse) succeeded — including a legitimately absent key (`provision` is `null`, not an error).",
105
+ "_neutron": {
106
+ "surface": "state"
107
+ }
108
+ },
109
+ {
110
+ "kind": "field",
111
+ "name": "isError",
112
+ "type": {
113
+ "text": "boolean"
114
+ },
115
+ "privacy": "public",
116
+ "readonly": true,
117
+ "description": "`JSON.parse` threw on the last read (the stored value isn't valid JSON). `provision` is `null`.",
118
+ "_neutron": {
119
+ "surface": "state"
120
+ }
121
+ }
122
+ ],
123
+ "events": [
124
+ {
125
+ "name": "provider-storage-changed",
126
+ "description": "Dispatched after a `storage` event from another tab for this element's `store-name` + `key-name` (or a `clear()` of that store) has been re-read into `provision` — so `provision`, `is-success` / `is-error` are already updated when it fires. The read also publishes `neutron-provision`, as usual.",
127
+ "type": {
128
+ "text": "ProviderStorageChangedEvent",
129
+ "expanded": "CustomEvent & { type: \"provider-storage-changed\"; detail: { keyName: string; oldValue: unknown; newValue: unknown; }; bubbles: true; cancelable: true; composed: true }"
130
+ }
131
+ }
132
+ ],
133
+ "_neutron": {
134
+ "provisions": [
135
+ {
136
+ "name": "provision",
137
+ "type": {
138
+ "text": "object"
139
+ },
140
+ "description": "`JSON.parse(store.getItem(keyName))`, or `null` if the key is absent or the read failed. Not reflected as an attribute.",
141
+ "fieldName": "provision"
142
+ }
143
+ ]
144
+ }
145
+ }
146
+ ],
147
+ "exports": [
148
+ {
149
+ "kind": "js",
150
+ "name": "ProviderStorage",
151
+ "declaration": {
152
+ "name": "ProviderStorage",
153
+ "module": "provider-storage.ts"
154
+ }
155
+ },
156
+ {
157
+ "kind": "custom-element-definition",
158
+ "name": "provider-storage",
159
+ "declaration": {
160
+ "name": "ProviderStorage",
161
+ "module": "provider-storage.ts"
162
+ }
163
+ }
164
+ ]
165
+ }
166
+ ]
167
+ }
@@ -0,0 +1,18 @@
1
+ <div>
2
+ <event-handler role="button" fire-event="demo-seed-storage">
3
+ Seed localStorage &amp; re-read
4
+ </event-handler>
5
+ <provider-storage></provider-storage>
6
+ <output>Not read yet — click above.</output>
7
+ <small role="note">Same-tab writes only show up after
8
+ <code>key-name</code> is re-set; a write from another tab is picked up
9
+ live.</small>
10
+ <quark-sheet>
11
+ @use "/demo-utils" as *;
12
+
13
+ :scope {
14
+ @on demo-seed-storage (handle: seedDemoStorage);
15
+ @on neutron-provision (handle: setOutputFromElementData);
16
+ }
17
+ </quark-sheet>
18
+ </div>
@@ -0,0 +1,118 @@
1
+ # provider-storage
2
+
3
+ Read a JSON value out of `localStorage` / `sessionStorage` into `provision`,
4
+ declaratively — and keep it in sync across tabs. No app JS required to display
5
+ cached client state.
6
+
7
+ ## Features
8
+
9
+ - **Declarative read** Point `key-name` at a storage key and read the result
10
+ - **Local or session** `store-name` picks `localStorage` (default) /
11
+ `sessionStorage`
12
+ - **Live across tabs** A write from another tab re-reads and fires
13
+ `provider-storage-changed`
14
+ - **Reactive to attributes** Changing `key-name` / `store-name` re-reads
15
+ immediately
16
+ - **Tiny, read-only** Never writes
17
+
18
+ ## Installation
19
+
20
+
21
+ `@excom/provider-storage` v0.1.0
22
+
23
+ ```bash
24
+ pnpm add @excom/provider-storage
25
+ ```
26
+
27
+ ```bash
28
+ npm install @excom/provider-storage
29
+ ```
30
+
31
+ ```bash
32
+ yarn add @excom/provider-storage
33
+ ```
34
+
35
+ ### Import
36
+
37
+ ```ts
38
+ import "@excom/provider-storage";
39
+ ```
40
+
41
+
42
+
43
+ ## Usage
44
+
45
+ ```html
46
+ <provider-storage key-name="user-preferences"></provider-storage>
47
+ <provider-storage key-name="checkout-draft" store-name="session"></provider-storage>
48
+ ```
49
+
50
+ **Read-only.** It reads on every `key-name` / `store-name` set/change and never
51
+ writes. Removing `key-name` clears `provision` and both states.
52
+
53
+ **Live across tabs, not within one.** Browsers fire `storage` only in *other*
54
+ tabs, so:
55
+
56
+ - A write from another tab (or a `clear()` there) re-reads and fires
57
+ `provider-storage-changed` — no app code needed. `sessionStorage` is
58
+ per-tab, so this only applies to `store-name="local"`.
59
+ - A write by your own app code (`localStorage.setItem(...)`) in the same tab
60
+ won't appear until you re-trigger a read — re-set `key-name` (e.g. to `""`
61
+ and back) after writing.
62
+
63
+ ### API Reference
64
+
65
+
66
+ #### Attributes
67
+
68
+ | Name | Surface | Type | Default | Values | Description |
69
+ | --- | --- | --- | --- | --- | --- |
70
+ | `key-name` | option | `string` | | `<storage key>` | Storage key to read. Setting or changing it re-reads immediately (parsed as JSON; `null` if absent). Removing it clears `provision` and both states. |
71
+ | `store-name` | option | `string` | `"local"` | `local, session` | Which Web Storage area to read. `local` survives the tab / browser closing and syncs across tabs; `session` is per-tab and gone when the tab closes. Changing it re-reads `key-name` from the new store. |
72
+ | `is-success` | state | `boolean` | | | The last read (parse) succeeded — including a legitimately absent key (`provision` is `null`, not an error). |
73
+ | `is-error` | state | `boolean` | | | `JSON.parse` threw on the last read (the stored value isn't valid JSON). `provision` is `null`. |
74
+
75
+ #### Provision
76
+
77
+ | Name | Type | Description |
78
+ | --- | --- | --- |
79
+ | `provision` | `object` | `JSON.parse(store.getItem(keyName))`, or `null` if the key is absent or the read failed. Not reflected as an attribute. |
80
+
81
+ #### Fires
82
+
83
+ | Name | Type | Description |
84
+ | --- | --- | --- |
85
+ | `provider-storage-changed` | `ProviderStorageChangedEvent` (`CustomEvent & { type: "provider-storage-changed"; detail: { keyName: string; oldValue: unknown; newValue: unknown; }; bubbles: true; cancelable: true; composed: true }`) | Dispatched after a `storage` event from another tab for this element's `store-name` + `key-name` (or a `clear()` of that store) has been re-read into `provision` — so `provision`, `is-success` / `is-error` are already updated when it fires. The read also publishes `neutron-provision`, as usual. |
86
+
87
+
88
+
89
+ ### Examples
90
+
91
+ #### Seed & re-read
92
+
93
+ Since this element never writes, the demo below seeds a value from a button
94
+ (re-setting `key-name` afterward to force the same-tab re-read) so you can see
95
+ it work without opening devtools. Open this page in a second tab and click
96
+ there too — this tab updates on its own.
97
+
98
+
99
+ ```html
100
+ <div>
101
+ <event-handler role="button" fire-event="demo-seed-storage">
102
+ Seed localStorage &amp; re-read
103
+ </event-handler>
104
+ <provider-storage></provider-storage>
105
+ <output>Not read yet — click above.</output>
106
+ <small role="note">Same-tab writes only show up after
107
+ <code>key-name</code> is re-set; a write from another tab is picked up
108
+ live.</small>
109
+ <quark-sheet>
110
+ @use "/demo-utils" as *;
111
+
112
+ :scope {
113
+ @on demo-seed-storage (handle: seedDemoStorage);
114
+ @on neutron-provision (handle: setOutputFromElementData);
115
+ }
116
+ </quark-sheet>
117
+ </div>
118
+ ```
@@ -0,0 +1,55 @@
1
+ # provider-storage
2
+
3
+ Read a JSON value out of `localStorage` / `sessionStorage` into `provision`,
4
+ declaratively — and keep it in sync across tabs. No app JS required to display
5
+ cached client state.
6
+
7
+ ## Features
8
+
9
+ - **Declarative read** Point `key-name` at a storage key and read the result
10
+ - **Local or session** `store-name` picks `localStorage` (default) /
11
+ `sessionStorage`
12
+ - **Live across tabs** A write from another tab re-reads and fires
13
+ `provider-storage-changed`
14
+ - **Reactive to attributes** Changing `key-name` / `store-name` re-reads
15
+ immediately
16
+ - **Tiny, read-only** Never writes
17
+
18
+ ## Installation
19
+
20
+ <include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
21
+
22
+ ## Usage
23
+
24
+ ```html
25
+ <provider-storage key-name="user-preferences"></provider-storage>
26
+ <provider-storage key-name="checkout-draft" store-name="session"></provider-storage>
27
+ ```
28
+
29
+ **Read-only.** It reads on every `key-name` / `store-name` set/change and never
30
+ writes. Removing `key-name` clears `provision` and both states.
31
+
32
+ **Live across tabs, not within one.** Browsers fire `storage` only in *other*
33
+ tabs, so:
34
+
35
+ - A write from another tab (or a `clear()` there) re-reads and fires
36
+ `provider-storage-changed` — no app code needed. `sessionStorage` is
37
+ per-tab, so this only applies to `store-name="local"`.
38
+ - A write by your own app code (`localStorage.setItem(...)`) in the same tab
39
+ won't appear until you re-trigger a read — re-set `key-name` (e.g. to `""`
40
+ and back) after writing.
41
+
42
+ ### API Reference
43
+
44
+ <include-content is-active template-ref="/views/api-reference/api-reference.html"></include-content>
45
+
46
+ ### Examples
47
+
48
+ #### Seed & re-read
49
+
50
+ Since this element never writes, the demo below seeds a value from a button
51
+ (re-setting `key-name` afterward to force the same-tab re-read) so you can see
52
+ it work without opening devtools. Open this page in a second tab and click
53
+ there too — this tab updates on its own.
54
+
55
+ <include-content data-demo="simple"></include-content>