@cocreate/state 1.24.9 → 1.25.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 (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +1 -1
  3. package/src/index.js +224 -200
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.25.0](https://github.com/CoCreate-app/CoCreate-state/compare/v1.24.9...v1.25.0) (2026-02-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * state suports local and session storage ([fab140f](https://github.com/CoCreate-app/CoCreate-state/commit/fab140f9c660d55a0d3819c18bf99635f5e024f5))
7
+
1
8
  ## [1.24.9](https://github.com/CoCreate-app/CoCreate-state/compare/v1.24.8...v1.24.9) (2026-02-25)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/state",
3
- "version": "1.24.9",
3
+ "version": "1.25.0",
4
4
  "description": "Data attributes which can be used for components in CoCreate framework combined as a group of attributes that dynamically change attributes & fetch custom data.",
5
5
  "keywords": [
6
6
  "state",
package/src/index.js CHANGED
@@ -2,250 +2,274 @@ import "@cocreate/element-prototype";
2
2
  import Observer from "@cocreate/observer";
3
3
  import Action from "@cocreate/actions";
4
4
  import uid from "@cocreate/uuid";
5
- import localStorage from "@cocreate/local-storage";
5
+ import storage from "@cocreate/local-storage";
6
+
7
+ const { localStorage, sessionStorage } = storage;
6
8
 
7
9
  function init() {
8
- let elements = document.querySelectorAll("[state-id]");
9
- initElements(elements);
10
- window.addEventListener("storage", function (e) {
11
- if (e.key == "state") {
12
- elements = document.querySelectorAll(
13
- '[state-id]:not([state-onstoragechange="false"])'
14
- );
15
- initElements(elements);
16
- }
17
- });
18
- document.addEventListener("click", function (e) {
19
- const target = e.target.closest("[state-to]");
20
- if (target) {
21
- if (target.closest('[actions*="state"]')) return;
22
- stateAttributes(target);
23
- }
24
- });
10
+ let elements = document.querySelectorAll("[state-id]");
11
+ initElements(elements);
12
+
13
+ window.addEventListener("storage", function (e) {
14
+ if (e.key == "state") {
15
+ // Re-initialize only elements that are explicitly listening to storage changes
16
+ elements = document.querySelectorAll(
17
+ '[state-id][state-onstoragechange]:not([state-onstoragechange="false"])'
18
+ );
19
+ initElements(elements);
20
+ }
21
+ });
22
+
23
+ document.addEventListener("click", function (e) {
24
+ const target = e.target.closest("[state-to]");
25
+ if (target) {
26
+ if (target.closest('[actions*="state"]')) return;
27
+ stateAttributes(target);
28
+ }
29
+ });
25
30
  }
26
31
 
27
32
  function initElements(elements) {
28
- for (let element of elements) initElement(element);
33
+ for (let element of elements) initElement(element);
29
34
  }
30
35
 
31
36
  function initElement(element) {
32
- let state_id = element.getAttribute("state-id");
33
- if (!state_id) return;
37
+ let state_id = element.getAttribute("state-id");
38
+ if (!state_id) return;
39
+
40
+ // Determine target storage based on attributes
41
+ let isLocal = element.hasAttribute("state-localstorage") || element.hasAttribute("state-onstoragechange");
42
+ let storageObj = isLocal ? localStorage : sessionStorage;
34
43
 
35
- let statedAttributes = localStorage.getItem("state");
44
+ let statedAttributes = storageObj.getItem("state");
36
45
 
37
- if (!statedAttributes || statedAttributes.length == 0) return;
38
- statedAttributes = JSON.parse(statedAttributes);
46
+ if (!statedAttributes || statedAttributes.length == 0) return;
47
+
48
+ try {
49
+ statedAttributes = JSON.parse(statedAttributes);
50
+ } catch(e) {
51
+ return;
52
+ }
39
53
 
40
- if (state_id.includes(",")) state_id = state_id.split(",");
54
+ if (state_id.includes(",")) state_id = state_id.split(",");
41
55
 
42
- if (!Array.isArray(state_id)) state_id = [state_id];
56
+ if (!Array.isArray(state_id)) state_id = [state_id];
43
57
 
44
- for (let i = 0; i < state_id.length; i++) {
45
- let attrValues = statedAttributes[`${state_id[i].trim()}`];
46
- if (!attrValues) return;
47
- _setAttributeValues(element, attrValues);
48
- }
58
+ for (let i = 0; i < state_id.length; i++) {
59
+ let attrValues = statedAttributes[`${state_id[i].trim()}`];
60
+ if (!attrValues) continue; // Use continue to ensure other IDs in the array still process
61
+ _setAttributeValues(element, attrValues);
62
+ }
49
63
  }
50
64
 
51
65
  function _setAttributeValues(el, attrValues) {
52
- let isOverwrite = el.getAttribute("state-overwrite");
53
- if (isOverwrite === null || isOverwrite === undefined)
54
- isOverwrite = attrValues["overwrite"];
66
+ let isOverwrite = el.getAttribute("state-overwrite");
67
+ if (isOverwrite === null || isOverwrite === undefined)
68
+ isOverwrite = attrValues["overwrite"];
55
69
 
56
- if ((isOverwrite && isOverwrite != "false") || isOverwrite === "")
57
- isOverwrite = true;
58
- else if (!isOverwrite || isOverwrite === "false") isOverwrite = false;
70
+ if ((isOverwrite && isOverwrite != "false") || isOverwrite === "")
71
+ isOverwrite = true;
72
+ else if (!isOverwrite || isOverwrite === "false") isOverwrite = false;
59
73
 
60
- // delete attrValues["overwrite"];
74
+ // delete attrValues["overwrite"];
61
75
 
62
- for (const key of Object.keys(attrValues)) {
63
- _setAttributeValue(el, key, attrValues[key], isOverwrite);
64
- }
76
+ for (const key of Object.keys(attrValues)) {
77
+ _setAttributeValue(el, key, attrValues[key], isOverwrite);
78
+ }
65
79
  }
66
80
 
67
81
  async function _setAttributeValue(element, attribute, value, isOverwrite) {
68
- // TODO: if (value !== undefined)???
69
- if (!element.getAttribute(attribute) || isOverwrite) {
70
- if (attribute == "value") {
71
- if (element.value == "" || (element.value && isOverwrite))
72
- element.value = value;
73
- else if (
74
- isOverwrite ||
75
- (element.hasAttribute("value") && !(await element.getValue()))
76
- )
77
- element.setValue(value);
78
- } else if (element.hasAttribute(attribute) && (value || value === "")) {
79
- element.setAttribute(attribute, value);
80
- } else if (attribute.startsWith("$")) {
81
- // Handle using operators.js
82
- const prop = attribute.substring(1);
83
- if (element[prop]) {
84
- if (typeof element[prop] === "function") {
85
- element[prop](value);
86
- } else {
87
- element[prop] = value;
88
- }
89
- }
90
- }
91
- }
82
+ // TODO: if (value !== undefined)???
83
+ if (!element.getAttribute(attribute) || isOverwrite) {
84
+ if (attribute == "value") {
85
+ if (element.value == "" || (element.value && isOverwrite))
86
+ element.value = value;
87
+ else if (
88
+ isOverwrite ||
89
+ (element.hasAttribute("value") && !(await element.getValue()))
90
+ )
91
+ element.setValue(value);
92
+ } else if (element.hasAttribute(attribute) && (value || value === "")) {
93
+ element.setAttribute(attribute, value);
94
+ } else if (attribute.startsWith("$")) {
95
+ // Handle using operators.js
96
+ const prop = attribute.substring(1);
97
+ if (element[prop]) {
98
+ if (typeof element[prop] === "function") {
99
+ element[prop](value);
100
+ } else {
101
+ element[prop] = value;
102
+ }
103
+ }
104
+ }
105
+ }
92
106
  }
93
107
 
94
108
  async function stateAttributes(element) {
95
- let statedAttributes = {};
96
- try {
97
- const storedAttributes = localStorage.getItem("state");
98
- if (storedAttributes) {
99
- statedAttributes = JSON.parse(storedAttributes);
100
- }
101
- } catch (error) {
102
- console.error("Failed to parse state from localStorage:", error);
103
- // Optionally reset `state` in localStorage to ensure valid data:
104
- localStorage.setItem("state", JSON.stringify({}));
105
- }
106
- let elements = [];
107
-
108
- // let form = element.closest("form");
109
- // if (form) {
110
- // elements = form.querySelectorAll("[state_to]");
111
- // } else {
112
- if (element.hasAttribute("state-to")) elements.push(element);
113
- let nestedElements = element.querySelectorAll("[state-to]");
114
- elements.push(...nestedElements);
115
- let current = element;
116
- while (current) {
117
- if (current.hasAttribute("state-to") && !elements.includes(current)) {
118
- elements.push(current);
119
- }
120
- current = current.parentElement;
121
- }
122
- // }
123
-
124
- let changeState = false;
125
- for (let i = 0; i < elements.length; i++) {
126
- let attrValues = await _getAttributeValues(elements[i]);
127
- if (attrValues.src === "$back" || attrValues.href === "$back") {
128
- changeState = true;
129
- window.history.back();
130
- break;
131
- } else if (
132
- attrValues.src === "$forward" ||
133
- attrValues.href === "$forward"
134
- ) {
135
- changeState = true;
136
- window.history.forward();
137
- break;
138
- }
139
-
140
- let state_to = elements[i].getAttribute("state-to");
141
- Object.assign(statedAttributes, { [`${state_to}`]: attrValues });
142
- _getStateId(attrValues, state_to);
143
- }
144
- statedAttributes = JSON.stringify(statedAttributes);
145
- let href = element.closest("href");
146
- if (!changeState && !href) {
147
- // TODO: Handle $title when adding statedAttributes
148
- let title = element.getAttribute("title") || document.title || "";
149
- history.pushState(
150
- { statedAttributes, title, url: location.href },
151
- title,
152
- location.href
153
- );
154
- }
155
-
156
- if (!changeState) localStorage.setItem("state", statedAttributes);
157
-
158
- element.dispatchEvent(
159
- new CustomEvent("stateEnd", {
160
- detail: {}
161
- })
162
- );
109
+ let sessionState = {};
110
+ let localState = {};
111
+
112
+ try { sessionState = JSON.parse(sessionStorage.getItem("state") || "{}"); } catch(e) {}
113
+ try { localState = JSON.parse(localStorage.getItem("state") || "{}"); } catch(e) {}
114
+
115
+ let elements = [];
116
+
117
+ // let form = element.closest("form");
118
+ // if (form) {
119
+ // elements = form.querySelectorAll("[state_to]");
120
+ // } else {
121
+ if (element.hasAttribute("state-to")) elements.push(element);
122
+ let nestedElements = element.querySelectorAll("[state-to]");
123
+ elements.push(...nestedElements);
124
+ let current = element;
125
+ while (current) {
126
+ if (current.hasAttribute("state-to") && !elements.includes(current)) {
127
+ elements.push(current);
128
+ }
129
+ current = current.parentElement;
130
+ }
131
+ // }
132
+
133
+ let changeState = false;
134
+ let sessionChanged = false;
135
+ let localChanged = false;
136
+ let triggerIsLocal = element.hasAttribute("state-localstorage") || element.hasAttribute("state-onstoragechange");
137
+
138
+ for (let i = 0; i < elements.length; i++) {
139
+ let attrValues = await _getAttributeValues(elements[i]);
140
+ if (attrValues.src === "$back" || attrValues.href === "$back") {
141
+ changeState = true;
142
+ window.history.back();
143
+ break;
144
+ } else if (
145
+ attrValues.src === "$forward" ||
146
+ attrValues.href === "$forward"
147
+ ) {
148
+ changeState = true;
149
+ window.history.forward();
150
+ break;
151
+ }
152
+
153
+ let state_to = elements[i].getAttribute("state-to");
154
+
155
+ let isLocal = triggerIsLocal || elements[i].hasAttribute("state-localstorage") || elements[i].hasAttribute("state-onstoragechange");
156
+
157
+ if (isLocal) {
158
+ Object.assign(localState, { [`${state_to}`]: attrValues });
159
+ localChanged = true;
160
+ } else {
161
+ Object.assign(sessionState, { [`${state_to}`]: attrValues });
162
+ sessionChanged = true;
163
+ }
164
+
165
+ _getStateId(attrValues, state_to);
166
+ }
167
+
168
+ let href = element.closest("href");
169
+ if (!changeState && !href) {
170
+ // TODO: Handle $title when adding statedAttributes
171
+ let title = element.getAttribute("title") || document.title || "";
172
+
173
+ history.pushState(
174
+ { sessionState: JSON.stringify(sessionState), localState: JSON.stringify(localState), title, url: location.href },
175
+ title,
176
+ location.href
177
+ );
178
+ }
179
+
180
+ if (!changeState) {
181
+ if (sessionChanged) sessionStorage.setItem("state", JSON.stringify(sessionState));
182
+ if (localChanged) localStorage.setItem("state", JSON.stringify(localState));
183
+ }
184
+
185
+ element.dispatchEvent(
186
+ new CustomEvent("stateEnd", {
187
+ detail: {}
188
+ })
189
+ );
163
190
  }
164
191
 
165
192
  async function _getAttributeValues(element) {
166
- let attributeValues = {};
167
- let attributes = element.attributes;
168
- for (let attribute of attributes) {
169
- if (attribute.name.startsWith("state.")) {
170
- const key = attribute.name.slice(6);
171
- if (attribute.value == "$uid") {
172
- Object.assign(attributeValues, { [key]: uid.generate(6) });
173
- } else if (key === "value" && !attribute.value) {
174
- Object.assign(attributeValues, { value: await element.getValue() });
175
- } else {
176
- Object.assign(attributeValues, { [key]: element.getAttribute(attribute.name) });
177
- }
178
- }
179
- }
180
- if (
181
- element.value !== undefined &&
182
- !element.hasAttribute("state.value") &&
183
- element.getValue
184
- )
185
- Object.assign(attributeValues, { value: await element.getValue() });
186
-
187
- return attributeValues;
193
+ let attributeValues = {};
194
+ let attributes = element.attributes;
195
+ for (let attribute of attributes) {
196
+ if (attribute.name.startsWith("state.")) {
197
+ const key = attribute.name.slice(6);
198
+ if (attribute.value == "$uid") {
199
+ Object.assign(attributeValues, { [key]: uid.generate(6) });
200
+ } else if (key === "value" && !attribute.value) {
201
+ Object.assign(attributeValues, { value: await element.getValue() });
202
+ } else {
203
+ Object.assign(attributeValues, { [key]: element.getAttribute(attribute.name) });
204
+ }
205
+ }
206
+ }
207
+ return attributeValues;
188
208
  }
189
209
 
190
210
  function _getStateId(attrValues, state_to) {
191
- const elements = document.querySelectorAll(`[state-id="${state_to}"]`);
192
- for (let element of elements) _setAttributeValues(element, attrValues);
211
+ const elements = document.querySelectorAll(`[state-id="${state_to}"]`);
212
+ for (let element of elements) _setAttributeValues(element, attrValues);
193
213
  }
194
214
 
195
215
  window.onload = function () {
196
- if (!history.length) {
197
- const statedAttributes = localStorage.getItem("state") || "";
198
- history.replaceState(
199
- { statedAttributes, url: location.href, title: document.title },
200
- document.title,
201
- location.href
202
- );
203
- } else if (!history.state || history.state.url !== location.href) {
204
- const statedAttributes = localStorage.getItem("state") || "";
205
- history.pushState(
206
- { statedAttributes, url: location.href, title: document.title },
207
- document.title,
208
- location.href
209
- );
210
- }
216
+ if (!history.length) {
217
+ const sessionState = sessionStorage.getItem("state") || "";
218
+ const localState = localStorage.getItem("state") || "";
219
+ history.replaceState(
220
+ { sessionState, localState, url: location.href, title: document.title },
221
+ document.title,
222
+ location.href
223
+ );
224
+ } else if (!history.state || history.state.url !== location.href) {
225
+ const sessionState = sessionStorage.getItem("state") || "";
226
+ const localState = localStorage.getItem("state") || "";
227
+ history.pushState(
228
+ { sessionState, localState, url: location.href, title: document.title },
229
+ document.title,
230
+ location.href
231
+ );
232
+ }
211
233
  };
212
234
 
213
235
  window.addEventListener("popstate", function (event) {
214
- if (event.state) {
215
- if (event.state.statedAttributes) {
216
- localStorage.setItem("state", event.state.statedAttributes);
217
- let elements = document.querySelectorAll("[state-id]");
218
- initElements(elements);
219
- }
220
-
221
- if (event.state.url && event.state.url !== window.location.href) {
222
- if (event.state.title)
223
- sessionStorage.setItem("currentPageTitle", event.state.title); // Store title in session storage
224
-
225
- location.href = event.state.url; // Navigate if the URL is different
226
- } else if (event.state.title) {
227
- document.title = event.state.title; // Update the title if provided
228
- }
229
- }
236
+ if (event.state) {
237
+ if (event.state.sessionState || event.state.localState) {
238
+ if (event.state.sessionState) sessionStorage.setItem("state", event.state.sessionState);
239
+ if (event.state.localState) localStorage.setItem("state", event.state.localState);
240
+
241
+ let elements = document.querySelectorAll("[state-id]");
242
+ initElements(elements);
243
+ }
244
+
245
+ if (event.state.url && event.state.url !== window.location.href) {
246
+ if (event.state.title)
247
+ sessionStorage.setItem("currentPageTitle", event.state.title); // Store title using the wrapper
248
+
249
+ location.href = event.state.url; // Navigate if the URL is different
250
+ } else if (event.state.title) {
251
+ document.title = event.state.title; // Update the title if provided
252
+ }
253
+ }
230
254
  });
231
255
 
232
256
  Observer.init({
233
- name: "CoCreateState",
234
- types: ["addedNodes"],
235
- selector: "[state-id]",
236
- callback: function (mutation) {
237
- initElement(mutation.target);
238
- }
257
+ name: "CoCreateState",
258
+ types: ["addedNodes"],
259
+ selector: "[state-id]",
260
+ callback: function (mutation) {
261
+ initElement(mutation.target);
262
+ }
239
263
  });
240
264
 
241
265
  Action.init({
242
- name: "state",
243
- endEvent: "stateEnd",
244
- callback: (action) => {
245
- stateAttributes(action.element);
246
- }
266
+ name: "state",
267
+ endEvent: "stateEnd",
268
+ callback: (action) => {
269
+ stateAttributes(action.element);
270
+ }
247
271
  });
248
272
 
249
273
  init();
250
274
 
251
- export default { initElements, initElement, stateAttributes };
275
+ export default { initElements, initElement, stateAttributes };