@cocreate/state 1.24.9 → 1.25.1

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 +14 -0
  2. package/package.json +1 -1
  3. package/src/index.js +227 -200
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.25.1](https://github.com/CoCreate-app/CoCreate-state/compare/v1.25.0...v1.25.1) (2026-03-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * support adding classes via state attributes ([dfa50d2](https://github.com/CoCreate-app/CoCreate-state/commit/dfa50d29ac8b5c15ef384dfebd67dc8faad816f8))
7
+
8
+ # [1.25.0](https://github.com/CoCreate-app/CoCreate-state/compare/v1.24.9...v1.25.0) (2026-02-28)
9
+
10
+
11
+ ### Features
12
+
13
+ * state suports local and session storage ([fab140f](https://github.com/CoCreate-app/CoCreate-state/commit/fab140f9c660d55a0d3819c18bf99635f5e024f5))
14
+
1
15
  ## [1.24.9](https://github.com/CoCreate-app/CoCreate-state/compare/v1.24.8...v1.24.9) (2026-02-25)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/state",
3
- "version": "1.24.9",
3
+ "version": "1.25.1",
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,277 @@ 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 (attribute === "class" && value) {
93
+ const classes = String(value).split(/\s+/).filter(Boolean);
94
+ if (classes.length) element.classList.add(...classes);
95
+ } else if (element.hasAttribute(attribute) && (value || value === "")) {
96
+ element.setAttribute(attribute, value);
97
+ } else if (attribute.startsWith("$")) {
98
+ // Handle using operators.js
99
+ const prop = attribute.substring(1);
100
+ if (element[prop]) {
101
+ if (typeof element[prop] === "function") {
102
+ element[prop](value);
103
+ } else {
104
+ element[prop] = value;
105
+ }
106
+ }
107
+ }
108
+ }
92
109
  }
93
110
 
94
111
  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
- );
112
+ let sessionState = {};
113
+ let localState = {};
114
+
115
+ try { sessionState = JSON.parse(sessionStorage.getItem("state") || "{}"); } catch(e) {}
116
+ try { localState = JSON.parse(localStorage.getItem("state") || "{}"); } catch(e) {}
117
+
118
+ let elements = [];
119
+
120
+ // let form = element.closest("form");
121
+ // if (form) {
122
+ // elements = form.querySelectorAll("[state_to]");
123
+ // } else {
124
+ if (element.hasAttribute("state-to")) elements.push(element);
125
+ let nestedElements = element.querySelectorAll("[state-to]");
126
+ elements.push(...nestedElements);
127
+ let current = element;
128
+ while (current) {
129
+ if (current.hasAttribute("state-to") && !elements.includes(current)) {
130
+ elements.push(current);
131
+ }
132
+ current = current.parentElement;
133
+ }
134
+ // }
135
+
136
+ let changeState = false;
137
+ let sessionChanged = false;
138
+ let localChanged = false;
139
+ let triggerIsLocal = element.hasAttribute("state-localstorage") || element.hasAttribute("state-onstoragechange");
140
+
141
+ for (let i = 0; i < elements.length; i++) {
142
+ let attrValues = await _getAttributeValues(elements[i]);
143
+ if (attrValues.src === "$back" || attrValues.href === "$back") {
144
+ changeState = true;
145
+ window.history.back();
146
+ break;
147
+ } else if (
148
+ attrValues.src === "$forward" ||
149
+ attrValues.href === "$forward"
150
+ ) {
151
+ changeState = true;
152
+ window.history.forward();
153
+ break;
154
+ }
155
+
156
+ let state_to = elements[i].getAttribute("state-to");
157
+
158
+ let isLocal = triggerIsLocal || elements[i].hasAttribute("state-localstorage") || elements[i].hasAttribute("state-onstoragechange");
159
+
160
+ if (isLocal) {
161
+ Object.assign(localState, { [`${state_to}`]: attrValues });
162
+ localChanged = true;
163
+ } else {
164
+ Object.assign(sessionState, { [`${state_to}`]: attrValues });
165
+ sessionChanged = true;
166
+ }
167
+
168
+ _getStateId(attrValues, state_to);
169
+ }
170
+
171
+ let href = element.closest("href");
172
+ if (!changeState && !href) {
173
+ // TODO: Handle $title when adding statedAttributes
174
+ let title = element.getAttribute("title") || document.title || "";
175
+
176
+ history.pushState(
177
+ { sessionState: JSON.stringify(sessionState), localState: JSON.stringify(localState), title, url: location.href },
178
+ title,
179
+ location.href
180
+ );
181
+ }
182
+
183
+ if (!changeState) {
184
+ if (sessionChanged) sessionStorage.setItem("state", JSON.stringify(sessionState));
185
+ if (localChanged) localStorage.setItem("state", JSON.stringify(localState));
186
+ }
187
+
188
+ element.dispatchEvent(
189
+ new CustomEvent("stateEnd", {
190
+ detail: {}
191
+ })
192
+ );
163
193
  }
164
194
 
165
195
  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;
196
+ let attributeValues = {};
197
+ let attributes = element.attributes;
198
+ for (let attribute of attributes) {
199
+ if (attribute.name.startsWith("state.")) {
200
+ const key = attribute.name.slice(6);
201
+ if (attribute.value == "$uid") {
202
+ Object.assign(attributeValues, { [key]: uid.generate(6) });
203
+ } else if (key === "value" && !attribute.value) {
204
+ Object.assign(attributeValues, { value: await element.getValue() });
205
+ } else {
206
+ Object.assign(attributeValues, { [key]: element.getAttribute(attribute.name) });
207
+ }
208
+ }
209
+ }
210
+ return attributeValues;
188
211
  }
189
212
 
190
213
  function _getStateId(attrValues, state_to) {
191
- const elements = document.querySelectorAll(`[state-id="${state_to}"]`);
192
- for (let element of elements) _setAttributeValues(element, attrValues);
214
+ const elements = document.querySelectorAll(`[state-id="${state_to}"]`);
215
+ for (let element of elements) _setAttributeValues(element, attrValues);
193
216
  }
194
217
 
195
218
  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
- }
219
+ if (!history.length) {
220
+ const sessionState = sessionStorage.getItem("state") || "";
221
+ const localState = localStorage.getItem("state") || "";
222
+ history.replaceState(
223
+ { sessionState, localState, url: location.href, title: document.title },
224
+ document.title,
225
+ location.href
226
+ );
227
+ } else if (!history.state || history.state.url !== location.href) {
228
+ const sessionState = sessionStorage.getItem("state") || "";
229
+ const localState = localStorage.getItem("state") || "";
230
+ history.pushState(
231
+ { sessionState, localState, url: location.href, title: document.title },
232
+ document.title,
233
+ location.href
234
+ );
235
+ }
211
236
  };
212
237
 
213
238
  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
- }
239
+ if (event.state) {
240
+ if (event.state.sessionState || event.state.localState) {
241
+ if (event.state.sessionState) sessionStorage.setItem("state", event.state.sessionState);
242
+ if (event.state.localState) localStorage.setItem("state", event.state.localState);
243
+
244
+ let elements = document.querySelectorAll("[state-id]");
245
+ initElements(elements);
246
+ }
247
+
248
+ if (event.state.url && event.state.url !== window.location.href) {
249
+ if (event.state.title)
250
+ sessionStorage.setItem("currentPageTitle", event.state.title); // Store title using the wrapper
251
+
252
+ location.href = event.state.url; // Navigate if the URL is different
253
+ } else if (event.state.title) {
254
+ document.title = event.state.title; // Update the title if provided
255
+ }
256
+ }
230
257
  });
231
258
 
232
259
  Observer.init({
233
- name: "CoCreateState",
234
- types: ["addedNodes"],
235
- selector: "[state-id]",
236
- callback: function (mutation) {
237
- initElement(mutation.target);
238
- }
260
+ name: "CoCreateState",
261
+ types: ["addedNodes"],
262
+ selector: "[state-id]",
263
+ callback: function (mutation) {
264
+ initElement(mutation.target);
265
+ }
239
266
  });
240
267
 
241
268
  Action.init({
242
- name: "state",
243
- endEvent: "stateEnd",
244
- callback: (action) => {
245
- stateAttributes(action.element);
246
- }
269
+ name: "state",
270
+ endEvent: "stateEnd",
271
+ callback: (action) => {
272
+ stateAttributes(action.element);
273
+ }
247
274
  });
248
275
 
249
276
  init();
250
277
 
251
- export default { initElements, initElement, stateAttributes };
278
+ export default { initElements, initElement, stateAttributes };