@mintjamsinc/ichigojs 0.1.57 → 0.1.59
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 +31 -0
- package/dist/ichigo.cjs +162 -70
- package/dist/ichigo.cjs.map +1 -1
- package/dist/ichigo.esm.js +162 -70
- 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 +162 -70
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/types/ichigo/VNode.d.ts +8 -0
- package/dist/types/ichigo/util/VFragmentRange.d.ts +52 -0
- package/package.json +1 -1
package/dist/ichigo.esm.js
CHANGED
|
@@ -8647,6 +8647,14 @@ class VDirectiveManager {
|
|
|
8647
8647
|
// Other directives (@click, :class, etc.) will be processed on the cloned/rendered elements.
|
|
8648
8648
|
const attributes = [];
|
|
8649
8649
|
if (element.hasAttribute(StandardDirectiveName.V_FOR)) {
|
|
8650
|
+
// <template v-for v-if> is not supported: v-for clones .content (a
|
|
8651
|
+
// DocumentFragment), so the v-if attribute on the <template> itself
|
|
8652
|
+
// is lost. Warn the developer so silent failure is avoided.
|
|
8653
|
+
if (element instanceof HTMLTemplateElement && element.hasAttribute(StandardDirectiveName.V_IF)) {
|
|
8654
|
+
console.warn('[ichigo.js] <template> cannot combine v-for and v-if. ' +
|
|
8655
|
+
'The v-if will be ignored. Move v-if to an inner element, ' +
|
|
8656
|
+
'or replace <template> with a regular element.', element);
|
|
8657
|
+
}
|
|
8650
8658
|
// For v-for template element: only process v-for and :key
|
|
8651
8659
|
// Other attributes will be processed when child VNodes are created for cloned elements
|
|
8652
8660
|
attributes.push(element.getAttributeNode(StandardDirectiveName.V_FOR));
|
|
@@ -8955,6 +8963,13 @@ class VNode {
|
|
|
8955
8963
|
* VNode is destroyed.
|
|
8956
8964
|
*/
|
|
8957
8965
|
#userData;
|
|
8966
|
+
/**
|
|
8967
|
+
* When this VNode wraps a <template>-derived DocumentFragment that has been
|
|
8968
|
+
* expanded into the DOM, this range tracks the start/end boundary of the
|
|
8969
|
+
* expanded content. Directives such as v-for and v-if use this to move or
|
|
8970
|
+
* remove the entire fragment atomically.
|
|
8971
|
+
*/
|
|
8972
|
+
#fragmentRange;
|
|
8958
8973
|
/**
|
|
8959
8974
|
* Creates an instance of the virtual node.
|
|
8960
8975
|
* @param args The initialization arguments for the virtual node.
|
|
@@ -9190,6 +9205,17 @@ class VNode {
|
|
|
9190
9205
|
}
|
|
9191
9206
|
return this.#userData;
|
|
9192
9207
|
}
|
|
9208
|
+
/**
|
|
9209
|
+
* The DOM range that bounds this VNode's expanded fragment content, if any.
|
|
9210
|
+
* Set by directives that expand a <template> into a DocumentFragment
|
|
9211
|
+
* (currently v-for and v-if).
|
|
9212
|
+
*/
|
|
9213
|
+
get fragmentRange() {
|
|
9214
|
+
return this.#fragmentRange;
|
|
9215
|
+
}
|
|
9216
|
+
set fragmentRange(range) {
|
|
9217
|
+
this.#fragmentRange = range;
|
|
9218
|
+
}
|
|
9193
9219
|
/**
|
|
9194
9220
|
* The DOM path of this virtual node.
|
|
9195
9221
|
* This is a string representation of the path from the root to this node,
|
|
@@ -9535,6 +9561,102 @@ class VConditionalDirectiveContext {
|
|
|
9535
9561
|
}
|
|
9536
9562
|
}
|
|
9537
9563
|
|
|
9564
|
+
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
|
9565
|
+
/**
|
|
9566
|
+
* Represents a contiguous range of DOM nodes that conceptually belong to a single VNode
|
|
9567
|
+
* whose source is a <template> element (and therefore expands to multiple sibling nodes
|
|
9568
|
+
* when inserted into the DOM).
|
|
9569
|
+
*
|
|
9570
|
+
* The range is bounded by two comment markers (start and end). All nodes between the
|
|
9571
|
+
* markers (inclusive) form the range. Move and remove operations always act on the
|
|
9572
|
+
* entire range atomically via the DOM Range API, which prevents the marker pair from
|
|
9573
|
+
* becoming desynchronized from the content it bounds.
|
|
9574
|
+
*
|
|
9575
|
+
* This abstraction replaces ad-hoc per-marker bookkeeping previously stored in
|
|
9576
|
+
* {@link VNode.userData} by directives such as v-for and v-if.
|
|
9577
|
+
*/
|
|
9578
|
+
class VFragmentRange {
|
|
9579
|
+
#start;
|
|
9580
|
+
#end;
|
|
9581
|
+
constructor(start, end) {
|
|
9582
|
+
this.#start = start;
|
|
9583
|
+
this.#end = end;
|
|
9584
|
+
}
|
|
9585
|
+
/**
|
|
9586
|
+
* Inserts content into a parent before the given reference sibling (or as last
|
|
9587
|
+
* child when {@code refSibling} is null), wrapping it with start/end comment
|
|
9588
|
+
* markers. Returns a {@link VFragmentRange} that tracks the inserted region.
|
|
9589
|
+
*
|
|
9590
|
+
* @param parent The parent node to insert into.
|
|
9591
|
+
* @param refSibling The sibling to insert before (null = append).
|
|
9592
|
+
* @param label A short label used for the marker comment text (helps debugging).
|
|
9593
|
+
* @param content The content to insert. Typically a DocumentFragment cloned
|
|
9594
|
+
* from a template's content, but any Node works.
|
|
9595
|
+
*/
|
|
9596
|
+
static insert(parent, refSibling, label, content) {
|
|
9597
|
+
const start = document.createComment(`#${label}-start`);
|
|
9598
|
+
const end = document.createComment(`#${label}-end`);
|
|
9599
|
+
parent.insertBefore(start, refSibling);
|
|
9600
|
+
parent.insertBefore(end, refSibling);
|
|
9601
|
+
parent.insertBefore(content, end);
|
|
9602
|
+
return new VFragmentRange(start, end);
|
|
9603
|
+
}
|
|
9604
|
+
/**
|
|
9605
|
+
* The start marker (inclusive boundary).
|
|
9606
|
+
*/
|
|
9607
|
+
get firstNode() {
|
|
9608
|
+
return this.#start;
|
|
9609
|
+
}
|
|
9610
|
+
/**
|
|
9611
|
+
* The end marker (inclusive boundary).
|
|
9612
|
+
*/
|
|
9613
|
+
get lastNode() {
|
|
9614
|
+
return this.#end;
|
|
9615
|
+
}
|
|
9616
|
+
/**
|
|
9617
|
+
* Move the entire range (start marker through end marker, inclusive) to be
|
|
9618
|
+
* inserted before {@code refSibling} under {@code parent}. If {@code refSibling}
|
|
9619
|
+
* is null the range is appended.
|
|
9620
|
+
*
|
|
9621
|
+
* Implemented via the DOM Range API so that all nodes between the markers move
|
|
9622
|
+
* together as a single contiguous block, regardless of how many or which kinds
|
|
9623
|
+
* of nodes currently sit between them.
|
|
9624
|
+
*/
|
|
9625
|
+
moveBefore(parent, refSibling) {
|
|
9626
|
+
// No-op if already in the desired position
|
|
9627
|
+
if (refSibling === this.#start) {
|
|
9628
|
+
return;
|
|
9629
|
+
}
|
|
9630
|
+
const range = this.#asDomRange();
|
|
9631
|
+
const fragment = range.extractContents();
|
|
9632
|
+
parent.insertBefore(fragment, refSibling);
|
|
9633
|
+
range.detach();
|
|
9634
|
+
}
|
|
9635
|
+
/**
|
|
9636
|
+
* Remove the entire range (start marker through end marker, inclusive) from its
|
|
9637
|
+
* current parent. Safe to call even if the markers are already detached.
|
|
9638
|
+
*/
|
|
9639
|
+
remove() {
|
|
9640
|
+
if (!this.#start.parentNode) {
|
|
9641
|
+
return;
|
|
9642
|
+
}
|
|
9643
|
+
const range = this.#asDomRange();
|
|
9644
|
+
range.deleteContents();
|
|
9645
|
+
range.detach();
|
|
9646
|
+
}
|
|
9647
|
+
/**
|
|
9648
|
+
* Builds a DOM {@link Range} that spans from immediately before the start marker
|
|
9649
|
+
* to immediately after the end marker, covering both markers and everything
|
|
9650
|
+
* between them.
|
|
9651
|
+
*/
|
|
9652
|
+
#asDomRange() {
|
|
9653
|
+
const range = document.createRange();
|
|
9654
|
+
range.setStartBefore(this.#start);
|
|
9655
|
+
range.setEndAfter(this.#end);
|
|
9656
|
+
return range;
|
|
9657
|
+
}
|
|
9658
|
+
}
|
|
9659
|
+
|
|
9538
9660
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
|
9539
9661
|
/**
|
|
9540
9662
|
* Base class for conditional directives such as v-if, v-else-if, and v-else.
|
|
@@ -9735,19 +9857,7 @@ class VConditionalDirective {
|
|
|
9735
9857
|
const anchorParent = this.#vNode.anchorNode?.parentNode;
|
|
9736
9858
|
const nextSibling = this.#vNode.anchorNode?.nextSibling ?? null;
|
|
9737
9859
|
if (clone.nodeType === Node.DOCUMENT_FRAGMENT_NODE && anchorParent) {
|
|
9738
|
-
|
|
9739
|
-
const endMarker = document.createComment('#vif-fragment-end');
|
|
9740
|
-
if (nextSibling) {
|
|
9741
|
-
anchorParent.insertBefore(startMarker, nextSibling);
|
|
9742
|
-
}
|
|
9743
|
-
else {
|
|
9744
|
-
anchorParent.appendChild(startMarker);
|
|
9745
|
-
}
|
|
9746
|
-
anchorParent.insertBefore(endMarker, startMarker.nextSibling);
|
|
9747
|
-
anchorParent.insertBefore(clone, endMarker);
|
|
9748
|
-
// Store markers for later removal
|
|
9749
|
-
vNode.userData.set('vif_fragment_start', startMarker);
|
|
9750
|
-
vNode.userData.set('vif_fragment_end', endMarker);
|
|
9860
|
+
vNode.fragmentRange = VFragmentRange.insert(anchorParent, nextSibling, 'vif-fragment', clone);
|
|
9751
9861
|
this.#renderedVNode = vNode;
|
|
9752
9862
|
this.#renderedVNode.forceUpdate();
|
|
9753
9863
|
return;
|
|
@@ -9768,19 +9878,11 @@ class VConditionalDirective {
|
|
|
9768
9878
|
}
|
|
9769
9879
|
// Destroy VNode first (calls @unmount hooks while DOM is still accessible)
|
|
9770
9880
|
this.#renderedVNode.destroy();
|
|
9771
|
-
// Then remove from DOM. Handle fragment
|
|
9772
|
-
const
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
let node = startMarker;
|
|
9777
|
-
while (node) {
|
|
9778
|
-
const next = node.nextSibling;
|
|
9779
|
-
parentNode.removeChild(node);
|
|
9780
|
-
if (node === endMarker)
|
|
9781
|
-
break;
|
|
9782
|
-
node = next;
|
|
9783
|
-
}
|
|
9881
|
+
// Then remove from DOM. Handle fragment ranges if present.
|
|
9882
|
+
const range = this.#renderedVNode.fragmentRange;
|
|
9883
|
+
if (range) {
|
|
9884
|
+
range.remove();
|
|
9885
|
+
this.#renderedVNode.fragmentRange = undefined;
|
|
9784
9886
|
this.#renderedVNode = undefined;
|
|
9785
9887
|
return;
|
|
9786
9888
|
}
|
|
@@ -10046,6 +10148,12 @@ class VForDirective {
|
|
|
10046
10148
|
}
|
|
10047
10149
|
// Then remove DOM nodes
|
|
10048
10150
|
for (const vNode of this.#renderedItems.values()) {
|
|
10151
|
+
const range = vNode.fragmentRange;
|
|
10152
|
+
if (range) {
|
|
10153
|
+
range.remove();
|
|
10154
|
+
vNode.fragmentRange = undefined;
|
|
10155
|
+
continue;
|
|
10156
|
+
}
|
|
10049
10157
|
if (vNode.node.parentNode) {
|
|
10050
10158
|
vNode.node.parentNode.removeChild(vNode.node);
|
|
10051
10159
|
}
|
|
@@ -10119,22 +10227,12 @@ class VForDirective {
|
|
|
10119
10227
|
vNode.destroy();
|
|
10120
10228
|
}
|
|
10121
10229
|
}
|
|
10122
|
-
// Then remove from DOM. Handle both Element nodes and fragment
|
|
10230
|
+
// Then remove from DOM. Handle both Element nodes and fragment ranges.
|
|
10123
10231
|
for (const vNode of nodesToRemove) {
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
const parentNode = startMarker.parentNode;
|
|
10129
|
-
let node = startMarker;
|
|
10130
|
-
// Remove nodes from startMarker up to and including endMarker
|
|
10131
|
-
while (node) {
|
|
10132
|
-
const next = node.nextSibling;
|
|
10133
|
-
parentNode.removeChild(node);
|
|
10134
|
-
if (node === endMarker)
|
|
10135
|
-
break;
|
|
10136
|
-
node = next;
|
|
10137
|
-
}
|
|
10232
|
+
const range = vNode.fragmentRange;
|
|
10233
|
+
if (range) {
|
|
10234
|
+
range.remove();
|
|
10235
|
+
vNode.fragmentRange = undefined;
|
|
10138
10236
|
continue;
|
|
10139
10237
|
}
|
|
10140
10238
|
// Fallback: remove the node itself if it's attached
|
|
@@ -10174,26 +10272,14 @@ class VForDirective {
|
|
|
10174
10272
|
bindings,
|
|
10175
10273
|
dependentIdentifiers: depIds,
|
|
10176
10274
|
});
|
|
10177
|
-
// If clone is a DocumentFragment,
|
|
10275
|
+
// If clone is a DocumentFragment, wrap it in a VFragmentRange so the
|
|
10276
|
+
// entire expanded content moves/removes as one atomic unit.
|
|
10178
10277
|
if (clone.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
10179
|
-
const
|
|
10180
|
-
|
|
10181
|
-
// Insert start and end markers and the fragment's children between them
|
|
10182
|
-
if (prevNode.nextSibling) {
|
|
10183
|
-
parent.insertBefore(startMarker, prevNode.nextSibling);
|
|
10184
|
-
}
|
|
10185
|
-
else {
|
|
10186
|
-
parent.appendChild(startMarker);
|
|
10187
|
-
}
|
|
10188
|
-
parent.insertBefore(endMarker, startMarker.nextSibling);
|
|
10189
|
-
parent.insertBefore(clone, endMarker);
|
|
10190
|
-
// Store markers on the VNode for later removal/movement
|
|
10191
|
-
vNode.userData.set('vfor_fragment_start', startMarker);
|
|
10192
|
-
vNode.userData.set('vfor_fragment_end', endMarker);
|
|
10278
|
+
const range = VFragmentRange.insert(parent, prevNode.nextSibling, 'vfor-fragment', clone);
|
|
10279
|
+
vNode.fragmentRange = range;
|
|
10193
10280
|
newRenderedItems.set(key, vNode);
|
|
10194
10281
|
vNode.forceUpdate();
|
|
10195
|
-
|
|
10196
|
-
prevNode = endMarker;
|
|
10282
|
+
prevNode = range.lastNode;
|
|
10197
10283
|
continue;
|
|
10198
10284
|
}
|
|
10199
10285
|
// Determine what to insert: anchor node (if exists) or the clone itself
|
|
@@ -10213,22 +10299,28 @@ class VForDirective {
|
|
|
10213
10299
|
newRenderedItems.set(key, vNode);
|
|
10214
10300
|
// Update bindings
|
|
10215
10301
|
this.#updateItemBindings(vNode, context);
|
|
10216
|
-
//
|
|
10217
|
-
|
|
10218
|
-
const
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
parent.insertBefore(actualNode, prevNode.nextSibling);
|
|
10302
|
+
// For fragment-backed iterations, move the entire range atomically.
|
|
10303
|
+
// For single-node iterations, move just the node.
|
|
10304
|
+
const range = vNode.fragmentRange;
|
|
10305
|
+
if (range) {
|
|
10306
|
+
if (prevNode.nextSibling !== range.firstNode) {
|
|
10307
|
+
range.moveBefore(parent, prevNode.nextSibling);
|
|
10223
10308
|
}
|
|
10224
|
-
|
|
10225
|
-
|
|
10309
|
+
}
|
|
10310
|
+
else {
|
|
10311
|
+
const actualNode = vNode.anchorNode || vNode.node;
|
|
10312
|
+
if (prevNode.nextSibling !== actualNode) {
|
|
10313
|
+
if (prevNode.nextSibling) {
|
|
10314
|
+
parent.insertBefore(actualNode, prevNode.nextSibling);
|
|
10315
|
+
}
|
|
10316
|
+
else {
|
|
10317
|
+
parent.appendChild(actualNode);
|
|
10318
|
+
}
|
|
10226
10319
|
}
|
|
10227
10320
|
}
|
|
10228
10321
|
}
|
|
10229
|
-
//
|
|
10230
|
-
|
|
10231
|
-
prevNode = fragmentEndForPrev || vNode.anchorNode || vNode.node;
|
|
10322
|
+
// Advance prevNode to this iteration's last DOM node
|
|
10323
|
+
prevNode = vNode.fragmentRange?.lastNode ?? vNode.anchorNode ?? vNode.node;
|
|
10232
10324
|
}
|
|
10233
10325
|
// Update rendered items map
|
|
10234
10326
|
this.#renderedItems = newRenderedItems;
|