@mintjamsinc/ichigojs 0.1.78 → 0.1.80

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
@@ -957,6 +957,18 @@ defineComponent('my-list', {
957
957
  </my-list>
958
958
  ```
959
959
 
960
+ > **Ordering:** a component's `<template>` must be in the document, and
961
+ > `defineComponent()` must have run, before the application that uses the
962
+ > component is mounted. The application expands a component while it compiles
963
+ > it — after installing the host context, delivering the prop values and
964
+ > compiling the projected slot content in the parent scope — so a component
965
+ > compiled without its template is reported as a console warning and left
966
+ > empty. It is never retried: an app that renders anyway is an app whose bug
967
+ > cannot be found. A hyphenated tag that nothing has registered (a forgotten
968
+ > import, a typo) is reported the same way. Components therefore have to live
969
+ > inside a mounted application; one placed outside any application is never
970
+ > expanded.
971
+
960
972
  **Props:**
961
973
 
962
974
  - Declared via the `props` array, or as a record with per-prop options:
package/dist/ichigo.cjs CHANGED
@@ -9224,8 +9224,9 @@
9224
9224
  this.#scopeBindings.setLocal(this.#entry.scopeVar, this.#buildSlotProps());
9225
9225
  }
9226
9226
  // Clone the scoped template's content and create its VNode tree BEFORE
9227
- // inserting into the DOM, so components inside the slot content are
9228
- // compiled before their connectedCallback can expand them.
9227
+ // inserting into the DOM, so its directives run against a subtree
9228
+ // nothing else can observe yet. Components inside are expanded
9229
+ // afterwards, once the content is in the document.
9229
9230
  // (Created through the parent application to keep the VNode import
9230
9231
  // type-only; a direct `new VNode` here would close a module cycle
9231
9232
  // VNode -> VDirectiveManager -> VSlotOutletDirective -> VNode.)
@@ -9244,6 +9245,8 @@
9244
9245
  slot.replaceWith(fragment);
9245
9246
  // Initial rendering
9246
9247
  this.#contentVNode.forceUpdate();
9248
+ // Components in the slot content expand once it is in the document.
9249
+ this.#contentVNode.expandComponents();
9247
9250
  }
9248
9251
  /**
9249
9252
  * Re-evaluates the slot props (component-side change) and pushes them into
@@ -9860,6 +9863,31 @@
9860
9863
  * remove the entire fragment atomically.
9861
9864
  */
9862
9865
  #fragmentRange;
9866
+ /**
9867
+ * Custom element tags already reported by
9868
+ * {@link VNode.#warnIfUnknownCustomElement}.
9869
+ */
9870
+ static #warnedUnknownTags = new Set();
9871
+ /**
9872
+ * Warns about a hyphenated tag that no one has registered — a forgotten
9873
+ * import, a missing defineComponent() call, or a typo in the tag name.
9874
+ * Such an element is compiled as an ordinary element and renders its own
9875
+ * children, which looks enough like a working page to cost an afternoon.
9876
+ * Reported once per tag; third-party custom elements are recognised
9877
+ * through the registry the browser keeps.
9878
+ */
9879
+ static #warnIfUnknownCustomElement(nodeName) {
9880
+ const tag = nodeName.toLowerCase();
9881
+ if (!tag.includes('-') || VNode.#warnedUnknownTags.has(tag)) {
9882
+ return;
9883
+ }
9884
+ if (typeof customElements !== 'undefined' && customElements.get(tag)) {
9885
+ return;
9886
+ }
9887
+ VNode.#warnedUnknownTags.add(tag);
9888
+ console.warn(`[ichigo] <${tag}>: unknown custom element — nothing has registered this tag.`
9889
+ + ' Did you forget to import its module or call defineComponent(), or is the tag misspelled?');
9890
+ }
9863
9891
  /**
9864
9892
  * Creates an instance of the virtual node.
9865
9893
  * @param args The initialization arguments for the virtual node.
@@ -9891,6 +9919,9 @@
9891
9919
  if (isComponent) {
9892
9920
  element._ichigoHost = { vNode: this };
9893
9921
  }
9922
+ else {
9923
+ VNode.#warnIfUnknownCustomElement(this.#nodeName);
9924
+ }
9894
9925
  // Initialize child virtual nodes
9895
9926
  this.#childVNodes = [];
9896
9927
  // Initialize directive manager
@@ -9949,6 +9980,37 @@
9949
9980
  // Register this node as a dependent of the parent node, if any
9950
9981
  this.#closers = this.#parentVNode?.addDependent(this);
9951
9982
  }
9983
+ /**
9984
+ * Expands the component elements this application compiled in this subtree
9985
+ * but has not expanded yet, parents before their children.
9986
+ *
9987
+ * This is the ONLY thing that expands a component. It is deliberately a
9988
+ * separate step from compilation rather than a call at the end of the
9989
+ * constructor: a v-if / v-for clone is compiled while it is still detached
9990
+ * (so that its directives run before anything can observe a half-built
9991
+ * subtree), and expanding a detached element would mount a component
9992
+ * application outside the document. The callers therefore compile, insert,
9993
+ * and then call this — see VApplication.mount, VConditionalDirective,
9994
+ * VForDirective and VSlotOutletDirective.
9995
+ *
9996
+ * Walking only {@link childVNodes} keeps the traversal inside this
9997
+ * application: the internals of an expanded component were never compiled
9998
+ * here, so they are not reachable and are left to their own application.
9999
+ * A templatized node is skipped whole — it is a template source, and its
10000
+ * clones expand when they are inserted.
10001
+ */
10002
+ expandComponents() {
10003
+ if (this.#templatized) {
10004
+ return;
10005
+ }
10006
+ if (this.#nodeType === Node.ELEMENT_NODE && IchigoElementRegistry.has(this.#nodeName)) {
10007
+ const element = this.#node;
10008
+ if (element._ichigoExpanded !== true) {
10009
+ element._ichigoExpand?.();
10010
+ }
10011
+ }
10012
+ this.#childVNodes?.forEach(child => child.expandComponents());
10013
+ }
9952
10014
  /**
9953
10015
  * The application instance associated with this virtual node.
9954
10016
  */
@@ -10749,11 +10811,10 @@
10749
10811
  }
10750
10812
  // Clone the original node and create a new VNode for it
10751
10813
  const clone = this.#cloneNode();
10752
- // Create a new VNode for the cloned element BEFORE inserting into DOM.
10753
- // This prevents custom elements (Web Components) from having their
10754
- // connectedCallback fire before VNode processes their children, which
10755
- // would cause the parent VApplication to incorrectly adopt the custom
10756
- // element's internal template content as its own VNode tree.
10814
+ // Compile the clone BEFORE inserting it, so its directives run against a
10815
+ // subtree nothing else can observe yet (v-if inside creates anchors and
10816
+ // detaches template sources). Components inside are expanded afterwards,
10817
+ // once the clone is in the document — see expandComponents() below.
10757
10818
  // Pass the current bindings to ensure loop variables from v-for are available
10758
10819
  const vNode = new VNode({
10759
10820
  node: clone,
@@ -10768,12 +10829,16 @@
10768
10829
  vNode.fragmentRange = VFragmentRange.insert(anchorParent, nextSibling, 'vif-fragment', clone);
10769
10830
  this.#renderedVNode = vNode;
10770
10831
  this.#renderedVNode.forceUpdate();
10832
+ this.#renderedVNode.expandComponents();
10771
10833
  return;
10772
10834
  }
10773
10835
  const nodeToInsert = vNode.anchorNode || clone;
10774
10836
  anchorParent?.insertBefore(nodeToInsert, nextSibling);
10775
10837
  this.#renderedVNode = vNode;
10776
10838
  this.#renderedVNode.forceUpdate();
10839
+ // Components expand once the clone is in the document and its bindings
10840
+ // have been applied, so they mount connected and with their props set.
10841
+ this.#renderedVNode.expandComponents();
10777
10842
  }
10778
10843
  /**
10779
10844
  * Removes the node from the DOM.
@@ -11230,6 +11295,7 @@
11230
11295
  vNode.fragmentRange = range;
11231
11296
  newRenderedItems.push({ key, vNode });
11232
11297
  vNode.forceUpdate();
11298
+ vNode.expandComponents();
11233
11299
  prevNode = range.lastNode;
11234
11300
  continue;
11235
11301
  }
@@ -11244,6 +11310,10 @@
11244
11310
  }
11245
11311
  newRenderedItems.push({ key, vNode });
11246
11312
  vNode.forceUpdate();
11313
+ // Components expand once the clone is in the document and its
11314
+ // bindings have been applied, so they mount connected and with
11315
+ // their props set.
11316
+ vNode.expandComponents();
11247
11317
  }
11248
11318
  else {
11249
11319
  // Reuse existing item
@@ -14813,6 +14883,10 @@
14813
14883
  });
14814
14884
  // Initial rendering
14815
14885
  this.#vNode.update();
14886
+ // Expand the components in the tree. Last, so every component element
14887
+ // has been compiled and has had its prop values delivered before its
14888
+ // template is cloned in. See VNode.expandComponents.
14889
+ this.#vNode.expandComponents();
14816
14890
  // Remove v-cloak attributes after mounting
14817
14891
  // v-cloak is used to hide un-compiled template until the app is ready
14818
14892
  // Usage: Add CSS rule "[v-cloak] { display: none; }" to hide elements with v-cloak
@@ -15377,6 +15451,19 @@
15377
15451
  /**
15378
15452
  * Base class for ichigo.js-backed Web Components (Light DOM, no Shadow DOM).
15379
15453
  *
15454
+ * Expansion — cloning the template into this element — is driven by the
15455
+ * application that owns the element, not by connectedCallback: VNode compiles
15456
+ * the element first (installing the host context, delivering the prop values
15457
+ * and compiling the authored slot content in the owner's scope) and only then
15458
+ * calls {@link _ichigoExpand}. That leaves connectedCallback a single job,
15459
+ * re-attachment; see there.
15460
+ *
15461
+ * The component's <template> must be present in the document, and
15462
+ * defineComponent() must have run, before the owning application is mounted.
15463
+ * That is the application's responsibility. A missing template is reported as
15464
+ * a warning and the element is left empty — it is never silently retried,
15465
+ * because an app that renders anyway is an app whose bug cannot be found.
15466
+ *
15380
15467
  * Mount timing:
15381
15468
  * - If the component declares no props, the VApplication is mounted synchronously
15382
15469
  * at the end of connectedCallback (the template is known, no props to wait for).
@@ -15428,13 +15515,6 @@
15428
15515
  * belong to this component's VApplication and scope).
15429
15516
  */
15430
15517
  _ichigoExpanded = false;
15431
- /**
15432
- * Host context installed by the parent application's VNode when it
15433
- * compiles this element. Gives component-boundary features (e.g. scoped
15434
- * slot outlets) access to the parent scope. Undefined when the element is
15435
- * placed outside any ichigo application.
15436
- */
15437
- _ichigoHost;
15438
15518
  /**
15439
15519
  * The scoped slot templates captured from this element's children, keyed
15440
15520
  * by slot name ('default' for `v-slot` without a name). Read by
@@ -15443,20 +15523,42 @@
15443
15523
  get _ichigoScopedSlots() {
15444
15524
  return this.#slotCache?.scopedSlots;
15445
15525
  }
15526
+ /**
15527
+ * Handles RE-attachment, and nothing else.
15528
+ *
15529
+ * A first connection is not what expands this element: the owning
15530
+ * application does that from {@link _ichigoExpand} once it has compiled the
15531
+ * element. So a connection event on a not-yet-expanded element means the
15532
+ * owner has not reached it yet, and the right thing to do is nothing. That
15533
+ * covers every case the old guards enumerated one by one — a v-if / v-for
15534
+ * template source, an element inside an enclosing component's template
15535
+ * clone, a clone inserted before its directive expands it.
15536
+ *
15537
+ * An expanded element whose mount root has been torn down, on the other
15538
+ * hand, was disconnected and is now coming back: v-for reorders relocate
15539
+ * the host element, which disconnects and reconnects it. Its children are
15540
+ * the previous expansion, not authored slot content, so it re-expands from
15541
+ * the cached slot nodes. Testing the mount root rather than the expansion
15542
+ * flag alone keeps this from firing on an element that was expanded while
15543
+ * detached and is being inserted for the first time.
15544
+ */
15446
15545
  connectedCallback() {
15447
- // --- 0a. Guard: do not expand a template-source element ---
15448
- // An element still carrying a templatizing directive attribute (v-if
15449
- // family / v-for) is a template source: the owning application clones it
15450
- // and inserts the clones (whose directive attributes are stripped during
15451
- // clone compilation, so the clones expand normally). Expanding the source
15452
- // here would bake the expansion into every clone, and the clone's
15453
- // internals would then be compiled in the parent scope.
15454
- if (this.hasAttribute(StandardDirectiveName.V_IF) ||
15455
- this.hasAttribute(StandardDirectiveName.V_ELSE_IF) ||
15456
- this.hasAttribute(StandardDirectiveName.V_ELSE) ||
15457
- this.hasAttribute(StandardDirectiveName.V_FOR)) {
15458
- return;
15546
+ if (this._ichigoExpanded && !this.#mountRoot) {
15547
+ this._ichigoExpand();
15459
15548
  }
15549
+ }
15550
+ /**
15551
+ * Expands this element's template. Called by the application that owns the
15552
+ * element, right after it compiled it (VNode.expandComponents): the host
15553
+ * context is installed, the prop bindings have delivered their values and
15554
+ * the authored slot content has been compiled in the owner's scope, so the
15555
+ * capture below sees owner-compiled nodes and the component's own
15556
+ * application starts with its props already in hand.
15557
+ *
15558
+ * Also called by {@link connectedCallback} to rebuild the expansion after a
15559
+ * move.
15560
+ */
15561
+ _ichigoExpand() {
15460
15562
  // --- 0b. Reclaim prop values assigned before upgrade ---
15461
15563
  // A plain property set on the instance (e.g. by a binding delivered
15462
15564
  // while the element was detached or not yet upgraded) is an own data
@@ -15472,13 +15574,15 @@
15472
15574
  this[prop] = value;
15473
15575
  }
15474
15576
  }
15475
- // --- 0c. Guard: skip if template not yet loaded ---
15476
- // customElements.define() may run before loadComponent() completes (e.g. when
15477
- // dynamic imports are inlined by the bundler). The static placeholder element
15478
- // in the HTML will be removed and replaced by v-for once the template is ready,
15479
- // so it is safe to do nothing here.
15577
+ // --- 0c. Resolve the template ---
15578
+ // Having the <template> in the document by now is the application's
15579
+ // contract (see the class doc). Report the breach and leave the element
15580
+ // empty: rendering something plausible here would hide the bug at the
15581
+ // one moment it is still cheap to find.
15480
15582
  const templateEl = document.querySelector(ctor._template);
15481
15583
  if (!templateEl || !(templateEl instanceof HTMLTemplateElement)) {
15584
+ IchigoElement.#warnOncePerTag(this.tagName, `template '${ctor._template}' was not found in the document. `
15585
+ + 'Inject the <template> and call defineComponent() before mounting the application.');
15482
15586
  return;
15483
15587
  }
15484
15588
  // --- 1. Capture slot content (first connection only) ---
@@ -15793,6 +15897,23 @@
15793
15897
  #warn(message) {
15794
15898
  console.warn(`[ichigo] <${this.tagName.toLowerCase()}>: ${message}`);
15795
15899
  }
15900
+ /**
15901
+ * Tags already reported by {@link #warnOncePerTag}.
15902
+ */
15903
+ static #warnedTags = new Set();
15904
+ /**
15905
+ * Reports a per-component-type problem once. A missing template breaks
15906
+ * every instance, and a v-for over a hundred rows would otherwise print a
15907
+ * hundred identical lines and bury the first one.
15908
+ */
15909
+ static #warnOncePerTag(tagName, message) {
15910
+ const tag = tagName.toLowerCase();
15911
+ if (IchigoElement.#warnedTags.has(tag)) {
15912
+ return;
15913
+ }
15914
+ IchigoElement.#warnedTags.add(tag);
15915
+ console.warn(`[ichigo] <${tag}>: ${message}`);
15916
+ }
15796
15917
  }
15797
15918
 
15798
15919
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.