spikijs 1.1.23 → 1.1.25
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 +186 -408
- 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,304 @@
|
|
|
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
|
-
##
|
|
17
|
-
|
|
18
|
-
### CDN (Browser)
|
|
19
|
-
Simply add the script tag to your HTML. Spiki will automatically attach to `window.spiki`.
|
|
20
|
-
|
|
21
|
-
```html
|
|
22
|
-
<script src="https://unpkg.com/spikijs"></script>
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### NPM (Module)
|
|
27
|
-
|
|
28
|
-
Install via package manager.
|
|
19
|
+
## Content Security Policy (CSP) & Zero Inline Logic
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
npm install spikijs
|
|
21
|
+
SpikiJS enforces strict separation of concerns to comply with modern web security standards:
|
|
32
22
|
|
|
33
|
-
|
|
23
|
+
1. **No Inline JS Logic:** Do **NOT** write expressions inside attributes (e.g. `<button s-click="count++">` is forbidden).
|
|
24
|
+
2. **No Inline Arguments:** Do **NOT** pass function parameters in HTML (e.g. `<button s-click="save(item)">` is forbidden).
|
|
25
|
+
3. **Method Name Binding:** Always bind function names directly (e.g. `<button s-click="save">`).
|
|
26
|
+
4. **Access State via `this`:** Mutate and access state properties directly inside component methods via `this.variableName`.
|
|
34
27
|
|
|
35
28
|
---
|
|
36
29
|
|
|
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.
|
|
30
|
+
## Installation
|
|
76
31
|
|
|
77
|
-
|
|
32
|
+
### CDN (Browser)
|
|
33
|
+
Include SpikiJS via script tag. Spiki automatically exposes `window.spiki`:
|
|
78
34
|
|
|
79
35
|
```html
|
|
80
|
-
<
|
|
81
|
-
Count: <span s-text="count"></span>
|
|
82
|
-
<button s-click="increment">+</button>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
36
|
+
<script src="https://unpkg.com/spikijs"></script>
|
|
85
37
|
```
|
|
86
38
|
|
|
87
|
-
|
|
39
|
+
### ESM Module (Modern Browsers / NPM)
|
|
40
|
+
Import SpikiJS directly as an ES module:
|
|
88
41
|
|
|
89
42
|
```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
|
-
|
|
43
|
+
import spiki from './spikijs/spiki.esm.js';
|
|
110
44
|
```
|
|
111
45
|
|
|
112
46
|
---
|
|
113
47
|
|
|
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
|
-
```
|
|
48
|
+
## Directives & API Reference
|
|
49
|
+
|
|
50
|
+
| Directive / API | Description | Example |
|
|
51
|
+
| :--- | :--- | :--- |
|
|
52
|
+
| **`s-data`** | Defines component state scope. | `<div s-data="myApp">` |
|
|
53
|
+
| **`s-text`** | Sets safe text content. | `<span s-text="username"></span>` |
|
|
54
|
+
| **`s-html`** | Sets inner HTML (XSS caution). | `<div s-html="richText"></div>` |
|
|
55
|
+
| **`s-if`** | Conditionally mounts/unmounts DOM nodes (supports `!`). | `<div s-if="!isOnline">Offline</div>` |
|
|
56
|
+
| **`:hidden`** | Toggles native `hidden` visibility (replaces `s-show`, supports `!`). | `<div hidden :hidden="!isVisible">Content</div>` |
|
|
57
|
+
| **`s-for`** | Loops array items inside `<template>` tags. | `<template s-for="(item, idx) in list" s-key="id">` |
|
|
58
|
+
| **`s-key`** | Unique key tracking for list items. | `<template ... s-key="id">` |
|
|
59
|
+
| **`s-model`** | Two-way input data binding. | `<input s-model="query">` |
|
|
60
|
+
| **`s-value`** | One-way value binding. | `<input s-value="formattedText" readonly>` |
|
|
61
|
+
| **`s-effect`** | Automatic reactive watcher (runs on state mutations). | `<div s-effect="saveToStorage">` |
|
|
62
|
+
| **`s-ref`** | Registers DOM element reference in `this.$refs`. | `<input s-ref="textInput">` |
|
|
63
|
+
| **`s-init`** | Executes hook on component mount. | `<div s-init="onMount">` |
|
|
64
|
+
| **`s-destroy`** | Executes hook on component unmount. | `<div s-destroy="onUnmount">` |
|
|
65
|
+
| **`s-ignore`** | Skips DOM compilation for static blocks. | `<div s-ignore>` |
|
|
66
|
+
| **`:[attr]`** | Binds dynamic attributes (HTML & SVG). | `<img :src="userAvatar">` |
|
|
67
|
+
| **`:class`** | Binds dynamic classes (HTML & SVG). | `<div :class="activeClass">` |
|
|
68
|
+
| **`s-[event]`** | Attaches event listener (`s-click`, `s-keyup`, etc.). | `<button s-click="submitForm">` |
|
|
69
|
+
| **`this.$root`** | Accesses component's root container element. | `this.$root.classList.add('active');` |
|
|
70
|
+
| **`this.$refs`** | Object containing all `s-ref` DOM elements. | `this.$refs.textInput.focus();` |
|
|
71
|
+
| **`this.$store`** | Accesses registered global stores. | `this.$store.settings.theme` |
|
|
72
|
+
| **`spiki.store()`** | Registers global store (runs `init()` automatically if present). | `spiki.store('settings', { theme: 'dark', init() {} })` |
|
|
73
|
+
| **`spiki.raw()`** | Unwraps Spiki Proxy to a plain JavaScript object. | `const plainObj = spiki.raw(this.user);` |
|
|
147
74
|
|
|
148
75
|
---
|
|
149
76
|
|
|
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)` |
|
|
77
|
+
## Detailed Feature Documentation
|
|
174
78
|
|
|
175
|
-
|
|
79
|
+
### 1. Conditional Rendering (`s-if`) & Visibility (`:hidden`)
|
|
176
80
|
|
|
177
|
-
|
|
81
|
+
SpikiJS provides two ways to control element display:
|
|
178
82
|
|
|
179
|
-
|
|
83
|
+
- **`s-if`**: Completely mounts or unmounts the element from the DOM tree. Supports negation (`!`).
|
|
84
|
+
- **`:hidden`**: Replaces traditional `s-show` by toggling the native `hidden` DOM attribute. Supports negation (`!`).
|
|
180
85
|
|
|
181
|
-
|
|
86
|
+
> **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
87
|
|
|
183
88
|
```html
|
|
184
|
-
<div s-data="
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
89
|
+
<div s-data="visibilityDemo">
|
|
90
|
+
<button s-click="toggle">Toggle Visibility</button>
|
|
91
|
+
|
|
92
|
+
<!-- s-if (Mount/Unmount) -->
|
|
93
|
+
<div s-if="isOnline">Status: Online</div>
|
|
94
|
+
<div s-if="!isOnline">Status: Disconnected (Negation Example)</div>
|
|
95
|
+
|
|
96
|
+
<!-- :hidden (Visibility Toggle with static hidden attribute to prevent flicker) -->
|
|
97
|
+
<div hidden :hidden="!isVisible">Visible Content</div>
|
|
188
98
|
</div>
|
|
189
99
|
|
|
190
100
|
<script>
|
|
191
|
-
spiki.data('
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return this.msg.toUpperCase();
|
|
101
|
+
spiki.data('visibilityDemo', () => ({
|
|
102
|
+
isOnline: true,
|
|
103
|
+
isVisible: true,
|
|
104
|
+
toggle() {
|
|
105
|
+
this.isOnline = !this.isOnline;
|
|
106
|
+
this.isVisible = !this.isVisible;
|
|
198
107
|
}
|
|
199
108
|
}));
|
|
200
109
|
</script>
|
|
201
|
-
|
|
202
110
|
```
|
|
203
111
|
|
|
204
|
-
|
|
112
|
+
---
|
|
205
113
|
|
|
206
|
-
### 2.
|
|
114
|
+
### 2. SVG & HTML Dynamic Class Binding (`:class`)
|
|
207
115
|
|
|
208
|
-
|
|
116
|
+
Dynamic class binding works seamlessly on both HTML and SVG elements (preventing `SVGAnimatedString` class errors):
|
|
209
117
|
|
|
210
118
|
```html
|
|
211
|
-
<div s-data="
|
|
212
|
-
|
|
213
|
-
</div>
|
|
119
|
+
<div s-data="themeApp">
|
|
120
|
+
<!-- HTML Element -->
|
|
121
|
+
<div :class="statusClass">Component Active</div>
|
|
214
122
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
</script>
|
|
220
|
-
|
|
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.
|
|
123
|
+
<!-- SVG Element -->
|
|
124
|
+
<svg :class="iconClass" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
125
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
126
|
+
</svg>
|
|
228
127
|
|
|
229
|
-
|
|
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>
|
|
128
|
+
<button s-click="toggleStatus">Toggle Theme</button>
|
|
235
129
|
</div>
|
|
236
130
|
|
|
237
131
|
<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;
|
|
132
|
+
spiki.data('themeApp', () => ({
|
|
133
|
+
isActive: false,
|
|
134
|
+
toggleStatus() {
|
|
135
|
+
this.isActive = !this.isActive;
|
|
136
|
+
},
|
|
137
|
+
get statusClass() {
|
|
138
|
+
return this.isActive ? 'bg-indigo-600 text-white' : 'bg-slate-200 text-slate-700';
|
|
139
|
+
},
|
|
140
|
+
get iconClass() {
|
|
141
|
+
return this.isActive ? 'stroke-indigo-500 text-indigo-500' : 'stroke-slate-400 text-slate-400';
|
|
252
142
|
}
|
|
253
143
|
}));
|
|
254
144
|
</script>
|
|
255
|
-
|
|
256
145
|
```
|
|
257
146
|
|
|
258
|
-
|
|
147
|
+
---
|
|
259
148
|
|
|
260
|
-
###
|
|
149
|
+
### 3. List Rendering & Scope Auto-Injection (`s-for`)
|
|
261
150
|
|
|
262
|
-
|
|
151
|
+
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
152
|
|
|
264
153
|
```html
|
|
265
|
-
<div s-data="
|
|
266
|
-
<
|
|
154
|
+
<div s-data="todoApp">
|
|
155
|
+
<input s-model="newItem" s-keyup="checkAdd" placeholder="Add fruit (e.g. Mango)...">
|
|
156
|
+
<button s-click="addItem">Add</button>
|
|
157
|
+
|
|
267
158
|
<ul>
|
|
268
|
-
<template s-for="
|
|
269
|
-
<li
|
|
159
|
+
<template s-for="(fruit, idx) in fruits" s-key="id">
|
|
160
|
+
<li>
|
|
161
|
+
#<span s-text="idx"></span>: <span s-text="fruit.name"></span>
|
|
162
|
+
<!-- No parameters needed! this.fruit and this.idx are auto-injected -->
|
|
163
|
+
<button s-click="removeItem">Delete</button>
|
|
164
|
+
</li>
|
|
270
165
|
</template>
|
|
271
166
|
</ul>
|
|
272
167
|
</div>
|
|
273
168
|
|
|
274
169
|
<script>
|
|
275
|
-
spiki.data('
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
170
|
+
spiki.data('todoApp', () => ({
|
|
171
|
+
newItem: 'Mango',
|
|
172
|
+
fruits: [
|
|
173
|
+
{ id: 1, name: 'Apple' },
|
|
174
|
+
{ id: 2, name: 'Banana' }
|
|
175
|
+
],
|
|
176
|
+
checkAdd(e) {
|
|
177
|
+
if (e.key === 'Enter') this.addItem();
|
|
178
|
+
},
|
|
179
|
+
addItem() {
|
|
180
|
+
if (!this.newItem.trim()) return;
|
|
181
|
+
this.fruits.push({ id: Date.now(), name: this.newItem.trim() });
|
|
182
|
+
this.newItem = '';
|
|
183
|
+
},
|
|
184
|
+
removeItem() {
|
|
185
|
+
// Automatically reads this.idx auto-injected by Spiki loop scope
|
|
186
|
+
if (typeof this.idx === 'number') {
|
|
187
|
+
this.fruits.splice(this.idx, 1);
|
|
188
|
+
}
|
|
290
189
|
}
|
|
291
190
|
}));
|
|
292
191
|
</script>
|
|
293
|
-
|
|
294
192
|
```
|
|
295
193
|
|
|
296
|
-
|
|
194
|
+
---
|
|
297
195
|
|
|
298
|
-
###
|
|
196
|
+
### 4. Instance API: `this.$refs` & `this.$root`
|
|
299
197
|
|
|
300
|
-
|
|
198
|
+
Access raw DOM nodes without query selectors using `this.$refs` and `this.$root`:
|
|
301
199
|
|
|
302
200
|
```html
|
|
303
|
-
<div s-data="
|
|
304
|
-
<
|
|
201
|
+
<div s-data="refDemo">
|
|
202
|
+
<input s-ref="textInput" type="text" value="Spiki Ref Input">
|
|
203
|
+
<button s-click="focusInput">Focus</button>
|
|
204
|
+
<button s-click="highlightContainer">Highlight Container</button>
|
|
305
205
|
</div>
|
|
306
206
|
|
|
307
207
|
<script>
|
|
308
|
-
spiki.data('
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
208
|
+
spiki.data('refDemo', () => ({
|
|
209
|
+
focusInput() {
|
|
210
|
+
// Access raw HTMLInputElement directly
|
|
211
|
+
this.$refs.textInput.focus();
|
|
212
|
+
this.$refs.textInput.select();
|
|
213
|
+
},
|
|
214
|
+
highlightContainer() {
|
|
215
|
+
// Manipulate component container directly
|
|
216
|
+
this.$root.classList.toggle('ring-2');
|
|
314
217
|
}
|
|
315
218
|
}));
|
|
316
219
|
</script>
|
|
317
|
-
|
|
318
220
|
```
|
|
319
221
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
### 6. Side Effects (`s-effect`)
|
|
222
|
+
---
|
|
323
223
|
|
|
324
|
-
|
|
224
|
+
### 5. Global Store (`spiki.store()`) with Auto `init()` Execution
|
|
325
225
|
|
|
326
|
-
|
|
327
|
-
<div s-data="saveApp" s-effect="autoSave">
|
|
328
|
-
<textarea s-model="note"></textarea>
|
|
329
|
-
<span s-text="status"></span>
|
|
330
|
-
</div>
|
|
226
|
+
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
227
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
this.
|
|
228
|
+
```javascript
|
|
229
|
+
// 1. Register store with automatic init hook
|
|
230
|
+
spiki.store('settings', {
|
|
231
|
+
theme: 'dark',
|
|
232
|
+
initialized: false,
|
|
233
|
+
init() {
|
|
234
|
+
// Runs automatically when store is registered!
|
|
235
|
+
this.theme = localStorage.getItem('theme') || 'dark';
|
|
236
|
+
this.initialized = true;
|
|
340
237
|
}
|
|
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>
|
|
238
|
+
});
|
|
359
239
|
|
|
360
|
-
|
|
361
|
-
spiki.data('
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
240
|
+
// 2. Consume store inside any component
|
|
241
|
+
spiki.data('headerApp', () => ({
|
|
242
|
+
get currentTheme() {
|
|
243
|
+
return this.$store.settings.theme; // Reactive store subscription!
|
|
244
|
+
},
|
|
245
|
+
toggleTheme() {
|
|
246
|
+
this.$store.settings.theme = this.$store.settings.theme === 'dark' ? 'light' : 'dark';
|
|
366
247
|
}
|
|
367
248
|
}));
|
|
368
|
-
</script>
|
|
369
|
-
|
|
370
249
|
```
|
|
371
250
|
|
|
372
251
|
---
|
|
373
252
|
|
|
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.
|
|
253
|
+
### 6. Unwrapping Reactive Proxies (`spiki.raw()`)
|
|
392
254
|
|
|
393
|
-
|
|
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>
|
|
255
|
+
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:
|
|
404
256
|
|
|
405
|
-
|
|
406
|
-
spiki.data('
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### 3. Accessing Index
|
|
421
|
-
|
|
422
|
-
You can get the current index by using parentheses: `(item, index) in array`.
|
|
423
|
-
|
|
424
|
-
```html
|
|
425
|
-
<template s-for="(item, i) in list" s-key="id">
|
|
426
|
-
<button s-click="remove">
|
|
427
|
-
Remove Index <span s-text="i"></span>
|
|
428
|
-
</button>
|
|
429
|
-
</template>
|
|
430
|
-
|
|
431
|
-
<script>
|
|
432
|
-
spiki.data('listApp', () => ({
|
|
433
|
-
list: ['A', 'B', 'C'],
|
|
434
|
-
remove() {
|
|
435
|
-
// 'this.i' is automatically available
|
|
436
|
-
this.list.splice(this.i, 1);
|
|
257
|
+
```javascript
|
|
258
|
+
spiki.data('userProfile', () => ({
|
|
259
|
+
user: { name: 'Alice', role: 'Admin' },
|
|
260
|
+
|
|
261
|
+
saveUser() {
|
|
262
|
+
// Extract raw, un-proxied JS object
|
|
263
|
+
const plainData = spiki.raw(this.user);
|
|
264
|
+
|
|
265
|
+
fetch('/api/user', {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
headers: { 'Content-Type': 'application/json' },
|
|
268
|
+
body: JSON.stringify(plainData)
|
|
269
|
+
});
|
|
437
270
|
}
|
|
438
271
|
}));
|
|
439
|
-
</script>
|
|
440
|
-
|
|
441
272
|
```
|
|
442
273
|
|
|
443
274
|
---
|
|
444
275
|
|
|
445
|
-
|
|
276
|
+
### 7. Reactive Side Effects (`s-effect`)
|
|
446
277
|
|
|
447
|
-
|
|
278
|
+
Use `s-effect="handler"` to run automatic reactive watchers.
|
|
448
279
|
|
|
449
|
-
|
|
280
|
+
> **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
281
|
|
|
451
282
|
```html
|
|
452
|
-
<div s-data="
|
|
453
|
-
<input
|
|
454
|
-
<button s-click="focusMe">Focus Input</button>
|
|
283
|
+
<div s-data="noteApp" s-effect="autoSave">
|
|
284
|
+
<input type="text" s-model="note">
|
|
455
285
|
</div>
|
|
456
286
|
|
|
457
287
|
<script>
|
|
458
|
-
spiki.data('
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
288
|
+
spiki.data('noteApp', () => ({
|
|
289
|
+
note: 'Hello Spiki',
|
|
290
|
+
autoSave() {
|
|
291
|
+
// Pure side effect: read state and write to localStorage
|
|
292
|
+
try {
|
|
293
|
+
localStorage.setItem('user_note', this.note);
|
|
294
|
+
} catch(e) {}
|
|
462
295
|
}
|
|
463
296
|
}));
|
|
464
297
|
</script>
|
|
465
|
-
|
|
466
298
|
```
|
|
467
299
|
|
|
468
300
|
---
|
|
469
301
|
|
|
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
302
|
## License
|
|
525
303
|
|
|
526
304
|
MIT License.
|
package/package.json
CHANGED
package/spiki.esm.js
CHANGED
|
@@ -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('');
|
|
@@ -483,7 +483,7 @@ var spiki = (() => {
|
|
|
483
483
|
el.addEventListener('compositionend', () => { el._c = false; handle({target: el, type: 'input'}); });
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
(el._h[evt] = el._h[evt] || []).
|
|
486
|
+
(el._h[evt] = el._h[evt] || []).push({ model: true, p: p });
|
|
487
487
|
addListen(evt);
|
|
488
488
|
} else if (type === 'ref') {
|
|
489
489
|
state.$refs[attrValue] = el;
|
|
@@ -491,7 +491,7 @@ var spiki = (() => {
|
|
|
491
491
|
bindings.push({ type: type, path: p });
|
|
492
492
|
} else {
|
|
493
493
|
el._s = scope; el._h = el._h || {};
|
|
494
|
-
|
|
494
|
+
(el._h[type] = el._h[type] || []).unshift({ p: p });
|
|
495
495
|
addListen(type);
|
|
496
496
|
}
|
|
497
497
|
}
|
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,v="length",f="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="
|
|
1
|
+
var spiki=(()=>{var r,e=Object,t=Array,a=Reflect,n=Promise,o=Node,i=document,l=console,v="length",f="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",vr="s-data",fr="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[v];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[v];o<i-1&&n;)n=n[e[o++]];a=n?n[e[i-1]]:void 0}if(void 0===a){var f=t[k](e)?e.join("."):e;l.warn("[spikijs] Property undefined: "+f)}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,v)}}});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[v];a<n;a++){var o=t[a];o[nr]?o[nr](o):o()}},Pr=r=>{for(var e=r[rr][v];e--;){var t=r[rr][e],a=t[p](r);-1!==a&&(t[a]=t[t[v]-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 v="_"+n;return v in r||kr(()=>i[v]=l[h][d](i)),i[v]}br(r,n);var f=a[h](r,n,i);return!f||typeof f!==Q||f instanceof o?f:Ar(f)},set:(r,n,o,i)=>{var l=r[n],f=t[k](r),s=f?Number(n)<r[v]: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||xr(r,n),p}u=e[b](u)}return p=a.set(r,n,o,i),!_r&&p&&(s&&o===l||(xr(r,n),f?xr(r,v):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,v):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[f]=(r,e)=>{r[T]===W?r[O]=!!e:r[T]===G?r[O]=r[f]==e:r[f]!=e&&(r[f]=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](vr);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[v];s--;){var u=i[s],p=u.p;if(u[J]){var c=t[T]===W?t[O]:t[f];if(typeof c===or){var _=c[X]();_&&isFinite(_)&&(48===_[ar](0)&&1!==_[v]&&46!==_[ar](1)||(c=Number(_)))}if(typeof p===or)l&&(l[p]=c);else{for(var y=l,m=0,g=p[v]-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](vr)){var l=wr(r,n);l&&o[s](l[ir])}else{var g=r[A](fr),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[v];r--;)or[r]()}),o[s](kr(()=>{var e=mr(n,pr).val;if(sr&&(e=!e),e)nr||((nr=r[F](!0))[w](fr),q(nr,n,or),rr[j][D](nr,rr));else if(nr){for(var t=or[v];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,v);var o=i.createDocumentFragment(),l=rr;_r=e[P](null);for(var f=t[k](a),s=f?a:e[y](a),p=f?a[v]:s[v],c=0;c<p;c++){var h,g=f?c:s[c],b=f?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[v];$++)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][v];$++)o.appendChild(A[Z][$]);l[j][D](o,l[C])}l=A[Z][A[Z][v]-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[v];jr--;){var Cr=Or[jr],Lr=Cr[$],qr=Cr[f];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:f,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[v]&&o[s](kr(()=>{for(var e=O[v];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[v];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[v];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
|
@@ -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('');
|
|
@@ -486,7 +486,7 @@ var spiki = (() => {
|
|
|
486
486
|
el.addEventListener('compositionend', () => { el._c = false; handle({target: el, type: 'input'}); });
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
(el._h[evt] = el._h[evt] || []).
|
|
489
|
+
(el._h[evt] = el._h[evt] || []).push({ model: true, p: p });
|
|
490
490
|
addListen(evt);
|
|
491
491
|
} else if (type === 'ref') {
|
|
492
492
|
state.$refs[attrValue] = el;
|
|
@@ -494,7 +494,7 @@ var spiki = (() => {
|
|
|
494
494
|
bindings.push({ type: type, path: p });
|
|
495
495
|
} else {
|
|
496
496
|
el._s = scope; el._h = el._h || {};
|
|
497
|
-
(el._h[type] = el._h[type] || []).
|
|
497
|
+
(el._h[type] = el._h[type] || []).unshift({ p: p });
|
|
498
498
|
addListen(type);
|
|
499
499
|
}
|
|
500
500
|
}
|
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,v="length",f="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="
|
|
1
|
+
(()=>{"use strict";var spiki=(()=>{var r,e=Object,t=Array,a=Reflect,n=Promise,o=Node,i=document,l=console,v="length",f="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",vr="s-data",fr="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[v];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[v];o<i-1&&n;)n=n[e[o++]];a=n?n[e[i-1]]:void 0}if(void 0===a){var f=t[k](e)?e.join("."):e;l.warn("[spikijs] Property undefined: "+f)}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,v)}}});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[v];a<n;a++){var o=t[a];o[nr]?o[nr](o):o()}},Pr=r=>{for(var e=r[rr][v];e--;){var t=r[rr][e],a=t[p](r);-1!==a&&(t[a]=t[t[v]-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 v="_"+n;return v in r||kr(()=>i[v]=l[h][d](i)),i[v]}br(r,n);var f=a[h](r,n,i);return!f||typeof f!==Q||f instanceof o?f:Ar(f)},set:(r,n,o,i)=>{var l=r[n],f=t[k](r),s=f?Number(n)<r[v]: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||xr(r,n),p}u=e[b](u)}return p=a.set(r,n,o,i),!_r&&p&&(s&&o===l||(xr(r,n),f?xr(r,v):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,v):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[f]=(r,e)=>{r[T]===W?r[O]=!!e:r[T]===G?r[O]=r[f]==e:r[f]!=e&&(r[f]=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](vr);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[v];s--;){var u=i[s],p=u.p;if(u[J]){var c=t[T]===W?t[O]:t[f];if(typeof c===or){var _=c[X]();_&&isFinite(_)&&(48===_[ar](0)&&1!==_[v]&&46!==_[ar](1)||(c=Number(_)))}if(typeof p===or)l&&(l[p]=c);else{for(var y=l,m=0,g=p[v]-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](vr)){var l=wr(r,n);l&&o[s](l[ir])}else{var g=r[A](fr),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[v];r--;)or[r]()}),o[s](kr(()=>{var e=mr(n,pr).val;if(sr&&(e=!e),e)nr||((nr=r[F](!0))[w](fr),q(nr,n,or),rr[j][D](nr,rr));else if(nr){for(var t=or[v];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,v);var o=i.createDocumentFragment(),l=rr;_r=e[P](null);for(var f=t[k](a),s=f?a:e[y](a),p=f?a[v]:s[v],c=0;c<p;c++){var h,g=f?c:s[c],b=f?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[v];$++)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][v];$++)o.appendChild(A[Z][$]);l[j][D](o,l[C])}l=A[Z][A[Z][v]-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[v];jr--;){var Cr=Or[jr],Lr=Cr[$],qr=Cr[f];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:f,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[v]&&o[s](kr(()=>{for(var e=O[v];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[v];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[v];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})();
|