@uistate/core 5.0.2 → 5.0.3

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.
@@ -4,6 +4,9 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>002 Counter Improved - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Counter: <span id="count">0</span></h1>
@@ -12,7 +15,7 @@
12
15
  <button id="double">x2</button>
13
16
 
14
17
  <script type="module">
15
- import { createEventState } from './eventState.js';
18
+ import { createEventState } from '@uistate/core';
16
19
 
17
20
  // Create store
18
21
  const store = createEventState({ count: 0 });
@@ -4,6 +4,9 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>003 Input Reactive - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Type something:</h1>
@@ -11,7 +14,7 @@
11
14
  <p id="output"></p>
12
15
 
13
16
  <script type="module">
14
- import { createEventState } from './eventState.js';
17
+ import { createEventState } from '@uistate/core';
15
18
 
16
19
  // Create store
17
20
  const store = createEventState({ text: '' });
@@ -4,23 +4,26 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>004 Computed State - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Computed State</h1>
10
-
13
+
11
14
  <label>First Name: <input type="text" id="firstName" placeholder="Abraham"></label><br>
12
15
  <label>Last Name: <input type="text" id="lastName" placeholder="Lincoln"></label><br>
13
-
16
+
14
17
  <p><strong>Full Name:</strong> <span id="fullName"></span></p>
15
18
  <p><strong>Character Count:</strong> <span id="charCount">0</span></p>
16
19
 
17
20
  <script type="module">
18
- import { createEventState } from './eventState.js';
21
+ import { createEventState } from '@uistate/core';
19
22
 
20
23
  // Create store
21
- const store = createEventState({
22
- firstName: '',
23
- lastName: ''
24
+ const store = createEventState({
25
+ firstName: '',
26
+ lastName: ''
24
27
  });
25
28
 
26
29
  // Computed: full name (derived from firstName + lastName)
@@ -4,19 +4,22 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>005 Conditional Rendering - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Conditional Rendering</h1>
10
-
13
+
11
14
  <label>
12
15
  <input type="checkbox" id="toggle">
13
16
  Show secret message
14
17
  </label>
15
-
16
- <p id="message" style="display: none;">🎉 You found the secret message!</p>
18
+
19
+ <p id="message" style="display: none;">You found the secret message!</p>
17
20
 
18
21
  <script type="module">
19
- import { createEventState } from './eventState.js';
22
+ import { createEventState } from '@uistate/core';
20
23
 
21
24
  // Create store
22
25
  const store = createEventState({ isVisible: false });
@@ -4,17 +4,20 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>006 List Rendering - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Shopping List</h1>
10
-
13
+
11
14
  <input type="text" id="itemInput" placeholder="Add item...">
12
15
  <button id="addBtn">Add</button>
13
-
16
+
14
17
  <ul id="itemList"></ul>
15
18
 
16
19
  <script type="module">
17
- import { createEventState } from './eventState.js';
20
+ import { createEventState } from '@uistate/core';
18
21
 
19
22
  // Create store
20
23
  const store = createEventState({ items: [] });
@@ -23,7 +26,7 @@
23
26
  const renderList = () => {
24
27
  const items = store.get('items');
25
28
  const list = document.getElementById('itemList');
26
-
29
+
27
30
  if (items.length === 0) {
28
31
  list.innerHTML = '<li><em>No items yet</em></li>';
29
32
  } else {
@@ -38,7 +41,7 @@
38
41
  document.getElementById('addBtn').onclick = () => {
39
42
  const input = document.getElementById('itemInput');
40
43
  const value = input.value.trim();
41
-
44
+
42
45
  if (value) {
43
46
  const items = store.get('items');
44
47
  store.set('items', [...items, value]);
@@ -4,30 +4,33 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>007 Form Validation - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Sign Up Form</h1>
10
-
13
+
11
14
  <label>
12
15
  Email:
13
16
  <input type="email" id="email" placeholder="you@example.com">
14
17
  </label>
15
18
  <p id="emailError" style="color: red;"></p>
16
-
19
+
17
20
  <label>
18
21
  Password:
19
22
  <input type="password" id="password" placeholder="Min 8 characters">
20
23
  </label>
21
24
  <p id="passwordError" style="color: red;"></p>
22
-
25
+
23
26
  <button id="submitBtn" disabled>Submit</button>
24
27
  <p id="submitMessage" style="color: green;"></p>
25
28
 
26
29
  <script type="module">
27
- import { createEventState } from './eventState.js';
30
+ import { createEventState } from '@uistate/core';
28
31
 
29
32
  // Create store
30
- const store = createEventState({
33
+ const store = createEventState({
31
34
  email: '',
32
35
  password: '',
33
36
  emailValid: false,
@@ -55,7 +58,7 @@
55
58
  store.subscribe('email', (value) => {
56
59
  const isValid = validateEmail(value);
57
60
  store.set('emailValid', isValid);
58
-
61
+
59
62
  const error = document.getElementById('emailError');
60
63
  error.textContent = value && !isValid ? 'Invalid email address' : '';
61
64
  });
@@ -64,7 +67,7 @@
64
67
  store.subscribe('password', (value) => {
65
68
  const isValid = validatePassword(value);
66
69
  store.set('passwordValid', isValid);
67
-
70
+
68
71
  const error = document.getElementById('passwordError');
69
72
  error.textContent = value && !isValid ? 'Password must be at least 8 characters' : '';
70
73
  });
@@ -87,7 +90,7 @@
87
90
  const email = store.get('email');
88
91
  const message = document.getElementById('submitMessage');
89
92
  message.textContent = `✓ Form submitted for ${email}`;
90
-
93
+
91
94
  // Clear form
92
95
  document.getElementById('email').value = '';
93
96
  document.getElementById('password').value = '';
@@ -4,25 +4,28 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>008 Undo/Redo - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Counter with Time Travel</h1>
10
-
13
+
11
14
  <h2>Count: <span id="count">0</span></h2>
12
-
15
+
13
16
  <button id="increment">+1</button>
14
17
  <button id="decrement">-1</button>
15
18
  <button id="double">×2</button>
16
-
19
+
17
20
  <hr>
18
-
21
+
19
22
  <button id="undo" disabled>⟲ Undo</button>
20
23
  <button id="redo" disabled>⟳ Redo</button>
21
-
24
+
22
25
  <p><small>History: <span id="historyInfo">0 states</span></small></p>
23
26
 
24
27
  <script type="module">
25
- import { createEventState } from './eventState.js';
28
+ import { createEventState } from '@uistate/core';
26
29
 
27
30
  // Create store
28
31
  const store = createEventState({ count: 0 });
@@ -36,29 +39,29 @@
36
39
  const updateUI = () => {
37
40
  const count = store.get('count');
38
41
  document.getElementById('count').textContent = count;
39
-
42
+
40
43
  // Update button states
41
44
  document.getElementById('undo').disabled = historyIndex === 0;
42
45
  document.getElementById('redo').disabled = historyIndex === history.length - 1;
43
-
46
+
44
47
  // Update history info
45
- document.getElementById('historyInfo').textContent =
48
+ document.getElementById('historyInfo').textContent =
46
49
  `${historyIndex + 1} of ${history.length} states`;
47
50
  };
48
51
 
49
52
  // Subscribe to count changes and record history
50
53
  store.subscribe('count', (value) => {
51
54
  updateUI();
52
-
55
+
53
56
  // Only record if not time traveling
54
57
  if (!isTimeTravel) {
55
58
  // Remove any "future" states if we're in the middle of history
56
59
  history = history.slice(0, historyIndex + 1);
57
-
60
+
58
61
  // Add new state
59
62
  history.push(value);
60
63
  historyIndex = history.length - 1;
61
-
64
+
62
65
  updateUI();
63
66
  }
64
67
  });
@@ -4,20 +4,23 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>009 localStorage Side Effects - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Persistent Counter</h1>
10
-
13
+
11
14
  <h2>Count: <span id="count">0</span></h2>
12
-
15
+
13
16
  <button id="increment">+1</button>
14
17
  <button id="decrement">-1</button>
15
18
  <button id="reset">Reset</button>
16
-
19
+
17
20
  <p><small>Reload the page - your count persists!</small></p>
18
21
 
19
22
  <script type="module">
20
- import { createEventState } from './eventState.js';
23
+ import { createEventState } from '@uistate/core';
21
24
 
22
25
  const STORAGE_KEY = 'eventstate-counter';
23
26
 
@@ -11,6 +11,9 @@
11
11
  margin: 10px 0;
12
12
  }
13
13
  </style>
14
+ <script type="importmap">
15
+ { "imports": { "@uistate/core": "../../index.js" } }
16
+ </script>
14
17
  </head>
15
18
  <body>
16
19
  <h1>Decoupled Components</h1>
@@ -37,10 +40,10 @@
37
40
  </div>
38
41
 
39
42
  <script type="module">
40
- import { createEventState } from './eventState.js';
43
+ import { createEventState } from '@uistate/core';
41
44
 
42
45
  // Shared store - the ONLY connection between components
43
- const store = createEventState({
46
+ const store = createEventState({
44
47
  message: '',
45
48
  messageCount: 0
46
49
  });
@@ -50,7 +53,7 @@
50
53
  document.getElementById('sendBtn').onclick = () => {
51
54
  const input = document.getElementById('messageInput');
52
55
  const value = input.value.trim();
53
-
56
+
54
57
  if (value) {
55
58
  store.set('message', value);
56
59
  store.set('messageCount', store.get('messageCount') + 1);
@@ -4,27 +4,30 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>011 Async Patterns - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Async Patterns</h1>
10
-
13
+
11
14
  <h2>Debounced Search</h2>
12
15
  <input type="text" id="searchInput" placeholder="Type to search...">
13
16
  <p id="searchStatus"></p>
14
17
  <p><strong>Search result:</strong> <span id="searchResult">(type something)</span></p>
15
-
18
+
16
19
  <hr>
17
-
20
+
18
21
  <h2>Simulated API Call</h2>
19
22
  <button id="fetchBtn">Fetch Data</button>
20
23
  <p id="loadingStatus"></p>
21
24
  <p><strong>Data:</strong> <span id="apiData">(not loaded)</span></p>
22
25
 
23
26
  <script type="module">
24
- import { createEventState } from './eventState.js';
27
+ import { createEventState } from '@uistate/core';
25
28
 
26
29
  // Create store
27
- const store = createEventState({
30
+ const store = createEventState({
28
31
  searchQuery: '',
29
32
  searchResult: '',
30
33
  isSearching: false,
@@ -48,16 +51,16 @@
48
51
  store.subscribe('searchQuery', async (value) => {
49
52
  // Clear previous timeout
50
53
  clearTimeout(searchTimeout);
51
-
54
+
52
55
  if (!value) {
53
56
  store.set('searchResult', '');
54
57
  store.set('isSearching', false);
55
58
  return;
56
59
  }
57
-
60
+
58
61
  // Show searching indicator
59
62
  store.set('isSearching', true);
60
-
63
+
61
64
  // Debounce: wait 300ms before searching
62
65
  searchTimeout = setTimeout(async () => {
63
66
  const result = await performSearch(value);
@@ -68,12 +71,12 @@
68
71
 
69
72
  // Update UI for search status
70
73
  store.subscribe('isSearching', (value) => {
71
- document.getElementById('searchStatus').textContent =
72
- value ? '🔍 Searching...' : '';
74
+ document.getElementById('searchStatus').textContent =
75
+ value ? 'Searching...' : '';
73
76
  });
74
77
 
75
78
  store.subscribe('searchResult', (value) => {
76
- document.getElementById('searchResult').textContent =
79
+ document.getElementById('searchResult').textContent =
77
80
  value || '(type something)';
78
81
  });
79
82
 
@@ -83,12 +86,12 @@
83
86
  };
84
87
 
85
88
  // ===== SIMULATED API CALL =====
86
-
89
+
87
90
  // Simulate API fetch
88
91
  const fetchData = () => {
89
92
  return new Promise((resolve) => {
90
93
  setTimeout(() => {
91
- resolve({
94
+ resolve({
92
95
  id: Math.floor(Math.random() * 1000),
93
96
  timestamp: new Date().toLocaleTimeString()
94
97
  });
@@ -98,7 +101,7 @@
98
101
 
99
102
  // Subscribe to loading state
100
103
  store.subscribe('isLoading', (value) => {
101
- document.getElementById('loadingStatus').textContent =
104
+ document.getElementById('loadingStatus').textContent =
102
105
  value ? '⏳ Loading...' : '';
103
106
  document.getElementById('fetchBtn').disabled = value;
104
107
  });
@@ -106,7 +109,7 @@
106
109
  // Subscribe to API data
107
110
  store.subscribe('apiData', (value) => {
108
111
  if (value) {
109
- document.getElementById('apiData').textContent =
112
+ document.getElementById('apiData').textContent =
110
113
  `ID: ${value.id}, Time: ${value.timestamp}`;
111
114
  }
112
115
  });
@@ -114,7 +117,7 @@
114
117
  // Fetch button
115
118
  document.getElementById('fetchBtn').onclick = async () => {
116
119
  store.set('isLoading', true);
117
-
120
+
118
121
  try {
119
122
  const data = await fetchData();
120
123
  store.set('apiData', data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uistate/core",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
4
4
  "description": "Lightweight event-driven state management with slot orchestration and experimental event-sequence testing (eventTest.js available under dual license)",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };