@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 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.