spikijs 1.1.24 → 1.1.26
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.
- package/README.md +187 -406
- package/package.json +1 -1
- package/spiki.esm.js +4 -4
- package/spiki.esm.min.js +1 -1
- package/spiki.js +4 -4
- package/spiki.min.js +1 -1
package/README.md
CHANGED
|
@@ -1,526 +1,307 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SpikiJS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SpikiJS is a lightweight (~5KB), high-performance JavaScript framework for building reactive web applications directly in your HTML without build tools. It utilizes ES6 Proxies for fine-grained reactivity and adheres to a strict Content Security Policy (CSP).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
7
|
## Key Features
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
- **Tiny Footprint:** Only ~5KB gzip. Works directly in HTML without build tools.
|
|
10
|
+
- **100% CSP Secure:** Safe execution without `eval()` or inline expression vulnerabilities.
|
|
11
|
+
- **Deep Reactivity:** Automatic tracking for nested objects & arrays using ES6 Proxies.
|
|
12
|
+
- **Full SVG Support:** Dynamic attributes and `:class` binding work seamlessly on HTML and SVG.
|
|
13
|
+
- **Auto Loop Context:** `s-for` automatically provides `this.item` and `this.index` in event handlers.
|
|
14
|
+
- **Global Store + `init()`:** Shared state store that automatically runs an `init()` method on registration.
|
|
15
|
+
- **Memory Safe:** Built-in `mount()` and `unmount()` methods to prevent SPA memory leaks.
|
|
13
16
|
|
|
14
17
|
---
|
|
15
18
|
|
|
16
|
-
##
|
|
19
|
+
## Directive Rules & CSP Security
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
Simply add the script tag to your HTML. Spiki will automatically attach to `window.spiki`.
|
|
21
|
+
SpikiJS enforces strict security and architectural separation of concerns:
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
1. **No Inline JS Code:** HTML attributes cannot contain JavaScript code, expressions, or operators (e.g. `count++` or `a + b` are invalid).
|
|
24
|
+
- **Allowed Exception:** Boolean directives (`s-if`, `:hidden`, `:disabled`, `:checked`) evaluate state property names directly or with a logical NOT (`!`) operator (e.g. `s-if="isReady"` or `:hidden="!isVisible"`).
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
2. **No Attribute Parameters:** Function parameters are not supported inside HTML attributes (e.g. `s-click="save(item)"` is invalid).
|
|
27
|
+
- **Correct Pattern:** Bind only the method name (`s-click="save"`). Inside loop templates, Spiki automatically injects context variables directly into `this` (e.g. `this.item`, `this.index`).
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Install via package manager.
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install spikijs
|
|
32
|
-
|
|
33
|
-
```
|
|
29
|
+
3. **State Access via `this`:** All data reading and state mutations must be performed inside component methods using the `this` context (e.g. `this.count++`).
|
|
34
30
|
|
|
35
31
|
---
|
|
36
32
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
Choose the initialization method that fits your architecture.
|
|
40
|
-
|
|
41
|
-
### Automatic
|
|
42
|
-
|
|
43
|
-
Best for simple websites. Spiki scans the entire document for `s-data` attributes.
|
|
44
|
-
|
|
45
|
-
**HTML:**
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<div s-data="counterApp">
|
|
49
|
-
Count: <span s-text="count"></span>
|
|
50
|
-
<button s-click="increment">+</button>
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
**JavaScript:**
|
|
56
|
-
|
|
57
|
-
```javascript
|
|
58
|
-
import spiki from 'spikijs';
|
|
59
|
-
|
|
60
|
-
// 1. Register Component
|
|
61
|
-
spiki.data('counterApp', () => ({
|
|
62
|
-
count: 0,
|
|
63
|
-
increment() {
|
|
64
|
-
this.count++;
|
|
65
|
-
}
|
|
66
|
-
}));
|
|
67
|
-
|
|
68
|
-
// 2. Start Engine (Scans the whole body)
|
|
69
|
-
spiki.start();
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Manual Mount
|
|
74
|
-
|
|
75
|
-
You control exactly *when* and *where* Spiki starts, and crucially, when it stops to free memory.
|
|
33
|
+
## Installation
|
|
76
34
|
|
|
77
|
-
|
|
35
|
+
### CDN (Browser)
|
|
36
|
+
Include SpikiJS via script tag. Spiki automatically exposes `window.spiki`:
|
|
78
37
|
|
|
79
38
|
```html
|
|
80
|
-
<
|
|
81
|
-
Count: <span s-text="count"></span>
|
|
82
|
-
<button s-click="increment">+</button>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
39
|
+
<script src="https://unpkg.com/spikijs"></script>
|
|
85
40
|
```
|
|
86
41
|
|
|
87
|
-
|
|
42
|
+
### ESM Module (Modern Browsers / NPM)
|
|
43
|
+
Import SpikiJS directly as an ES module:
|
|
88
44
|
|
|
89
45
|
```javascript
|
|
90
|
-
import spiki from 'spikijs';
|
|
91
|
-
|
|
92
|
-
// 1. Register Component logic
|
|
93
|
-
spiki.data('counterApp', () => ({
|
|
94
|
-
count: 0,
|
|
95
|
-
increment() {
|
|
96
|
-
this.count++;
|
|
97
|
-
}
|
|
98
|
-
}));
|
|
99
|
-
|
|
100
|
-
// 2. Select the DOM element
|
|
101
|
-
const myWidget = document.getElementById('my-widget');
|
|
102
|
-
|
|
103
|
-
// 3. Mount Spiki to this specific element
|
|
104
|
-
const counterApp = spiki.mount(myWidget);
|
|
105
|
-
|
|
106
|
-
// ... later when removing the element or changing pages ...
|
|
107
|
-
// 4. Unmount to free memory (Stop watchers and event listeners)
|
|
108
|
-
// counterApp.unmount();
|
|
109
|
-
|
|
46
|
+
import spiki from './spikijs/spiki.esm.js';
|
|
110
47
|
```
|
|
111
48
|
|
|
112
49
|
---
|
|
113
50
|
|
|
114
|
-
##
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// 2. Start Spiki
|
|
141
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
142
|
-
spiki.start();
|
|
143
|
-
});
|
|
144
|
-
</script>
|
|
145
|
-
|
|
146
|
-
```
|
|
51
|
+
## Directives & API Reference
|
|
52
|
+
|
|
53
|
+
| Directive / API | Description | Example |
|
|
54
|
+
| :--- | :--- | :--- |
|
|
55
|
+
| **`s-data`** | Defines component state scope. | `<div s-data="myApp">` |
|
|
56
|
+
| **`s-text`** | Sets safe text content. | `<span s-text="username"></span>` |
|
|
57
|
+
| **`s-html`** | Sets inner HTML (XSS caution). | `<div s-html="richText"></div>` |
|
|
58
|
+
| **`s-if`** | Conditionally mounts/unmounts DOM nodes (supports `!`). | `<div s-if="!isOnline">Offline</div>` |
|
|
59
|
+
| **`:hidden`** | Toggles native `hidden` visibility (replaces `s-show`, supports `!`). | `<div hidden :hidden="!isVisible">Content</div>` |
|
|
60
|
+
| **`s-for`** | Loops array items inside `<template>` tags. | `<template s-for="(item, idx) in list" s-key="id">` |
|
|
61
|
+
| **`s-key`** | Unique key tracking for list items. | `<template ... s-key="id">` |
|
|
62
|
+
| **`s-model`** | Two-way input data binding. | `<input s-model="query">` |
|
|
63
|
+
| **`s-value`** | One-way value binding. | `<input s-value="formattedText" readonly>` |
|
|
64
|
+
| **`s-effect`** | Automatic reactive watcher (runs on state mutations). | `<div s-effect="saveToStorage">` |
|
|
65
|
+
| **`s-ref`** | Registers DOM element reference in `this.$refs`. | `<input s-ref="textInput">` |
|
|
66
|
+
| **`s-init`** | Executes hook on component mount. | `<div s-init="onMount">` |
|
|
67
|
+
| **`s-destroy`** | Executes hook on component unmount. | `<div s-destroy="onUnmount">` |
|
|
68
|
+
| **`s-ignore`** | Skips DOM compilation for static blocks. | `<div s-ignore>` |
|
|
69
|
+
| **`:[attr]`** | Binds dynamic attributes (HTML & SVG). | `<img :src="userAvatar">` |
|
|
70
|
+
| **`:class`** | Binds dynamic classes (HTML & SVG). | `<div :class="activeClass">` |
|
|
71
|
+
| **`s-[event]`** | Attaches event listener (`s-click`, `s-keyup`, etc.). | `<button s-click="submitForm">` |
|
|
72
|
+
| **`this.$root`** | Accesses component's root container element. | `this.$root.classList.add('active');` |
|
|
73
|
+
| **`this.$refs`** | Object containing all `s-ref` DOM elements. | `this.$refs.textInput.focus();` |
|
|
74
|
+
| **`this.$store`** | Accesses registered global stores. | `this.$store.settings.theme` |
|
|
75
|
+
| **`spiki.store()`** | Registers global store (runs `init()` automatically if present). | `spiki.store('settings', { theme: 'dark', init() {} })` |
|
|
76
|
+
| **`spiki.raw()`** | Unwraps Spiki Proxy to a plain JavaScript object. | `const plainObj = spiki.raw(this.user);` |
|
|
147
77
|
|
|
148
78
|
---
|
|
149
79
|
|
|
150
|
-
##
|
|
151
|
-
|
|
152
|
-
| Directive | Description | Example |
|
|
153
|
-
| --- | --- | --- |
|
|
154
|
-
| **`s-data`** | Defines component scope. | `<div s-data="app">` |
|
|
155
|
-
| **`s-text`** | Sets text content (Safe). | `<span s-text="msg">` |
|
|
156
|
-
| **`s-html`** | Sets innerHTML (Use carefully). | `<div s-html="htmlContent">` |
|
|
157
|
-
| **`s-if`** | Conditional rendering. | `<p s-if="showMe">` |
|
|
158
|
-
| **`s-for`** | Loops content (requires `<template>`). | `<template s-for="item in list">` |
|
|
159
|
-
| **`s-key`** | Unique key for loops (Performance). | `<template ... s-key="id">` |
|
|
160
|
-
| **`s-model`** | Two-way binding (Inputs). | `<input s-model="text">` |
|
|
161
|
-
| **`s-value`** | One-way value binding. | `<input s-value="text">` |
|
|
162
|
-
| **`s-effect`** | Runs function on data change. | `<div s-effect="autoSave">` |
|
|
163
|
-
| **`s-ref`** | Stores DOM reference in `$refs`. | `<input s-ref="myInput">` |
|
|
164
|
-
| **`s-init`** | Runs on mount. | `<div s-init="onLoad">` |
|
|
165
|
-
| **`s-destroy`** | Runs on unmount. | `<div s-destroy="onRemove">` |
|
|
166
|
-
| **`s-ignore`** | Skips compilation (Static content). | `<div s-ignore>` |
|
|
167
|
-
| **`:[attr]`** | Dynamic attribute binding. | `<img :src="imgUrl">` |
|
|
168
|
-
| **`:class`** | Dynamic class toggling. | `<div :class="classConfig">` |
|
|
169
|
-
| **`s-[event]`** | Event listener. | `<button s-click="save">` |
|
|
170
|
-
| **`this.$root`** | Access the root elm. | `this.$root.classList.add('loaded');` |
|
|
171
|
-
| **`this.$store`** | Access the global store. | `this.$store.theme` |
|
|
172
|
-
| **`spiki.store()`** | Get or set global store. | `spiki.store('theme', 'dark')` |
|
|
173
|
-
| **`spiki.raw()`** | Proxy to original object. | `spiki.raw(this.users)` |
|
|
80
|
+
## Detailed Feature Documentation
|
|
174
81
|
|
|
175
|
-
|
|
82
|
+
### 1. Conditional Rendering (`s-if`) & Visibility (`:hidden`)
|
|
176
83
|
|
|
177
|
-
|
|
84
|
+
SpikiJS provides two ways to control element display:
|
|
178
85
|
|
|
179
|
-
|
|
86
|
+
- **`s-if`**: Completely mounts or unmounts the element from the DOM tree. Supports negation (`!`).
|
|
87
|
+
- **`:hidden`**: Replaces traditional `s-show` by toggling the native `hidden` DOM attribute. Supports negation (`!`).
|
|
180
88
|
|
|
181
|
-
|
|
89
|
+
> **Pro Tip (Prevent FOUC Glitch):** Add the static `hidden` attribute to elements in HTML (`<div hidden :hidden="show">`) so they remain hidden during page load before JS initializes.
|
|
182
90
|
|
|
183
91
|
```html
|
|
184
|
-
<div s-data="
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
92
|
+
<div s-data="visibilityDemo">
|
|
93
|
+
<button s-click="toggle">Toggle Visibility</button>
|
|
94
|
+
|
|
95
|
+
<!-- s-if (Mount/Unmount) -->
|
|
96
|
+
<div s-if="isOnline">Status: Online</div>
|
|
97
|
+
<div s-if="!isOnline">Status: Disconnected (Negation Example)</div>
|
|
98
|
+
|
|
99
|
+
<!-- :hidden (Visibility Toggle with static hidden attribute to prevent flicker) -->
|
|
100
|
+
<div hidden :hidden="!isVisible">Visible Content</div>
|
|
188
101
|
</div>
|
|
189
102
|
|
|
190
103
|
<script>
|
|
191
|
-
spiki.data('
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return this.msg.toUpperCase();
|
|
104
|
+
spiki.data('visibilityDemo', () => ({
|
|
105
|
+
isOnline: true,
|
|
106
|
+
isVisible: true,
|
|
107
|
+
toggle() {
|
|
108
|
+
this.isOnline = !this.isOnline;
|
|
109
|
+
this.isVisible = !this.isVisible;
|
|
198
110
|
}
|
|
199
111
|
}));
|
|
200
112
|
</script>
|
|
201
|
-
|
|
202
113
|
```
|
|
203
114
|
|
|
204
|
-
|
|
115
|
+
---
|
|
205
116
|
|
|
206
|
-
### 2.
|
|
117
|
+
### 2. SVG & HTML Dynamic Class Binding (`:class`)
|
|
207
118
|
|
|
208
|
-
|
|
119
|
+
Dynamic class binding works seamlessly on both HTML and SVG elements (preventing `SVGAnimatedString` class errors):
|
|
209
120
|
|
|
210
121
|
```html
|
|
211
|
-
<div s-data="
|
|
212
|
-
|
|
213
|
-
</div>
|
|
122
|
+
<div s-data="themeApp">
|
|
123
|
+
<!-- HTML Element -->
|
|
124
|
+
<div :class="statusClass">Component Active</div>
|
|
214
125
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
</script>
|
|
126
|
+
<!-- SVG Element -->
|
|
127
|
+
<svg :class="iconClass" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
128
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
129
|
+
</svg>
|
|
220
130
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
> **Note:** Just like `s-text`, if you use a function here, it receives the DOM element (`el`) as an argument.
|
|
224
|
-
|
|
225
|
-
### 3. Dynamic Attributes (`:[attribute]`)
|
|
226
|
-
|
|
227
|
-
You can bind ANY HTML attribute to a variable by adding a colon `:` before the attribute name.
|
|
228
|
-
|
|
229
|
-
```html
|
|
230
|
-
<div s-data="attrApp">
|
|
231
|
-
<img :src="avatarUrl" :alt="avatarAlt">
|
|
232
|
-
<a :href="profileLink">View Profile</a>
|
|
233
|
-
|
|
234
|
-
<button :disabled="checkStatus">Submit</button>
|
|
131
|
+
<button s-click="toggleStatus">Toggle Theme</button>
|
|
235
132
|
</div>
|
|
236
133
|
|
|
237
134
|
<script>
|
|
238
|
-
spiki.data('
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return true; // Return value sets the attribute
|
|
249
|
-
}
|
|
250
|
-
el.style.opacity = '1';
|
|
251
|
-
return false;
|
|
135
|
+
spiki.data('themeApp', () => ({
|
|
136
|
+
isActive: false,
|
|
137
|
+
toggleStatus() {
|
|
138
|
+
this.isActive = !this.isActive;
|
|
139
|
+
},
|
|
140
|
+
get statusClass() {
|
|
141
|
+
return this.isActive ? 'bg-indigo-600 text-white' : 'bg-slate-200 text-slate-700';
|
|
142
|
+
},
|
|
143
|
+
get iconClass() {
|
|
144
|
+
return this.isActive ? 'stroke-indigo-500 text-indigo-500' : 'stroke-slate-400 text-slate-400';
|
|
252
145
|
}
|
|
253
146
|
}));
|
|
254
147
|
</script>
|
|
255
|
-
|
|
256
148
|
```
|
|
257
149
|
|
|
258
|
-
|
|
150
|
+
---
|
|
259
151
|
|
|
260
|
-
###
|
|
152
|
+
### 3. List Rendering & Scope Auto-Injection (`s-for`)
|
|
261
153
|
|
|
262
|
-
|
|
154
|
+
Inside `<template s-for="(child, index) in children">`, event handlers do **NOT** require parameters. Spiki automatically injects `this.child` (current item) and `this.index` (current loop index) directly into method execution context:
|
|
263
155
|
|
|
264
156
|
```html
|
|
265
|
-
<div s-data="
|
|
266
|
-
<
|
|
157
|
+
<div s-data="todoApp">
|
|
158
|
+
<input s-model="newItem" s-keyup="checkAdd" placeholder="Add fruit (e.g. Mango)...">
|
|
159
|
+
<button s-click="addItem">Add</button>
|
|
160
|
+
|
|
267
161
|
<ul>
|
|
268
|
-
<template s-for="
|
|
269
|
-
<li
|
|
162
|
+
<template s-for="(fruit, idx) in fruits" s-key="id">
|
|
163
|
+
<li>
|
|
164
|
+
#<span s-text="idx"></span>: <span s-text="fruit.name"></span>
|
|
165
|
+
<!-- No parameters needed! this.fruit and this.idx are auto-injected -->
|
|
166
|
+
<button s-click="removeItem">Delete</button>
|
|
167
|
+
</li>
|
|
270
168
|
</template>
|
|
271
169
|
</ul>
|
|
272
170
|
</div>
|
|
273
171
|
|
|
274
172
|
<script>
|
|
275
|
-
spiki.data('
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
173
|
+
spiki.data('todoApp', () => ({
|
|
174
|
+
newItem: 'Mango',
|
|
175
|
+
fruits: [
|
|
176
|
+
{ id: 1, name: 'Apple' },
|
|
177
|
+
{ id: 2, name: 'Banana' }
|
|
178
|
+
],
|
|
179
|
+
checkAdd(e) {
|
|
180
|
+
if (e.key === 'Enter') this.addItem();
|
|
181
|
+
},
|
|
182
|
+
addItem() {
|
|
183
|
+
if (!this.newItem.trim()) return;
|
|
184
|
+
this.fruits.push({ id: Date.now(), name: this.newItem.trim() });
|
|
185
|
+
this.newItem = '';
|
|
186
|
+
},
|
|
187
|
+
removeItem() {
|
|
188
|
+
// Automatically reads this.idx auto-injected by Spiki loop scope
|
|
189
|
+
if (typeof this.idx === 'number') {
|
|
190
|
+
this.fruits.splice(this.idx, 1);
|
|
191
|
+
}
|
|
290
192
|
}
|
|
291
193
|
}));
|
|
292
194
|
</script>
|
|
293
|
-
|
|
294
195
|
```
|
|
295
196
|
|
|
296
|
-
|
|
197
|
+
---
|
|
297
198
|
|
|
298
|
-
###
|
|
199
|
+
### 4. Instance API: `this.$refs` & `this.$root`
|
|
299
200
|
|
|
300
|
-
|
|
201
|
+
Access raw DOM nodes without query selectors using `this.$refs` and `this.$root`:
|
|
301
202
|
|
|
302
203
|
```html
|
|
303
|
-
<div s-data="
|
|
304
|
-
<
|
|
204
|
+
<div s-data="refDemo">
|
|
205
|
+
<input s-ref="textInput" type="text" value="Spiki Ref Input">
|
|
206
|
+
<button s-click="focusInput">Focus</button>
|
|
207
|
+
<button s-click="highlightContainer">Highlight Container</button>
|
|
305
208
|
</div>
|
|
306
209
|
|
|
307
210
|
<script>
|
|
308
|
-
spiki.data('
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
211
|
+
spiki.data('refDemo', () => ({
|
|
212
|
+
focusInput() {
|
|
213
|
+
// Access raw HTMLInputElement directly
|
|
214
|
+
this.$refs.textInput.focus();
|
|
215
|
+
this.$refs.textInput.select();
|
|
216
|
+
},
|
|
217
|
+
highlightContainer() {
|
|
218
|
+
// Manipulate component container directly
|
|
219
|
+
this.$root.classList.toggle('ring-2');
|
|
314
220
|
}
|
|
315
221
|
}));
|
|
316
222
|
</script>
|
|
317
|
-
|
|
318
223
|
```
|
|
319
224
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
### 6. Side Effects (`s-effect`)
|
|
225
|
+
---
|
|
323
226
|
|
|
324
|
-
|
|
227
|
+
### 5. Global Store (`spiki.store()`) with Auto `init()` Execution
|
|
325
228
|
|
|
326
|
-
|
|
327
|
-
<div s-data="saveApp" s-effect="autoSave">
|
|
328
|
-
<textarea s-model="note"></textarea>
|
|
329
|
-
<span s-text="status"></span>
|
|
330
|
-
</div>
|
|
229
|
+
Register shared global state with `spiki.store('name', stateObject)`. If your store defines an `init()` method, Spiki executes `store.init.call(store)` automatically upon registration:
|
|
331
230
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
this.
|
|
231
|
+
```javascript
|
|
232
|
+
// 1. Register store with automatic init hook
|
|
233
|
+
spiki.store('settings', {
|
|
234
|
+
theme: 'dark',
|
|
235
|
+
initialized: false,
|
|
236
|
+
init() {
|
|
237
|
+
// Runs automatically when store is registered!
|
|
238
|
+
this.theme = localStorage.getItem('theme') || 'dark';
|
|
239
|
+
this.initialized = true;
|
|
340
240
|
}
|
|
341
|
-
})
|
|
342
|
-
</script>
|
|
343
|
-
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
> **Note:** The function receives the element (`el`) as an argument. Useful if you need to update something visual on the container when data changes.
|
|
347
|
-
|
|
348
|
-
### 7. Form Inputs (`s-model` vs `s-value`)
|
|
349
|
-
|
|
350
|
-
* **`s-model` (Two-Way):** Updates data when user types, and updates input when data changes.
|
|
351
|
-
* **`s-value` (One-Way):** Only updates the input when data changes.
|
|
352
|
-
|
|
353
|
-
```html
|
|
354
|
-
<div s-data="formApp">
|
|
355
|
-
<input type="text" s-model="username">
|
|
356
|
-
|
|
357
|
-
<input type="text" s-value="previewName" readonly>
|
|
358
|
-
</div>
|
|
241
|
+
});
|
|
359
242
|
|
|
360
|
-
|
|
361
|
-
spiki.data('
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
243
|
+
// 2. Consume store inside any component
|
|
244
|
+
spiki.data('headerApp', () => ({
|
|
245
|
+
get currentTheme() {
|
|
246
|
+
return this.$store.settings.theme; // Reactive store subscription!
|
|
247
|
+
},
|
|
248
|
+
toggleTheme() {
|
|
249
|
+
this.$store.settings.theme = this.$store.settings.theme === 'dark' ? 'light' : 'dark';
|
|
366
250
|
}
|
|
367
251
|
}));
|
|
368
|
-
</script>
|
|
369
|
-
|
|
370
252
|
```
|
|
371
253
|
|
|
372
254
|
---
|
|
373
255
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
### 1. Basic Loop Requirement
|
|
377
|
-
|
|
378
|
-
You **MUST** use the `<template>` tag. Spiki uses the template to stamp out copies of your HTML.
|
|
379
|
-
|
|
380
|
-
```html
|
|
381
|
-
<ul>
|
|
382
|
-
<template s-for="user in users" s-key="id">
|
|
383
|
-
<li><span s-text="user.name"></span></li>
|
|
384
|
-
</template>
|
|
385
|
-
</ul>
|
|
386
|
-
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
### 2. Accessing Data Inside Loop (`this.item`)
|
|
390
|
-
|
|
391
|
-
When you trigger an event inside a loop, Spiki automatically injects the current item into `this`. The property name matches your loop alias.
|
|
392
|
-
|
|
393
|
-
```html
|
|
394
|
-
<div s-data="shopApp">
|
|
395
|
-
<ul>
|
|
396
|
-
<template s-for="product in products" s-key="id">
|
|
397
|
-
<li>
|
|
398
|
-
<span s-text="product.name"></span>
|
|
399
|
-
<button s-click="selectProduct">Select</button>
|
|
400
|
-
</li>
|
|
401
|
-
</template>
|
|
402
|
-
</ul>
|
|
403
|
-
</div>
|
|
404
|
-
|
|
405
|
-
<script>
|
|
406
|
-
spiki.data('shopApp', () => ({
|
|
407
|
-
products: [
|
|
408
|
-
{ id: 1, name: 'Laptop' },
|
|
409
|
-
{ id: 2, name: 'Phone' }
|
|
410
|
-
],
|
|
411
|
-
selectProduct() {
|
|
412
|
-
// 'this.product' is automatically available
|
|
413
|
-
console.log('You selected:', this.product.name);
|
|
414
|
-
}
|
|
415
|
-
}));
|
|
416
|
-
</script>
|
|
417
|
-
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### 3. Accessing Index
|
|
256
|
+
### 6. Unwrapping Reactive Proxies (`spiki.raw()`)
|
|
421
257
|
|
|
422
|
-
|
|
258
|
+
Spiki wraps state objects in ES6 Proxies. Use `spiki.raw(proxy)` to unwrap state back into plain JavaScript objects before calling `JSON.stringify()`, logging, or sending network payloads:
|
|
423
259
|
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
260
|
+
```javascript
|
|
261
|
+
spiki.data('userProfile', () => ({
|
|
262
|
+
user: { name: 'Alice', role: 'Admin' },
|
|
263
|
+
|
|
264
|
+
saveUser() {
|
|
265
|
+
// Extract raw, un-proxied JS object
|
|
266
|
+
const plainData = spiki.raw(this.user);
|
|
267
|
+
|
|
268
|
+
fetch('/api/user', {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
headers: { 'Content-Type': 'application/json' },
|
|
271
|
+
body: JSON.stringify(plainData)
|
|
272
|
+
});
|
|
437
273
|
}
|
|
438
274
|
}));
|
|
439
|
-
</script>
|
|
440
|
-
|
|
441
275
|
```
|
|
442
276
|
|
|
443
277
|
---
|
|
444
278
|
|
|
445
|
-
|
|
279
|
+
### 7. Reactive Side Effects (`s-effect`)
|
|
446
280
|
|
|
447
|
-
|
|
281
|
+
Use `s-effect="handler"` to run automatic reactive watchers.
|
|
448
282
|
|
|
449
|
-
|
|
283
|
+
> **Warning:** Do **NOT** mutate component state properties inside `s-effect` methods (e.g. `this.runs++`), as modifying state read inside an effect causes an infinite loop. Use `s-effect` strictly for external side effects like `localStorage`, network calls, or direct DOM updates.
|
|
450
284
|
|
|
451
285
|
```html
|
|
452
|
-
<div s-data="
|
|
453
|
-
<input
|
|
454
|
-
<button s-click="focusMe">Focus Input</button>
|
|
286
|
+
<div s-data="noteApp" s-effect="autoSave">
|
|
287
|
+
<input type="text" s-model="note">
|
|
455
288
|
</div>
|
|
456
289
|
|
|
457
290
|
<script>
|
|
458
|
-
spiki.data('
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
291
|
+
spiki.data('noteApp', () => ({
|
|
292
|
+
note: 'Hello Spiki',
|
|
293
|
+
autoSave() {
|
|
294
|
+
// Pure side effect: read state and write to localStorage
|
|
295
|
+
try {
|
|
296
|
+
localStorage.setItem('user_note', this.note);
|
|
297
|
+
} catch(e) {}
|
|
462
298
|
}
|
|
463
299
|
}));
|
|
464
300
|
</script>
|
|
465
|
-
|
|
466
301
|
```
|
|
467
302
|
|
|
468
303
|
---
|
|
469
304
|
|
|
470
|
-
## Core Concepts
|
|
471
|
-
|
|
472
|
-
### Component Instance (`this`)
|
|
473
|
-
|
|
474
|
-
Inside your functions, `this` refers to your component state.
|
|
475
|
-
|
|
476
|
-
> **Warning:** Do not use arrow functions for methods (`func: () => {}`) if you need `this`. Use method shorthand (`func() {}`) instead.
|
|
477
|
-
|
|
478
|
-
Spiki injects helper properties:
|
|
479
|
-
|
|
480
|
-
* **`this.$root`**: The HTML element containing the component.
|
|
481
|
-
* **`this.$refs`**: Access elements marked with `s-ref`.
|
|
482
|
-
* **`this.$store`**: Access the global store.
|
|
483
|
-
* **`this.$parent`**: Access the parent component.
|
|
484
|
-
|
|
485
|
-
### Global Store
|
|
486
|
-
|
|
487
|
-
Share state across multiple components.
|
|
488
|
-
|
|
489
|
-
```javascript
|
|
490
|
-
// 1. Define Store
|
|
491
|
-
spiki.store('user', {
|
|
492
|
-
name: 'John Doe',
|
|
493
|
-
isLoggedIn: true
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
// 2. Use in Component
|
|
497
|
-
spiki.data('profile', () => ({
|
|
498
|
-
get userName() {
|
|
499
|
-
return this.$store.user.name; // Reactive!
|
|
500
|
-
}
|
|
501
|
-
}));
|
|
502
|
-
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
### `spiki.raw(proxy)`
|
|
506
|
-
|
|
507
|
-
Get the original object from a Spiki proxy. Useful for console logging or API calls.
|
|
508
|
-
|
|
509
|
-
```javascript
|
|
510
|
-
const cleanData = spiki.raw(this.myData);
|
|
511
|
-
console.log(cleanData);
|
|
512
|
-
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
---
|
|
516
|
-
|
|
517
|
-
## Browser Support
|
|
518
|
-
|
|
519
|
-
Spiki requires a browser that supports **ES6 Proxy**.
|
|
520
|
-
|
|
521
|
-
* **Supported:** Chrome, Firefox, Edge, Safari (Modern versions).
|
|
522
|
-
* **Not Supported:** Internet Explorer 11.
|
|
523
|
-
|
|
524
305
|
## License
|
|
525
306
|
|
|
526
307
|
MIT License.
|
package/package.json
CHANGED
package/spiki.esm.js
CHANGED
|
@@ -160,7 +160,7 @@ var spiki = (() => {
|
|
|
160
160
|
while (proto && proto !== Object.prototype) {
|
|
161
161
|
if (Object.prototype.hasOwnProperty.call(proto, key)) {
|
|
162
162
|
var res = Reflect.set(proto, key, val);
|
|
163
|
-
if (!pauseTracking && val !== old) trigger(target, key);
|
|
163
|
+
if (!pauseTracking && (val !== old || (val && typeof val === 'object'))) trigger(target, key);
|
|
164
164
|
return res;
|
|
165
165
|
}
|
|
166
166
|
proto = Object.getPrototypeOf(proto);
|
|
@@ -169,7 +169,7 @@ var spiki = (() => {
|
|
|
169
169
|
|
|
170
170
|
var res = Reflect.set(target, key, val, receiver);
|
|
171
171
|
if (!pauseTracking && res) {
|
|
172
|
-
if (!hadKey || val !== old) {
|
|
172
|
+
if (!hadKey || val !== old || (val && typeof val === 'object')) {
|
|
173
173
|
trigger(target, key);
|
|
174
174
|
if (isArr) trigger(target, 'length');
|
|
175
175
|
else if (!hadKey) trigger(target, '_k');
|
|
@@ -233,7 +233,7 @@ var spiki = (() => {
|
|
|
233
233
|
for (var k in val) val[k] ? (obj[k] = 1) : delete obj[k];
|
|
234
234
|
}
|
|
235
235
|
var res = Object.keys(obj).join(' ');
|
|
236
|
-
if (el.
|
|
236
|
+
if ((el.getAttribute('class') || '') !== res) el.setAttribute('class', res);
|
|
237
237
|
};
|
|
238
238
|
|
|
239
239
|
var mount = (rootElement, parentScope) => {
|
|
@@ -328,7 +328,7 @@ var spiki = (() => {
|
|
|
328
328
|
var directiveFor = !directiveIf && el.tagName === 'TEMPLATE' && el.getAttribute('s-for');
|
|
329
329
|
var bindings = [];
|
|
330
330
|
|
|
331
|
-
if (el.hasAttribute('class') && !el._n) el._n = el.
|
|
331
|
+
if (el.hasAttribute('class') && !el._n) el._n = el.getAttribute('class');
|
|
332
332
|
|
|
333
333
|
if (directiveIf) {
|
|
334
334
|
var end = document.createTextNode('');
|
package/spiki.esm.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var spiki=(()=>{var r,e=Object,t=Array,a=Reflect,n=Promise,o=Node,i=document,l=console,
|
|
1
|
+
var spiki=(()=>{var r,e=Object,t=Array,a=Reflect,n=Promise,o=Node,i=document,l=console,f="length",v="value",s="push",u="forEach",p="indexOf",c="split",_="slice",d="call",y="keys",h="get",m="prototype",g="hasOwnProperty",b="getPrototypeOf",x="defineProperty",P="create",k="isArray",A="getAttribute",N="setAttribute",w="removeAttribute",E="hasAttribute",S="tagName",T="type",$="name",O="checked",j="parentNode",C="nextSibling",L="textContent",q="innerHTML",M="replaceWith",D="insertBefore",F="cloneNode",K="remove",B="createTextNode",H="addEventListener",R="input",W="checkbox",z="function",G="radio",I="class",J="model",Q="object",U="unshift",V="scope",X="trim",Y="cleanups",Z="nodes",rr="deps",er="destroy",tr="init",ar="charCodeAt",nr="sched",or="string",ir="unmount",lr="attr",fr="s-data",vr="s-if",sr=e[P](null),ur=[],pr=!1,cr=null,_r=!1,dr=n.resolve(),yr={blur:1,focus:1,scroll:1,load:1,error:1,mouseenter:1,mouseleave:1,toggle:1},hr=r=>{r._q||(r._q=!0,ur[s](r),pr||(pr=!0,dr.then(()=>{var r=ur;ur=[],pr=!1;for(var e=r[f];e--;)r[e]._q=!1,r[e]()})))},mr=(r,e)=>{var a,n=r;if(typeof e===or)a=r?r[e]:void 0;else{for(var o=0,i=e[f];o<i-1&&n;)n=n[e[o++]];a=n?n[e[i-1]]:void 0}if(void 0===a){var v=t[k](e)?e.join("."):e;l.warn("[spikijs] Property undefined: "+v)}return{ctx:n,val:a}},gr={};[s,"pop","shift",U,"splice","sort","reverse"][u](r=>{gr[r]=function(...e){_r=!0;try{return t[m][r].apply(this,e)}finally{_r=!1,xr(this,f)}}});var br=(r,t)=>{if(cr){var a=r._d||(e[x](r,"_d",{value:e[P](null),writable:!0}),r._d),n=a[t]||(a[t]=[]);-1===n[p](cr)&&(n[s](cr),cr[rr][s](n))}},xr=(r,e)=>{if(!_r&&r._d&&r._d[e])for(var t=r._d[e][_](),a=0,n=t[f];a<n;a++){var o=t[a];o[nr]?o[nr](o):o()}},Pr=r=>{for(var e=r[rr][f];e--;){var t=r[rr][e],a=t[p](r);-1!==a&&(t[a]=t[t[f]-1],t.pop())}r[rr]=[]},kr=(r,e)=>{var t=()=>{Pr(t);var e=cr;cr=t;try{r()}finally{cr=e}};return t[rr]=[],t[nr]=e,t(),()=>Pr(t)},Ar=r=>{if(!r||typeof r!==Q||r._y||r instanceof o)return r;if(e[m][g][d](r,"_p"))return r._p;var n=new Proxy(r,{get:(r,n,i)=>{if("_y"===n)return i===r._p;if("_t"===n)return r;if("_d"===n)return r._d;if("_i"===n)return r._i;if(t[k](r)&&gr[g](n))return gr[n];var l=e.getOwnPropertyDescriptor(r,n);if(l&&l[h]){var f="_"+n;return f in r||kr(()=>i[f]=l[h][d](i)),i[f]}br(r,n);var v=a[h](r,n,i);return!v||typeof v!==Q||v instanceof o?v:Ar(v)},set:(r,n,o,i)=>{var l=r[n],v=t[k](r),s=v?Number(n)<r[f]:e[m][g][d](r,n);if(r._i&&!s)for(var u=e[b](r);u&&u!==e[m];){if(e[m][g][d](u,n)){var p=a.set(u,n,o);return!_r&&(o!==l||o&&typeof o===Q)&&xr(r,n),p}u=e[b](u)}return p=a.set(r,n,o,i),!_r&&p&&(!s||o!==l||o&&typeof o===Q)&&(xr(r,n),v?xr(r,f):s||xr(r,"_k")),p},deleteProperty:(r,n)=>{var o=e[m][g][d](r,n),i=a.deleteProperty(r,n);return i&&o&&(xr(r,n),t[k](r)?xr(r,f):xr(r,"_k")),i},ownKeys:r=>(br(r,"_k"),a.ownKeys(r))});return e[x](r,"_p",{value:n}),n};r=Ar({});var Nr=e[P](null);Nr.text=(r,e)=>{e=null==e?"":e,r[L]!==String(e)&&(r[L]=e)},Nr.html=(r,e)=>{r[q]!=e&&(r[q]=null==e?"":e)},Nr[v]=(r,e)=>{r[T]===W?r[O]=!!e:r[T]===G?r[O]=r[v]==e:r[v]!=e&&(r[v]=null==e?"":e)},Nr[lr]=(r,e,t)=>{if(null==e||!1===e)r[w](t);else{var a=!0===e?"":String(e);r[A](t)!==a&&r[N](t,a)}},Nr[I]=(r,t)=>{var a=r._n||"",n=e[P](null);if(a[c](/\s+/)[u](r=>r&&(n[r]=1)),typeof t===or)t[c](/\s+/)[u](r=>r&&(n[r]=1));else if(t)for(var o in t)t[o]?n[o]=1:delete n[o];var i=e[y](n).join(" ");(r[A](I)||"")!==i&&r[N](I,i)};var wr=(a,n)=>{if(a._m)return a._m;var o=a[A](fr);if(sr[o]){var l=sr[o]();n&&e.setPrototypeOf(l,n);var h=Ar(l);h.$refs={},h.$root=a,h.$store=r,h.$parent=n;var g=[],b=e[P](null),N=r=>{var t=r.target;if(r[T]!==R||!t._c)for(var n=a[j],o=r[T];t&&t!==n;){var i=t._h&&t._h[o];if(i){var l=t._s;if(!l||l.$root!==h.$root){t=t[j];continue}for(var s=i[f];s--;){var u=i[s],p=u.p;if(u[J]){var c=t[T]===W?t[O]:t[v];if(typeof c===or){var _=c[X]();_&&isFinite(_)&&(48===_[ar](0)&&1!==_[f]&&46!==_[ar](1)||(c=Number(_)))}if(typeof p===or)l&&(l[p]=c);else{for(var y=l,m=0,g=p[f]-1;m<g&&y;)y=y[p[m++]];y&&(y[p[g]]=c)}}else{var b=mr(l,p);typeof b.val===z&&(e[x](r,"currentTarget",{configurable:!0,value:t}),b.val[d](b.ctx,r))}}}t=t[j]}},L=r=>{b[r]||(b[r]=!0,a[H](r,N,!!yr[r]))},q=(r,n,o)=>{if(1===r.nodeType&&!r[E]("s-ignore"))if(r!==a&&r[E](fr)){var l=wr(r,n);l&&o[s](l[ir])}else{var g=r[A](vr),b=!g&&"TEMPLATE"===r[S]&&r[A]("s-for"),O=[];if(r[E](I)&&!r._n&&(r._n=r[A](I)),g){var rr=i[B](""),nr=null,or=[];r[M](rr);var sr="!"===g[0],ur=sr?g[_](1):g,pr=-1===ur[p](".")?ur:ur[c](".");return o[s](()=>{for(var r=or[f];r--;)or[r]()}),o[s](kr(()=>{var e=mr(n,pr).val;if(sr&&(e=!e),e)nr||((nr=r[F](!0))[w](vr),q(nr,n,or),rr[j][D](nr,rr));else if(nr){for(var t=or[f];t--;)or[t]();or=[],nr[K](),nr=null}},hr))}if(b){var cr=b[p](" in ");if(-1!==cr){var _r,dr=b[_](0,cr)[X](),yr=b[_](cr+4)[X](),gr=dr.replace(/[()]/g,"")[c](","),xr=gr[0][X](),Pr=gr[1]?gr[1][X]():null,Er=-1===yr[p](".")?yr:yr[c]("."),Sr=r[A]("s-key"),Tr=Sr?-1===Sr[p](".")?Sr:Sr[c]("."):null,$r=(rr=i[B](""),e[P](null));return r[M](rr),o[s](()=>{for(var r in $r)$r[r][Y][u](r=>r())}),o[s](kr(()=>{var a=mr(n,Er).val||[];"number"==typeof a&&(a=t.from({length:Math.max(0,Math.floor(a))},(r,e)=>e+1)),t[k](a)&&br(a,f);var o=i.createDocumentFragment(),l=rr;_r=e[P](null);for(var v=t[k](a),s=v?a:e[y](a),p=v?a[f]:s[f],c=0;c<p;c++){var h,g=v?c:s[c],b=v?a[c]:a[g];h=null==b||typeof b!==Q?String(b):Tr?mr(b,Tr).val:String(g)+"_o",_r[h]&&(h+="_"+c);var A=$r[h];if(A)A[V][xr]=b,Pr&&(A[V][Pr]=g);else{var N=r.content[F](!0),w=e[P](n);e[x](w,"_i",{value:!0});var E=Ar(w);E[xr]=b,Pr&&(E[Pr]=g);for(var S=t[m][_][d](N.childNodes),T=[],$=0;$<S[f];$++)q(S[$],E,T);A={nodes:t[m][_][d](N.childNodes),scope:E,cleanups:T},$r[h]=A}if(A[Z][0]!==l[C]){for($=0;$<A[Z][f];$++)o.appendChild(A[Z][$]);l[j][D](o,l[C])}l=A[Z][A[Z][f]-1],_r[h]=!0}for(var O in $r)_r[O]||($r[O][Y][u](r=>r()),$r[O][Z][u](r=>r[K]()),delete $r[O])},hr))}}if(r.hasAttributes())for(var Or=r.attributes,jr=Or[f];jr--;){var Cr=Or[jr],Lr=Cr[$],qr=Cr[v];if(58===Lr[ar](0)){var Mr=Lr[_](1),Dr=33===qr[ar](0),Fr=Dr?qr[_](1):qr;pr=-1===Fr[p](".")?Fr:Fr[c]("."),O[s]({type:lr,name:Mr,path:pr,neg:Dr})}else if(115===Lr[ar](0)&&45===Lr[ar](1)){let e=Lr[_](2),t=-1===qr[p](".")?qr:qr[c](".");if(e===tr||e===er)((e,t)=>{var a=mr(n,t);typeof a.val===z&&(e===tr?hr(()=>{var e=a.val[d](a.ctx,r);typeof e===z&&o[s](e)}):o[s](()=>a.val[d](a.ctx,r)))})(e,t);else if("effect"===e)o[s](kr(()=>{var e=mr(n,t);typeof e.val===z&&e.val[d](e.ctx,r)},hr));else if(e===J){O[s]({type:v,path:t}),r._s=n,r._h=r._h||{};var Kr=r[T]===W||r[T]===G||"SELECT"===r[S]?"change":R;Kr===R&&(r[H]("compositionstart",()=>r._c=!0),r[H]("compositionend",()=>{r._c=!1,N({target:r,type:R})})),(r._h[Kr]=r._h[Kr]||[])[s]({model:!0,p:t}),L(Kr)}else"ref"===e?h.$refs[qr]=r:Nr[e]?O[s]({type:e,path:t}):(r._s=n,r._h=r._h||{},(r._h[e]=r._h[e]||[])[U]({p:t}),L(e))}}O[f]&&o[s](kr(()=>{for(var e=O[f];e--;){var t=O[e],a=mr(n,t.path),o=typeof a.val===z?a.val[d](a.ctx,r):a.val;t[T]===lr?(t.neg&&(o=!o),t[$]===I?Nr[I](r,o):Nr[lr](r,o,t[$])):Nr[t[T]](r,o)}},hr));for(var Br=r.firstChild;Br;){var Hr=Br[C];q(Br,n,o),Br=Hr}}};q(a,h,g),h[tr]&&h[tr]();var rr={unmount:()=>{h[er]&&h[er][d](h);for(var r=g[f];r--;)g[r]();for(var e in b)a.removeEventListener(e,N);a._m=null}};return a._m=rr,rr}},Er=r=>{if(!r||typeof r!==Q)return r;var e=r._t||r;if(t[k](e))return e.map(r=>Er(r));var a={};for(var n in e)a[n]=Er(e[n]);return a};return{data:(r,e)=>{sr[r]=e},start:()=>{for(var r=i.querySelectorAll("[s-data]"),e=r[f];e--;)wr(r[e])},store:(e,t)=>{if(void 0===t)return r[e];r[e]=t;var a=r[e];return a&&typeof a[tr]===z&&a[tr][d](a),a},raw:Er,mount:r=>wr(r),unmount:r=>{r&&r._m&&r._m[ir]()}}})();export default spiki;
|
package/spiki.js
CHANGED
|
@@ -163,7 +163,7 @@ var spiki = (() => {
|
|
|
163
163
|
while (proto && proto !== Object.prototype) {
|
|
164
164
|
if (Object.prototype.hasOwnProperty.call(proto, key)) {
|
|
165
165
|
var res = Reflect.set(proto, key, val);
|
|
166
|
-
if (!pauseTracking && val !== old) trigger(target, key);
|
|
166
|
+
if (!pauseTracking && (val !== old || (val && typeof val === 'object'))) trigger(target, key);
|
|
167
167
|
return res;
|
|
168
168
|
}
|
|
169
169
|
proto = Object.getPrototypeOf(proto);
|
|
@@ -172,7 +172,7 @@ var spiki = (() => {
|
|
|
172
172
|
|
|
173
173
|
var res = Reflect.set(target, key, val, receiver);
|
|
174
174
|
if (!pauseTracking && res) {
|
|
175
|
-
if (!hadKey || val !== old) {
|
|
175
|
+
if (!hadKey || val !== old || (val && typeof val === 'object')) {
|
|
176
176
|
trigger(target, key);
|
|
177
177
|
if (isArr) trigger(target, 'length');
|
|
178
178
|
else if (!hadKey) trigger(target, '_k');
|
|
@@ -236,7 +236,7 @@ var spiki = (() => {
|
|
|
236
236
|
for (var k in val) val[k] ? (obj[k] = 1) : delete obj[k];
|
|
237
237
|
}
|
|
238
238
|
var res = Object.keys(obj).join(' ');
|
|
239
|
-
if (el.
|
|
239
|
+
if ((el.getAttribute('class') || '') !== res) el.setAttribute('class', res);
|
|
240
240
|
};
|
|
241
241
|
|
|
242
242
|
var mount = (rootElement, parentScope) => {
|
|
@@ -331,7 +331,7 @@ var spiki = (() => {
|
|
|
331
331
|
var directiveFor = !directiveIf && el.tagName === 'TEMPLATE' && el.getAttribute('s-for');
|
|
332
332
|
var bindings = [];
|
|
333
333
|
|
|
334
|
-
if (el.hasAttribute('class') && !el._n) el._n = el.
|
|
334
|
+
if (el.hasAttribute('class') && !el._n) el._n = el.getAttribute('class');
|
|
335
335
|
|
|
336
336
|
if (directiveIf) {
|
|
337
337
|
var end = document.createTextNode('');
|
package/spiki.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";var spiki=(()=>{var r,e=Object,t=Array,a=Reflect,n=Promise,o=Node,i=document,l=console,
|
|
1
|
+
(()=>{"use strict";var spiki=(()=>{var r,e=Object,t=Array,a=Reflect,n=Promise,o=Node,i=document,l=console,f="length",v="value",s="push",u="forEach",p="indexOf",c="split",_="slice",d="call",y="keys",h="get",m="prototype",g="hasOwnProperty",b="getPrototypeOf",x="defineProperty",P="create",k="isArray",A="getAttribute",N="setAttribute",w="removeAttribute",E="hasAttribute",S="tagName",T="type",$="name",O="checked",j="parentNode",C="nextSibling",L="textContent",q="innerHTML",M="replaceWith",D="insertBefore",F="cloneNode",K="remove",B="createTextNode",H="addEventListener",R="input",W="checkbox",z="function",G="radio",I="class",J="model",Q="object",U="unshift",V="scope",X="trim",Y="cleanups",Z="nodes",rr="deps",er="destroy",tr="init",ar="charCodeAt",nr="sched",or="string",ir="unmount",lr="attr",fr="s-data",vr="s-if",sr=e[P](null),ur=[],pr=!1,cr=null,_r=!1,dr=n.resolve(),yr={blur:1,focus:1,scroll:1,load:1,error:1,mouseenter:1,mouseleave:1,toggle:1},hr=r=>{r._q||(r._q=!0,ur[s](r),pr||(pr=!0,dr.then(()=>{var r=ur;ur=[],pr=!1;for(var e=r[f];e--;)r[e]._q=!1,r[e]()})))},mr=(r,e)=>{var a,n=r;if(typeof e===or)a=r?r[e]:void 0;else{for(var o=0,i=e[f];o<i-1&&n;)n=n[e[o++]];a=n?n[e[i-1]]:void 0}if(void 0===a){var v=t[k](e)?e.join("."):e;l.warn("[spikijs] Property undefined: "+v)}return{ctx:n,val:a}},gr={};[s,"pop","shift",U,"splice","sort","reverse"][u](r=>{gr[r]=function(...e){_r=!0;try{return t[m][r].apply(this,e)}finally{_r=!1,xr(this,f)}}});var br=(r,t)=>{if(cr){var a=r._d||(e[x](r,"_d",{value:e[P](null),writable:!0}),r._d),n=a[t]||(a[t]=[]);-1===n[p](cr)&&(n[s](cr),cr[rr][s](n))}},xr=(r,e)=>{if(!_r&&r._d&&r._d[e])for(var t=r._d[e][_](),a=0,n=t[f];a<n;a++){var o=t[a];o[nr]?o[nr](o):o()}},Pr=r=>{for(var e=r[rr][f];e--;){var t=r[rr][e],a=t[p](r);-1!==a&&(t[a]=t[t[f]-1],t.pop())}r[rr]=[]},kr=(r,e)=>{var t=()=>{Pr(t);var e=cr;cr=t;try{r()}finally{cr=e}};return t[rr]=[],t[nr]=e,t(),()=>Pr(t)},Ar=r=>{if(!r||typeof r!==Q||r._y||r instanceof o)return r;if(e[m][g][d](r,"_p"))return r._p;var n=new Proxy(r,{get:(r,n,i)=>{if("_y"===n)return i===r._p;if("_t"===n)return r;if("_d"===n)return r._d;if("_i"===n)return r._i;if(t[k](r)&&gr[g](n))return gr[n];var l=e.getOwnPropertyDescriptor(r,n);if(l&&l[h]){var f="_"+n;return f in r||kr(()=>i[f]=l[h][d](i)),i[f]}br(r,n);var v=a[h](r,n,i);return!v||typeof v!==Q||v instanceof o?v:Ar(v)},set:(r,n,o,i)=>{var l=r[n],v=t[k](r),s=v?Number(n)<r[f]:e[m][g][d](r,n);if(r._i&&!s)for(var u=e[b](r);u&&u!==e[m];){if(e[m][g][d](u,n)){var p=a.set(u,n,o);return!_r&&(o!==l||o&&typeof o===Q)&&xr(r,n),p}u=e[b](u)}return p=a.set(r,n,o,i),!_r&&p&&(!s||o!==l||o&&typeof o===Q)&&(xr(r,n),v?xr(r,f):s||xr(r,"_k")),p},deleteProperty:(r,n)=>{var o=e[m][g][d](r,n),i=a.deleteProperty(r,n);return i&&o&&(xr(r,n),t[k](r)?xr(r,f):xr(r,"_k")),i},ownKeys:r=>(br(r,"_k"),a.ownKeys(r))});return e[x](r,"_p",{value:n}),n};r=Ar({});var Nr=e[P](null);Nr.text=(r,e)=>{e=null==e?"":e,r[L]!==String(e)&&(r[L]=e)},Nr.html=(r,e)=>{r[q]!=e&&(r[q]=null==e?"":e)},Nr[v]=(r,e)=>{r[T]===W?r[O]=!!e:r[T]===G?r[O]=r[v]==e:r[v]!=e&&(r[v]=null==e?"":e)},Nr[lr]=(r,e,t)=>{if(null==e||!1===e)r[w](t);else{var a=!0===e?"":String(e);r[A](t)!==a&&r[N](t,a)}},Nr[I]=(r,t)=>{var a=r._n||"",n=e[P](null);if(a[c](/\s+/)[u](r=>r&&(n[r]=1)),typeof t===or)t[c](/\s+/)[u](r=>r&&(n[r]=1));else if(t)for(var o in t)t[o]?n[o]=1:delete n[o];var i=e[y](n).join(" ");(r[A](I)||"")!==i&&r[N](I,i)};var wr=(a,n)=>{if(a._m)return a._m;var o=a[A](fr);if(sr[o]){var l=sr[o]();n&&e.setPrototypeOf(l,n);var h=Ar(l);h.$refs={},h.$root=a,h.$store=r,h.$parent=n;var g=[],b=e[P](null),N=r=>{var t=r.target;if(r[T]!==R||!t._c)for(var n=a[j],o=r[T];t&&t!==n;){var i=t._h&&t._h[o];if(i){var l=t._s;if(!l||l.$root!==h.$root){t=t[j];continue}for(var s=i[f];s--;){var u=i[s],p=u.p;if(u[J]){var c=t[T]===W?t[O]:t[v];if(typeof c===or){var _=c[X]();_&&isFinite(_)&&(48===_[ar](0)&&1!==_[f]&&46!==_[ar](1)||(c=Number(_)))}if(typeof p===or)l&&(l[p]=c);else{for(var y=l,m=0,g=p[f]-1;m<g&&y;)y=y[p[m++]];y&&(y[p[g]]=c)}}else{var b=mr(l,p);typeof b.val===z&&(e[x](r,"currentTarget",{configurable:!0,value:t}),b.val[d](b.ctx,r))}}}t=t[j]}},L=r=>{b[r]||(b[r]=!0,a[H](r,N,!!yr[r]))},q=(r,n,o)=>{if(1===r.nodeType&&!r[E]("s-ignore"))if(r!==a&&r[E](fr)){var l=wr(r,n);l&&o[s](l[ir])}else{var g=r[A](vr),b=!g&&"TEMPLATE"===r[S]&&r[A]("s-for"),O=[];if(r[E](I)&&!r._n&&(r._n=r[A](I)),g){var rr=i[B](""),nr=null,or=[];r[M](rr);var sr="!"===g[0],ur=sr?g[_](1):g,pr=-1===ur[p](".")?ur:ur[c](".");return o[s](()=>{for(var r=or[f];r--;)or[r]()}),o[s](kr(()=>{var e=mr(n,pr).val;if(sr&&(e=!e),e)nr||((nr=r[F](!0))[w](vr),q(nr,n,or),rr[j][D](nr,rr));else if(nr){for(var t=or[f];t--;)or[t]();or=[],nr[K](),nr=null}},hr))}if(b){var cr=b[p](" in ");if(-1!==cr){var _r,dr=b[_](0,cr)[X](),yr=b[_](cr+4)[X](),gr=dr.replace(/[()]/g,"")[c](","),xr=gr[0][X](),Pr=gr[1]?gr[1][X]():null,Er=-1===yr[p](".")?yr:yr[c]("."),Sr=r[A]("s-key"),Tr=Sr?-1===Sr[p](".")?Sr:Sr[c]("."):null,$r=(rr=i[B](""),e[P](null));return r[M](rr),o[s](()=>{for(var r in $r)$r[r][Y][u](r=>r())}),o[s](kr(()=>{var a=mr(n,Er).val||[];"number"==typeof a&&(a=t.from({length:Math.max(0,Math.floor(a))},(r,e)=>e+1)),t[k](a)&&br(a,f);var o=i.createDocumentFragment(),l=rr;_r=e[P](null);for(var v=t[k](a),s=v?a:e[y](a),p=v?a[f]:s[f],c=0;c<p;c++){var h,g=v?c:s[c],b=v?a[c]:a[g];h=null==b||typeof b!==Q?String(b):Tr?mr(b,Tr).val:String(g)+"_o",_r[h]&&(h+="_"+c);var A=$r[h];if(A)A[V][xr]=b,Pr&&(A[V][Pr]=g);else{var N=r.content[F](!0),w=e[P](n);e[x](w,"_i",{value:!0});var E=Ar(w);E[xr]=b,Pr&&(E[Pr]=g);for(var S=t[m][_][d](N.childNodes),T=[],$=0;$<S[f];$++)q(S[$],E,T);A={nodes:t[m][_][d](N.childNodes),scope:E,cleanups:T},$r[h]=A}if(A[Z][0]!==l[C]){for($=0;$<A[Z][f];$++)o.appendChild(A[Z][$]);l[j][D](o,l[C])}l=A[Z][A[Z][f]-1],_r[h]=!0}for(var O in $r)_r[O]||($r[O][Y][u](r=>r()),$r[O][Z][u](r=>r[K]()),delete $r[O])},hr))}}if(r.hasAttributes())for(var Or=r.attributes,jr=Or[f];jr--;){var Cr=Or[jr],Lr=Cr[$],qr=Cr[v];if(58===Lr[ar](0)){var Mr=Lr[_](1),Dr=33===qr[ar](0),Fr=Dr?qr[_](1):qr;pr=-1===Fr[p](".")?Fr:Fr[c]("."),O[s]({type:lr,name:Mr,path:pr,neg:Dr})}else if(115===Lr[ar](0)&&45===Lr[ar](1)){let e=Lr[_](2),t=-1===qr[p](".")?qr:qr[c](".");if(e===tr||e===er)((e,t)=>{var a=mr(n,t);typeof a.val===z&&(e===tr?hr(()=>{var e=a.val[d](a.ctx,r);typeof e===z&&o[s](e)}):o[s](()=>a.val[d](a.ctx,r)))})(e,t);else if("effect"===e)o[s](kr(()=>{var e=mr(n,t);typeof e.val===z&&e.val[d](e.ctx,r)},hr));else if(e===J){O[s]({type:v,path:t}),r._s=n,r._h=r._h||{};var Kr=r[T]===W||r[T]===G||"SELECT"===r[S]?"change":R;Kr===R&&(r[H]("compositionstart",()=>r._c=!0),r[H]("compositionend",()=>{r._c=!1,N({target:r,type:R})})),(r._h[Kr]=r._h[Kr]||[])[s]({model:!0,p:t}),L(Kr)}else"ref"===e?h.$refs[qr]=r:Nr[e]?O[s]({type:e,path:t}):(r._s=n,r._h=r._h||{},(r._h[e]=r._h[e]||[])[U]({p:t}),L(e))}}O[f]&&o[s](kr(()=>{for(var e=O[f];e--;){var t=O[e],a=mr(n,t.path),o=typeof a.val===z?a.val[d](a.ctx,r):a.val;t[T]===lr?(t.neg&&(o=!o),t[$]===I?Nr[I](r,o):Nr[lr](r,o,t[$])):Nr[t[T]](r,o)}},hr));for(var Br=r.firstChild;Br;){var Hr=Br[C];q(Br,n,o),Br=Hr}}};q(a,h,g),h[tr]&&h[tr]();var rr={unmount:()=>{h[er]&&h[er][d](h);for(var r=g[f];r--;)g[r]();for(var e in b)a.removeEventListener(e,N);a._m=null}};return a._m=rr,rr}},Er=r=>{if(!r||typeof r!==Q)return r;var e=r._t||r;if(t[k](e))return e.map(r=>Er(r));var a={};for(var n in e)a[n]=Er(e[n]);return a};return{data:(r,e)=>{sr[r]=e},start:()=>{for(var r=i.querySelectorAll("[s-data]"),e=r[f];e--;)wr(r[e])},store:(e,t)=>{if(void 0===t)return r[e];r[e]=t;var a=r[e];return a&&typeof a[tr]===z&&a[tr][d](a),a},raw:Er,mount:r=>wr(r),unmount:r=>{r&&r._m&&r._m[ir]()}}})();window.spiki=spiki})();
|