@mintjamsinc/ichigojs 0.1.81 → 0.1.83

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.
@@ -8984,9 +8984,9 @@ class IchigoElementRegistry {
8984
8984
  * ```
8985
8985
  *
8986
8986
  * This directive is created only when a scoped template for the slot's name
8987
- * exists (see {@link VSlotOutletDirective.tryCreate}); otherwise `<slot>`
8988
- * elements keep their existing behavior (static distribution at expansion,
8989
- * or fallback content compiled in the component scope).
8987
+ * exists (see {@link VSlotOutletDirective.tryCreate}); otherwise plain slot
8988
+ * content is moved into place by VStaticSlotOutletDirective, and a `<slot>`
8989
+ * with no content at all renders its fallback in the component scope.
8990
8990
  *
8991
8991
  * Rendering happens synchronously in onMount — before the component
8992
8992
  * application would compile the slot's fallback children — by replacing the
@@ -9278,6 +9278,266 @@ class VSlotOutletDirective {
9278
9278
  }
9279
9279
  }
9280
9280
 
9281
+ // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
9282
+ /**
9283
+ * Renders a static slot outlet: a `<slot>` element inside a component template
9284
+ * whose host element received plain slot content from the parent — child
9285
+ * nodes for the default slot, or elements carrying `slot="name"`:
9286
+ *
9287
+ * ```html
9288
+ * <!-- component template -->
9289
+ * <div v-if="open" class="body"><slot></slot></div>
9290
+ *
9291
+ * <!-- usage -->
9292
+ * <my-dialog :open="show">
9293
+ * <my-field :label="title"></my-field>
9294
+ * </my-dialog>
9295
+ * ```
9296
+ *
9297
+ * The slot content is the parent application's own nodes, compiled in the
9298
+ * parent scope; it keeps its bindings, listeners and component props only as
9299
+ * long as those very nodes are displayed. So the outlet MOVES them into place
9300
+ * and never clones them. That is what lets a `<slot>` sit behind a v-if or
9301
+ * v-for in the component template: the directive clones its template source,
9302
+ * the clone carries a fresh `<slot>`, and the outlet created for that clone
9303
+ * moves the live nodes in.
9304
+ *
9305
+ * Nodes can only be in one place, so when the same slot is rendered more than
9306
+ * once (e.g. a `<slot>` inside a v-for), the outlet rendered last receives the
9307
+ * content and the earlier ones are left empty. When the outlet holding the
9308
+ * content goes away while others remain (its row is removed), the content
9309
+ * moves to the last remaining one. For content per row, use a scoped slot
9310
+ * (`<template v-slot:name="scope">`), which renders a copy per outlet
9311
+ * (VSlotOutletDirective).
9312
+ *
9313
+ * The component application never compiles the moved nodes: they are placed
9314
+ * in onMount, after the parent VNode has taken its snapshot of the children
9315
+ * to compile. Components inside them belong to the parent application, which
9316
+ * expands them through {@link expandComponents} once they are in the document.
9317
+ * When the last outlet goes away (its v-if turns false) the nodes leave with
9318
+ * the removed clone and wait there until the next outlet moves them again;
9319
+ * they are not destroyed, because they are not this application's to destroy.
9320
+ *
9321
+ * Created only when the host captured content for the slot's name and no
9322
+ * scoped template claims it; otherwise the `<slot>` element renders its
9323
+ * fallback children in the component scope.
9324
+ */
9325
+ class VStaticSlotOutletDirective {
9326
+ /**
9327
+ * The live outlets of each slot, in the order they were rendered, keyed by
9328
+ * the slot's node array (one per host element and slot name). The last
9329
+ * one holds the content.
9330
+ */
9331
+ static #outlets = new WeakMap();
9332
+ /**
9333
+ * Marks where the `<slot>` element was, so the content can be moved back
9334
+ * here when a later outlet of the same slot goes away.
9335
+ */
9336
+ #anchor;
9337
+ /**
9338
+ * Guards against double destruction.
9339
+ */
9340
+ #destroyed = false;
9341
+ /**
9342
+ * The virtual node of the `<slot>` element (in the component application).
9343
+ */
9344
+ #vNode;
9345
+ /**
9346
+ * The slot content: the parent application's nodes, shared with the host
9347
+ * element's slot cache.
9348
+ */
9349
+ #nodes;
9350
+ /**
9351
+ * The host element's VNode in the parent application, whose children are
9352
+ * the VNodes of the slot content. Undefined when the host was not compiled
9353
+ * by an application (then there is nothing to expand).
9354
+ */
9355
+ #hostVNode;
9356
+ /**
9357
+ * Creates a static slot outlet for the given `<slot>` VNode when the host
9358
+ * element captured content for its slot name. Returns undefined when there
9359
+ * is none, so the `<slot>` element keeps rendering its fallback content.
9360
+ */
9361
+ static tryCreate(vNode) {
9362
+ const host = vNode.vApplication.hostElement;
9363
+ if (!host || typeof host._ichigoSlotNodes !== 'function') {
9364
+ return undefined;
9365
+ }
9366
+ const element = vNode.node;
9367
+ const nodes = host._ichigoSlotNodes(element.getAttribute('name') || 'default');
9368
+ if (!nodes) {
9369
+ return undefined;
9370
+ }
9371
+ return new VStaticSlotOutletDirective(vNode, nodes, host._ichigoHost?.vNode);
9372
+ }
9373
+ constructor(vNode, nodes, hostVNode) {
9374
+ this.#vNode = vNode;
9375
+ this.#nodes = nodes;
9376
+ this.#hostVNode = hostVNode;
9377
+ // Slot props (v-bind / :) only mean something to a scoped template.
9378
+ // Consume them so the generic attribute parsing does not evaluate them
9379
+ // on an element that is about to leave the document.
9380
+ const element = vNode.node;
9381
+ for (const attr of Array.from(element.attributes)) {
9382
+ if (attr.name.startsWith('v-bind:') || attr.name.startsWith(':')) {
9383
+ element.removeAttribute(attr.name);
9384
+ }
9385
+ }
9386
+ }
9387
+ /**
9388
+ * @inheritdoc
9389
+ */
9390
+ get name() {
9391
+ return 'static-slot-outlet';
9392
+ }
9393
+ /**
9394
+ * @inheritdoc
9395
+ */
9396
+ get vNode() {
9397
+ return this.#vNode;
9398
+ }
9399
+ /**
9400
+ * @inheritdoc
9401
+ */
9402
+ get needsAnchor() {
9403
+ return false;
9404
+ }
9405
+ /**
9406
+ * @inheritdoc
9407
+ */
9408
+ get bindingsPreparer() {
9409
+ return undefined;
9410
+ }
9411
+ /**
9412
+ * @inheritdoc
9413
+ */
9414
+ get domUpdater() {
9415
+ return undefined;
9416
+ }
9417
+ /**
9418
+ * @inheritdoc
9419
+ */
9420
+ get templatize() {
9421
+ return false;
9422
+ }
9423
+ /**
9424
+ * @inheritdoc
9425
+ */
9426
+ get dependentIdentifiers() {
9427
+ return [];
9428
+ }
9429
+ /**
9430
+ * @inheritdoc
9431
+ *
9432
+ * Moves the slot content in place of the `<slot>` element — synchronously
9433
+ * during VNode construction, before the component application would
9434
+ * compile the slot's fallback children (which the content replaces).
9435
+ */
9436
+ get onMount() {
9437
+ return () => {
9438
+ const slot = this.#vNode.node;
9439
+ while (slot.firstChild) {
9440
+ slot.removeChild(slot.firstChild);
9441
+ }
9442
+ this.#anchor = document.createComment('slot');
9443
+ slot.replaceWith(this.#anchor);
9444
+ let outlets = VStaticSlotOutletDirective.#outlets.get(this.#nodes);
9445
+ if (!outlets) {
9446
+ outlets = [];
9447
+ VStaticSlotOutletDirective.#outlets.set(this.#nodes, outlets);
9448
+ }
9449
+ outlets.push(this);
9450
+ this.#place();
9451
+ };
9452
+ }
9453
+ /**
9454
+ * @inheritdoc
9455
+ */
9456
+ get onMounted() {
9457
+ return undefined;
9458
+ }
9459
+ /**
9460
+ * @inheritdoc
9461
+ */
9462
+ get onUpdate() {
9463
+ return undefined;
9464
+ }
9465
+ /**
9466
+ * @inheritdoc
9467
+ */
9468
+ get onUpdated() {
9469
+ return undefined;
9470
+ }
9471
+ /**
9472
+ * @inheritdoc
9473
+ */
9474
+ get onUnmount() {
9475
+ return undefined;
9476
+ }
9477
+ /**
9478
+ * @inheritdoc
9479
+ */
9480
+ get onUnmounted() {
9481
+ return undefined;
9482
+ }
9483
+ /**
9484
+ * @inheritdoc
9485
+ *
9486
+ * Expands the components in the slot content through the parent
9487
+ * application's VNodes. Called by the walk of whoever inserted this
9488
+ * outlet into the document (VApplication.mount, v-if, v-for), so the
9489
+ * content is connected by now — unless a later outlet took it, in which
9490
+ * case that outlet's walk expands it.
9491
+ */
9492
+ expandComponents() {
9493
+ const children = this.#hostVNode?.childVNodes;
9494
+ if (!children) {
9495
+ return;
9496
+ }
9497
+ for (const child of children) {
9498
+ if (this.#nodes.includes(child.node)) {
9499
+ child.expandComponents();
9500
+ }
9501
+ }
9502
+ }
9503
+ /**
9504
+ * @inheritdoc
9505
+ *
9506
+ * The slot content is the parent application's; nothing to release here.
9507
+ */
9508
+ destroy() {
9509
+ if (this.#destroyed) {
9510
+ return;
9511
+ }
9512
+ this.#destroyed = true;
9513
+ const outlets = VStaticSlotOutletDirective.#outlets.get(this.#nodes);
9514
+ if (!outlets) {
9515
+ return;
9516
+ }
9517
+ const index = outlets.indexOf(this);
9518
+ if (index === -1) {
9519
+ return;
9520
+ }
9521
+ const wasHolder = index === outlets.length - 1;
9522
+ outlets.splice(index, 1);
9523
+ // Hand the content to the last remaining outlet before this one's DOM
9524
+ // is removed (v-if / v-for destroy the VNode first, then remove it),
9525
+ // so removing the row that shows it does not take it out of view.
9526
+ const next = outlets[outlets.length - 1];
9527
+ if (wasHolder && next) {
9528
+ next.#place();
9529
+ next.expandComponents();
9530
+ }
9531
+ }
9532
+ /**
9533
+ * Moves the slot content right after this outlet's anchor. Moving, not
9534
+ * cloning: nodes shown by another outlet of the same slot leave it.
9535
+ */
9536
+ #place() {
9537
+ this.#anchor?.after(...this.#nodes);
9538
+ }
9539
+ }
9540
+
9281
9541
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
9282
9542
  /**
9283
9543
  * Manages directives associated with a virtual node (VNode).
@@ -9446,10 +9706,12 @@ class VDirectiveManager {
9446
9706
  const element = this.#vNode.node;
9447
9707
  // Parse directives from attributes
9448
9708
  const directives = [];
9449
- // Scoped slot outlet: a <slot> element (without structural directives)
9450
- // inside a component whose host received a matching <template v-slot:...>.
9451
- // This is an element-based directive, created before the generic
9452
- // attribute parsing so it can consume the slot-prop bindings (:x).
9709
+ // Slot outlet: a <slot> element (without structural directives) inside a
9710
+ // component whose host received content for it — a matching
9711
+ // <template v-slot:...> (rendered per outlet), or else plain slot
9712
+ // content (moved into place). This is an element-based directive,
9713
+ // created before the generic attribute parsing so it can consume the
9714
+ // slot-prop bindings (:x).
9453
9715
  // A <slot> carrying v-if / v-for is handled structurally first; the
9454
9716
  // outlet is then created when the clone is compiled.
9455
9717
  if (element.tagName === 'SLOT' &&
@@ -9457,7 +9719,8 @@ class VDirectiveManager {
9457
9719
  !element.hasAttribute(StandardDirectiveName.V_IF) &&
9458
9720
  !element.hasAttribute(StandardDirectiveName.V_ELSE_IF) &&
9459
9721
  !element.hasAttribute(StandardDirectiveName.V_ELSE)) {
9460
- const outlet = VSlotOutletDirective.tryCreate(this.#vNode);
9722
+ const outlet = VSlotOutletDirective.tryCreate(this.#vNode)
9723
+ ?? VStaticSlotOutletDirective.tryCreate(this.#vNode);
9461
9724
  if (outlet) {
9462
9725
  directives.push(outlet);
9463
9726
  }
@@ -9867,9 +10130,9 @@ class VNode {
9867
10130
  */
9868
10131
  #fragmentRange;
9869
10132
  /**
9870
- * Set by {@link destroy}. A destroyed clone stays in its parent's child
9871
- * list (v-if / v-for render new clones under the same parent), so walks
9872
- * over the children must skip it.
10133
+ * Set by {@link destroy}. A destroyed node also leaves its parent's child
10134
+ * list, but a walk that took its copy of the list before the node was
10135
+ * destroyed may still reach it, and must skip it.
9873
10136
  */
9874
10137
  #destroyed = false;
9875
10138
  /**
@@ -10039,7 +10302,8 @@ class VNode {
10039
10302
  // walk runs once the subtree is in the document, which the outlet
10040
10303
  // cannot know when it renders inside a detached v-if / v-for clone.
10041
10304
  this.#directiveManager?.directives?.forEach(d => d.expandComponents?.());
10042
- this.#childVNodes?.forEach(child => child.expandComponents());
10305
+ // A copy: expanding can render and destroy clones under this node.
10306
+ this.#childVNodes?.slice().forEach(child => child.expandComponents());
10043
10307
  }
10044
10308
  /**
10045
10309
  * The application instance associated with this virtual node.
@@ -10351,8 +10615,9 @@ class VNode {
10351
10615
  updater.applyToDOM();
10352
10616
  }
10353
10617
  }
10354
- // Recursively update child virtual nodes
10355
- this.#childVNodes?.forEach(childVNode => {
10618
+ // Recursively update child virtual nodes. A copy: an update can
10619
+ // destroy a v-if / v-for clone, which removes it from this list.
10620
+ this.#childVNodes?.slice().forEach(childVNode => {
10356
10621
  childVNode.forceUpdate();
10357
10622
  });
10358
10623
  // If no directive requires template preservation, call onUpdated for directives that do not templatize
@@ -10364,7 +10629,7 @@ class VNode {
10364
10629
  }
10365
10630
  else if (this.#nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
10366
10631
  // For document fragments, recursively force update child VNodes
10367
- this.#childVNodes?.forEach(childVNode => {
10632
+ this.#childVNodes?.slice().forEach(childVNode => {
10368
10633
  childVNode.forceUpdate();
10369
10634
  });
10370
10635
  }
@@ -10446,6 +10711,7 @@ class VNode {
10446
10711
  * 4. Unregister dependencies
10447
10712
  * 5. Clean up directive manager
10448
10713
  * 6. Call onUnmounted lifecycle hooks
10714
+ * 7. Leave the parent's child list
10449
10715
  */
10450
10716
  destroy() {
10451
10717
  this.#destroyed = true;
@@ -10472,9 +10738,9 @@ class VNode {
10472
10738
  }
10473
10739
  this.#userData.clear();
10474
10740
  }
10475
- // Recursively destroy child nodes
10741
+ // Recursively destroy child nodes (a copy: each one leaves this list)
10476
10742
  if (this.#childVNodes) {
10477
- for (const childVNode of this.#childVNodes) {
10743
+ for (const childVNode of this.#childVNodes.slice()) {
10478
10744
  try {
10479
10745
  childVNode.destroy();
10480
10746
  }
@@ -10502,6 +10768,15 @@ class VNode {
10502
10768
  d.onUnmounted?.();
10503
10769
  });
10504
10770
  }
10771
+ // Leave the parent's child list. v-if and v-for render every new clone
10772
+ // under the same parent VNode, so a destroyed clone left in the list
10773
+ // would keep its whole subtree — VNodes, DOM nodes, bindings — alive
10774
+ // for as long as the parent, growing with each toggle or row change.
10775
+ const siblings = this.#parentVNode ? this.#parentVNode.#childVNodes : undefined;
10776
+ const index = siblings ? siblings.indexOf(this) : -1;
10777
+ if (index !== -1) {
10778
+ siblings.splice(index, 1);
10779
+ }
10505
10780
  }
10506
10781
  }
10507
10782
 
@@ -15504,9 +15779,13 @@ class VDOM {
15504
15779
  * microtask fires, so data() can read them. A component used without any prop
15505
15780
  * binding still mounts — with its declared defaults — instead of waiting forever.
15506
15781
  *
15507
- * Slot content is captured on the first connection and cached, so a component
15508
- * that is moved in the DOM (e.g. reordered by v-for) re-expands its template
15509
- * and redistributes the same slot nodes on reconnection.
15782
+ * Slot content is captured on the first expansion and cached. The nodes are the
15783
+ * parent application's compiled nodes and are only ever moved, never cloned:
15784
+ * each `<slot>` outlet the component renders moves them into place
15785
+ * (VStaticSlotOutletDirective; scoped `<template v-slot>` content is rendered
15786
+ * per outlet by VSlotOutletDirective instead). A component that is moved in
15787
+ * the DOM (e.g. reordered by v-for) re-expands its template, and its outlets
15788
+ * move the same nodes into the fresh expansion.
15510
15789
  *
15511
15790
  * Subclasses must set the static fields _template, _props, _propOptions and
15512
15791
  * _buildOptions before calling customElements.define(). defineComponent()
@@ -15531,12 +15810,13 @@ class IchigoElement extends HTMLElement {
15531
15810
  */
15532
15811
  #mountScheduled = false;
15533
15812
  /**
15534
- * Slot content captured on the first connection. Reused on reconnection
15813
+ * Slot content captured on the first expansion. Reused on reconnection
15535
15814
  * (v-for reorder moves the host element, which re-runs connectedCallback
15536
15815
  * with the previously expanded template as children — not authored slot
15537
- * content — so the original capture must be redistributed instead).
15538
- * `scopedSlots` holds `<template v-slot:...>` children by slot name; they
15539
- * are rendered per outlet by VSlotOutletDirective, not distributed here.
15816
+ * content — so the original capture must be used instead).
15817
+ * `defaultNodes` / `namedNodes` are moved into place by
15818
+ * VStaticSlotOutletDirective; `scopedSlots` holds `<template v-slot:...>`
15819
+ * children by slot name, rendered per outlet by VSlotOutletDirective.
15540
15820
  */
15541
15821
  #slotCache;
15542
15822
  /**
@@ -15554,6 +15834,18 @@ class IchigoElement extends HTMLElement {
15554
15834
  get _ichigoScopedSlots() {
15555
15835
  return this.#slotCache?.scopedSlots;
15556
15836
  }
15837
+ /**
15838
+ * The static slot content captured for the given slot name ('default'
15839
+ * for the unnamed slot): the parent application's nodes, moved into place
15840
+ * by VStaticSlotOutletDirective. Undefined when nothing was provided for
15841
+ * that name, in which case the outlet's fallback content renders.
15842
+ */
15843
+ _ichigoSlotNodes(name) {
15844
+ const nodes = name === 'default'
15845
+ ? this.#slotCache?.defaultNodes
15846
+ : this.#slotCache?.namedNodes.get(name);
15847
+ return nodes && nodes.length > 0 ? nodes : undefined;
15848
+ }
15557
15849
  /**
15558
15850
  * Handles RE-attachment, and nothing else.
15559
15851
  *
@@ -15655,29 +15947,23 @@ class IchigoElement extends HTMLElement {
15655
15947
  }
15656
15948
  this.#slotCache = { defaultNodes, namedNodes, scopedSlots };
15657
15949
  }
15658
- const { defaultNodes, namedNodes } = this.#slotCache;
15659
15950
  // Clear host element so we can append the cloned template.
15660
15951
  // (On reconnection this discards the previous expansion; the cached slot
15661
- // nodes are still referenced and are moved into the fresh clone below.)
15952
+ // nodes are still referenced and are moved into the fresh expansion by
15953
+ // its outlets.)
15954
+ // The slot nodes are NOT distributed into the template here: they are
15955
+ // the parent application's live, compiled nodes, and a <slot> behind a
15956
+ // v-if / v-for in the template is cloned with its surroundings — which
15957
+ // would render dead copies without their bindings, listeners or props.
15958
+ // They stay parked in the cache until a <slot> outlet is rendered, and
15959
+ // VStaticSlotOutletDirective then moves them into place.
15662
15960
  while (this.firstChild) {
15663
15961
  this.removeChild(this.firstChild);
15664
15962
  }
15665
15963
  // --- 2. Clone the component template ---
15666
15964
  const fragment = templateEl.content.cloneNode(true);
15667
15965
  const root = this.#findRootElement(fragment);
15668
- // --- 3. Distribute named slot content ---
15669
- for (const [name, nodes] of namedNodes) {
15670
- const slot = root.querySelector(`slot[name="${name}"]`);
15671
- if (slot) {
15672
- slot.replaceWith(...nodes);
15673
- }
15674
- }
15675
- // --- 4. Distribute default slot content ---
15676
- const defaultSlot = root.querySelector('slot:not([name])');
15677
- if (defaultSlot && defaultNodes.length > 0) {
15678
- defaultSlot.replaceWith(...defaultNodes);
15679
- }
15680
- // Attach the populated template to the host element
15966
+ // Attach the template to the host element
15681
15967
  this.appendChild(root);
15682
15968
  this.#mountRoot = root;
15683
15969
  this._ichigoExpanded = true;