@mintjamsinc/ichigojs 0.1.67 → 0.1.69
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 +293 -5
- package/dist/ichigo.cjs +213 -31
- package/dist/ichigo.cjs.map +1 -1
- package/dist/ichigo.esm.js +213 -31
- package/dist/ichigo.esm.js.map +1 -1
- package/dist/ichigo.esm.min.js +1 -1
- package/dist/ichigo.min.cjs +1 -1
- package/dist/ichigo.umd.js +213 -31
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/types/ichigo/VBindings.d.ts +7 -0
- package/dist/types/ichigo/util/ReactiveProxy.d.ts +47 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,14 +9,17 @@ A simple and intuitive reactive framework. Lightweight, fast, and user-friendly
|
|
|
9
9
|
|
|
10
10
|
- ✨ **Vue-like API** - Familiar syntax for Vue developers
|
|
11
11
|
- ⚡ **Reactive Proxy System** - Automatic change detection without manual triggers
|
|
12
|
-
- 🎯 **Computed Properties** - Automatic dependency tracking and re-evaluation
|
|
12
|
+
- 🎯 **Computed Properties** - Automatic dependency tracking and re-evaluation, including writable computed (`{ get, set }`)
|
|
13
|
+
- 👀 **Watchers** - React to data changes with the `watch` option (`deep`, `immediate`)
|
|
13
14
|
- 🔄 **Two-way Binding** - `v-model` with modifiers (`.lazy`, `.number`, `.trim`)
|
|
14
15
|
- 🔌 **Lifecycle Hooks** - `@mount`, `@mounted`, `@update`, `@updated`, `@unmount`, `@unmounted` with context (`$ctx`)
|
|
15
16
|
- 💾 **userData Storage** - Proxy-free storage for third-party library instances with auto-cleanup
|
|
17
|
+
- 🧩 **Components** - Reusable Web Components via `defineComponent` with `props`, `slot`, and `$emit`
|
|
16
18
|
- 📦 **Lightweight** - Minimal bundle size
|
|
17
19
|
- 🚀 **High Performance** - Efficient batched updates via microtask queue
|
|
18
20
|
- 💪 **TypeScript** - Written in TypeScript with full type support
|
|
19
|
-
- 🎨 **Directives** - `v-if`, `v-for`, `v-show`, `v-bind`, `v-on`, `v-model`, `v-resize`, `v-intersection`, `v-performance`
|
|
21
|
+
- 🎨 **Directives** - `v-if`, `v-else-if`, `v-else`, `v-for`, `v-show`, `v-bind`, `v-on`, `v-model`, `v-text`, `v-html`, `v-focus`, `v-resize`, `v-intersection`, `v-performance`
|
|
22
|
+
- 🎯 **Focus Management** - Declarative focus control with the `v-focus` directive (`.select`, `.cursor-end`)
|
|
20
23
|
- 📐 **Resize Observer** - Monitor element size changes with `v-resize` directive
|
|
21
24
|
- 👁️ **Intersection Observer** - Detect element visibility with `v-intersection` directive
|
|
22
25
|
- ⚡ **Performance Observer** - Monitor performance metrics with `v-performance` directive
|
|
@@ -195,6 +198,84 @@ VDOM.createApp({
|
|
|
195
198
|
}).mount('#app');
|
|
196
199
|
```
|
|
197
200
|
|
|
201
|
+
**Writable Computed Properties:**
|
|
202
|
+
|
|
203
|
+
A computed property can also be defined as an object with both a `get` and a
|
|
204
|
+
`set` function. This makes it writable, so it can be used as a `v-model` target
|
|
205
|
+
or assigned to directly. Reads go through `get`, while assignments are routed
|
|
206
|
+
through `set`.
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
VDOM.createApp({
|
|
210
|
+
data() {
|
|
211
|
+
return {
|
|
212
|
+
firstName: 'John',
|
|
213
|
+
lastName: 'Doe'
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
computed: {
|
|
217
|
+
fullName: {
|
|
218
|
+
get() {
|
|
219
|
+
return `${this.firstName} ${this.lastName}`;
|
|
220
|
+
},
|
|
221
|
+
set(value) {
|
|
222
|
+
const [first, last] = value.split(' ');
|
|
223
|
+
this.firstName = first;
|
|
224
|
+
this.lastName = last;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}).mount('#app');
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```html
|
|
232
|
+
<!-- Assigning through v-model invokes the computed setter -->
|
|
233
|
+
<input v-model="fullName">
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Watchers
|
|
237
|
+
|
|
238
|
+
Use the `watch` option to run a callback whenever a watched property changes.
|
|
239
|
+
Keys are property paths (e.g. `"count"`, `"user.name"`), and the callback
|
|
240
|
+
receives the new and previous values.
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
VDOM.createApp({
|
|
244
|
+
data() {
|
|
245
|
+
return {
|
|
246
|
+
count: 0,
|
|
247
|
+
user: { name: 'Alice' }
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
watch: {
|
|
251
|
+
// Shorthand: a callback function
|
|
252
|
+
count(newValue, oldValue) {
|
|
253
|
+
console.log(`count changed from ${oldValue} to ${newValue}`);
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// Watch a nested property by path
|
|
257
|
+
'user.name'(newValue, oldValue) {
|
|
258
|
+
console.log(`name changed from ${oldValue} to ${newValue}`);
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
// Full form: an options object with deep / immediate
|
|
262
|
+
user: {
|
|
263
|
+
handler(newValue, oldValue) {
|
|
264
|
+
console.log('user object changed', newValue);
|
|
265
|
+
},
|
|
266
|
+
deep: true, // Observe nested changes inside the object
|
|
267
|
+
immediate: true // Invoke once immediately with the current value
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}).mount('#app');
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Watcher options:**
|
|
274
|
+
|
|
275
|
+
- `handler` - The callback invoked when the watched value changes
|
|
276
|
+
- `deep` - When `true`, deeply observes nested object/array changes (default: `false`)
|
|
277
|
+
- `immediate` - When `true`, invokes the handler once immediately on registration with the current value (default: `false`)
|
|
278
|
+
|
|
198
279
|
### Directives
|
|
199
280
|
|
|
200
281
|
#### v-if / v-else-if / v-else
|
|
@@ -314,6 +395,73 @@ methods: {
|
|
|
314
395
|
}
|
|
315
396
|
```
|
|
316
397
|
|
|
398
|
+
#### v-text
|
|
399
|
+
|
|
400
|
+
Set the text content of an element. The expression result replaces the
|
|
401
|
+
element's `textContent`. Unlike `v-html`, the content is rendered as plain
|
|
402
|
+
text, so HTML is escaped and XSS is not a concern.
|
|
403
|
+
|
|
404
|
+
```html
|
|
405
|
+
<span v-text="message"></span>
|
|
406
|
+
|
|
407
|
+
<!-- Equivalent to -->
|
|
408
|
+
<span>{{ message }}</span>
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Use `v-text` when you want to set the entire text content of an element from a
|
|
412
|
+
single expression (it overwrites any existing content), rather than
|
|
413
|
+
interpolating with `{{ }}`.
|
|
414
|
+
|
|
415
|
+
#### v-html
|
|
416
|
+
|
|
417
|
+
Set the raw HTML content of an element. The expression result is assigned to
|
|
418
|
+
the element's `innerHTML`.
|
|
419
|
+
|
|
420
|
+
```html
|
|
421
|
+
<div v-html="htmlContent"></div>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
> ⚠️ **Security warning:** Dynamically rendering arbitrary HTML can easily lead
|
|
425
|
+
> to XSS attacks. Only use `v-html` on **trusted** content, and **never** on
|
|
426
|
+
> user-provided content. For plain text, use `v-text` or `{{ }}` interpolation
|
|
427
|
+
> instead.
|
|
428
|
+
|
|
429
|
+
#### v-focus
|
|
430
|
+
|
|
431
|
+
Declaratively manage focus on an element. Focus is deferred via
|
|
432
|
+
`requestAnimationFrame`, so elements that become visible just before the
|
|
433
|
+
directive runs (for example inside a `v-if` or a `display: none` container)
|
|
434
|
+
still receive focus reliably.
|
|
435
|
+
|
|
436
|
+
```html
|
|
437
|
+
<!-- Focus once after mount -->
|
|
438
|
+
<input v-focus>
|
|
439
|
+
|
|
440
|
+
<!-- Focus + select all text after mount -->
|
|
441
|
+
<input v-focus.select>
|
|
442
|
+
|
|
443
|
+
<!-- Focus + place the caret at the end of the value -->
|
|
444
|
+
<input v-focus.cursor-end value="prefilled">
|
|
445
|
+
|
|
446
|
+
<!-- Conditional focus: fires when the expression goes from falsy to truthy -->
|
|
447
|
+
<input v-focus="isEditing">
|
|
448
|
+
|
|
449
|
+
<!-- Conditional focus + select all -->
|
|
450
|
+
<input v-focus.select="isEditing">
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Behavior:**
|
|
454
|
+
|
|
455
|
+
- **Without an expression**, the element is focused exactly once after mount.
|
|
456
|
+
- **With an expression**, focus fires only on the falsy → truthy edge, so the
|
|
457
|
+
user is not repeatedly re-focused on every reactive update. If the value is
|
|
458
|
+
already truthy on mount, the element is focused immediately.
|
|
459
|
+
|
|
460
|
+
**Modifiers:**
|
|
461
|
+
|
|
462
|
+
- `.select` - After focusing, selects all text in the input/textarea
|
|
463
|
+
- `.cursor-end` - After focusing, places the caret at the end of the value
|
|
464
|
+
|
|
317
465
|
#### v-resize
|
|
318
466
|
|
|
319
467
|
Monitor element size changes using ResizeObserver:
|
|
@@ -591,9 +739,20 @@ Two-way data binding:
|
|
|
591
739
|
<input v-model.number="age"> <!-- Convert to number -->
|
|
592
740
|
<input v-model.trim="username"> <!-- Trim whitespace -->
|
|
593
741
|
|
|
594
|
-
<!-- Checkbox -->
|
|
742
|
+
<!-- Checkbox (boolean) -->
|
|
595
743
|
<input type="checkbox" v-model="isChecked">
|
|
596
744
|
|
|
745
|
+
<!-- Checkbox with custom true/false values -->
|
|
746
|
+
<input type="checkbox" v-model="status" :true-value="'yes'" :false-value="'no'">
|
|
747
|
+
|
|
748
|
+
<!-- Checkbox group bound to an array -->
|
|
749
|
+
<input type="checkbox" value="a" v-model="selectedItems">
|
|
750
|
+
<input type="checkbox" value="b" v-model="selectedItems">
|
|
751
|
+
|
|
752
|
+
<!-- Radio -->
|
|
753
|
+
<input type="radio" value="a" v-model="picked">
|
|
754
|
+
<input type="radio" value="b" v-model="picked">
|
|
755
|
+
|
|
597
756
|
<!-- Select -->
|
|
598
757
|
<select v-model="selected">
|
|
599
758
|
<option value="a">Option A</option>
|
|
@@ -601,6 +760,13 @@ Two-way data binding:
|
|
|
601
760
|
</select>
|
|
602
761
|
```
|
|
603
762
|
|
|
763
|
+
**Supported elements:**
|
|
764
|
+
|
|
765
|
+
- **Text inputs / `<textarea>`** - Binds to the element's value
|
|
766
|
+
- **Checkbox** - Binds to a boolean, to a custom value pair via `:true-value` / `:false-value`, or to an array (when the bound value is an array, the checkbox's `value` is added/removed)
|
|
767
|
+
- **Radio** - Binds to the `value` (or `:value`) of the selected radio button
|
|
768
|
+
- **Select** - Binds to the selected option's value (re-applied automatically when options are generated dynamically via `v-for`)
|
|
769
|
+
|
|
604
770
|
### Methods
|
|
605
771
|
|
|
606
772
|
Methods have access to data and computed properties via `this`:
|
|
@@ -639,6 +805,108 @@ methods: {
|
|
|
639
805
|
}
|
|
640
806
|
```
|
|
641
807
|
|
|
808
|
+
## Components
|
|
809
|
+
|
|
810
|
+
ichigo.js components are real [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)
|
|
811
|
+
backed by the same reactivity system. Define a component with `defineComponent`,
|
|
812
|
+
pointing it at a `<template>` for its markup.
|
|
813
|
+
|
|
814
|
+
```html
|
|
815
|
+
<!-- Component markup -->
|
|
816
|
+
<template id="my-list">
|
|
817
|
+
<ul v-if="items.length > 0">
|
|
818
|
+
<li v-for="item of items" :key="item.id">{{ item.name }}</li>
|
|
819
|
+
</ul>
|
|
820
|
+
<!-- Fallback content projected from the parent -->
|
|
821
|
+
<slot></slot>
|
|
822
|
+
</template>
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
```javascript
|
|
826
|
+
import { defineComponent } from '@mintjamsinc/ichigojs';
|
|
827
|
+
|
|
828
|
+
defineComponent('my-list', {
|
|
829
|
+
template: '#my-list', // CSS selector for the <template>
|
|
830
|
+
props: ['items'], // Props received from the parent
|
|
831
|
+
data() {
|
|
832
|
+
// Props are accessible via `this` and can be defaulted/transformed here
|
|
833
|
+
return { items: this.items ?? [] };
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
```html
|
|
839
|
+
<!-- Usage -->
|
|
840
|
+
<my-list :items="searchResults">
|
|
841
|
+
<span slot="empty">No results.</span>
|
|
842
|
+
</my-list>
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
**Props:**
|
|
846
|
+
|
|
847
|
+
- Declared via the `props` array. Each declared prop becomes a property on the
|
|
848
|
+
custom element, so the parent can bind to it with `v-bind` / `:`
|
|
849
|
+
(e.g. `:items="searchResults"`).
|
|
850
|
+
- Props are reactive from the start and are included in the component's data
|
|
851
|
+
automatically. Values returned from `data()` take precedence, allowing you to
|
|
852
|
+
default or transform a prop (e.g. `this.items ?? []`).
|
|
853
|
+
|
|
854
|
+
**Slots:**
|
|
855
|
+
|
|
856
|
+
Use the native `<slot>` element in the component template to project content
|
|
857
|
+
from the parent. ichigo.js components use Light DOM.
|
|
858
|
+
|
|
859
|
+
### Events (`$emit`)
|
|
860
|
+
|
|
861
|
+
Components (and applications) can dispatch custom events with `$emit`, which is
|
|
862
|
+
available in both templates and methods. By default the event bubbles from the
|
|
863
|
+
component's root element, so a parent can listen for it with `v-on` / `@` on the
|
|
864
|
+
component tag.
|
|
865
|
+
|
|
866
|
+
```javascript
|
|
867
|
+
defineComponent('my-button', {
|
|
868
|
+
template: '#my-button',
|
|
869
|
+
// Optional: declare the events this component emits.
|
|
870
|
+
// Emitting an undeclared event logs a development warning (validation only;
|
|
871
|
+
// it never blocks dispatch). Omit `emits` to allow any event name.
|
|
872
|
+
emits: ['selected'],
|
|
873
|
+
methods: {
|
|
874
|
+
onClick() {
|
|
875
|
+
// $emit(name, detail?, options?)
|
|
876
|
+
this.$emit('selected', { id: 42 });
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
```html
|
|
883
|
+
<!-- Parent listens for the custom event; payload is in event.detail -->
|
|
884
|
+
<my-button @selected="onSelected"></my-button>
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
**`$emit(name, detail?, options?)`:**
|
|
888
|
+
|
|
889
|
+
- `name` - The event name (listened to as `@name` on the parent)
|
|
890
|
+
- `detail` - The payload exposed as `event.detail`
|
|
891
|
+
- `options` - Dispatch options (`VEmitOptions`):
|
|
892
|
+
- `bubbles` - Whether the event bubbles (default: `true`)
|
|
893
|
+
- `cancelable` - Whether `preventDefault()` has an effect (default: `true`); `$emit` returns `false` when a listener calls `preventDefault()`
|
|
894
|
+
- `composed` - Whether the event crosses shadow DOM boundaries (default: `false`)
|
|
895
|
+
- `target` - The dispatch target (default: the application root element). Set to `document` / `window` for a global event bus.
|
|
896
|
+
|
|
897
|
+
### Legacy component directive (`v-component`)
|
|
898
|
+
|
|
899
|
+
> ⚠️ **Deprecated.** The `v-component` directive and the `VComponentRegistry`
|
|
900
|
+
> are deprecated and will be removed in a future release. Use
|
|
901
|
+
> [`defineComponent`](#components) (Custom Elements) for new code.
|
|
902
|
+
|
|
903
|
+
For reference, the legacy mechanism renders a component registered in the
|
|
904
|
+
application's `VComponentRegistry` by id, passing props through `:options`:
|
|
905
|
+
|
|
906
|
+
```html
|
|
907
|
+
<div v-component="my-component" :options="{ message: 'Hello' }"></div>
|
|
908
|
+
```
|
|
909
|
+
|
|
642
910
|
## Performance
|
|
643
911
|
|
|
644
912
|
ichigo.js uses several optimization techniques:
|
|
@@ -696,13 +964,33 @@ Creates a new application instance.
|
|
|
696
964
|
|
|
697
965
|
**Options:**
|
|
698
966
|
|
|
699
|
-
- `data()`: Function that returns the initial data object
|
|
700
|
-
- `computed`: Object containing computed property definitions
|
|
967
|
+
- `data()`: Function that returns the initial data object. Called with a `$ctx` (`{ $markRaw }`) as `this`.
|
|
968
|
+
- `computed`: Object containing computed property definitions. Each value is either a getter function (read-only) or a `{ get, set }` object (writable).
|
|
701
969
|
- `methods`: Object containing method definitions
|
|
970
|
+
- `watch`: Object mapping property paths to watcher definitions (a callback, or `{ handler, deep, immediate }`)
|
|
971
|
+
- `emits`: Optional array of event names the app/component is expected to emit via `$emit`. Emitting an undeclared event logs a development warning (validation only).
|
|
702
972
|
- `logLevel`: Logging level (`'debug'` | `'info'` | `'warn'` | `'error'`)
|
|
703
973
|
|
|
704
974
|
**Returns:** Application instance with `mount(selector)` method
|
|
705
975
|
|
|
976
|
+
**Instance helpers** (available in `data()`, methods, expressions, and lifecycle hooks as appropriate):
|
|
977
|
+
|
|
978
|
+
- `$markRaw(obj)`: Marks an object as non-reactive (see [Marking Objects as Non-Reactive](#marking-objects-as-non-reactive))
|
|
979
|
+
- `$nextTick(callback)`: Runs a callback after the next DOM update
|
|
980
|
+
- `$emit(name, detail?, options?)`: Dispatches a custom event (see [Events](#events-emit))
|
|
981
|
+
- `$ctx`: Lifecycle/handler context with `element`, `vnode`, and `userData`
|
|
982
|
+
|
|
983
|
+
### defineComponent(tagName, options)
|
|
984
|
+
|
|
985
|
+
Defines and registers a custom element backed by ichigo.js reactivity. See [Components](#components).
|
|
986
|
+
|
|
987
|
+
**Options** (extends the `createApp` options above):
|
|
988
|
+
|
|
989
|
+
- `template`: CSS selector for the `<template>` element that defines the component's markup (required)
|
|
990
|
+
- `props`: Array of property names received from the parent via attribute/property binding
|
|
991
|
+
|
|
992
|
+
**Returns:** `void` (the custom element is registered via `customElements.define`)
|
|
993
|
+
|
|
706
994
|
## Contributing
|
|
707
995
|
|
|
708
996
|
Contributions are welcome! Please feel free to submit a Pull Request.
|