@mintjamsinc/ichigojs 0.1.74 → 0.1.76

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.
@@ -179,6 +179,8 @@
179
179
  StandardDirectiveName["V_TEXT"] = "v-text";
180
180
  /** Focus management directive. */
181
181
  StandardDirectiveName["V_FOCUS"] = "v-focus";
182
+ /** Template reference directive (Vue's `ref` / `$refs`). */
183
+ StandardDirectiveName["REF"] = "ref";
182
184
  })(StandardDirectiveName || (StandardDirectiveName = {}));
183
185
 
184
186
  // This file was generated. Do not modify manually!
@@ -9174,6 +9176,83 @@
9174
9176
  }
9175
9177
  }
9176
9178
 
9179
+ // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
9180
+ /**
9181
+ * The default Object.prototype.toString, used to detect plain objects that
9182
+ * have not overridden their string representation.
9183
+ */
9184
+ const objectToString = Object.prototype.toString;
9185
+ /**
9186
+ * Checks if the value is a non-null object.
9187
+ */
9188
+ function isObject(value) {
9189
+ return value !== null && typeof value === 'object';
9190
+ }
9191
+ /**
9192
+ * Checks if the value is a plain object (i.e. `[object Object]`).
9193
+ */
9194
+ function isPlainObject(value) {
9195
+ return objectToString.call(value) === '[object Object]';
9196
+ }
9197
+ /**
9198
+ * Converts a symbol to a readable string, leaving other values untouched.
9199
+ * Used when rendering Map keys and Set entries.
9200
+ */
9201
+ function stringifySymbol(value, index = '') {
9202
+ return typeof value === 'symbol' ? `Symbol(${value.description ?? index})` : value;
9203
+ }
9204
+ /**
9205
+ * JSON.stringify replacer that renders values JSON cannot represent natively
9206
+ * (Map, Set, Symbol, and non-plain objects) in a readable form.
9207
+ */
9208
+ function replacer(_key, value) {
9209
+ if (value instanceof Map) {
9210
+ return {
9211
+ [`Map(${value.size})`]: [...value.entries()].reduce((entries, [key, val], i) => {
9212
+ entries[`${stringifySymbol(key, i)} =>`] = val;
9213
+ return entries;
9214
+ }, {}),
9215
+ };
9216
+ }
9217
+ if (value instanceof Set) {
9218
+ return {
9219
+ [`Set(${value.size})`]: [...value.values()].map(v => stringifySymbol(v)),
9220
+ };
9221
+ }
9222
+ if (typeof value === 'symbol') {
9223
+ return stringifySymbol(value);
9224
+ }
9225
+ if (isObject(value) && !Array.isArray(value) && !isPlainObject(value)) {
9226
+ // Non-plain objects (Date, RegExp, DOM nodes, ...) render via their own toString
9227
+ return String(value);
9228
+ }
9229
+ return value;
9230
+ }
9231
+ /**
9232
+ * Converts a value to a string suitable for display in rendered output,
9233
+ * following Vue's `toDisplayString` semantics:
9234
+ * - strings are returned as-is
9235
+ * - null and undefined become an empty string
9236
+ * - arrays and plain objects (without a custom toString) are rendered as
9237
+ * pretty-printed JSON, with Map/Set/Symbol entries made readable
9238
+ * - everything else is converted with String()
9239
+ * @param value The value to convert.
9240
+ * @returns The display string for the value.
9241
+ */
9242
+ function toDisplayString(value) {
9243
+ if (typeof value === 'string') {
9244
+ return value;
9245
+ }
9246
+ if (value === null || value === undefined) {
9247
+ return '';
9248
+ }
9249
+ if (Array.isArray(value) ||
9250
+ (isObject(value) && (value.toString === objectToString || typeof value.toString !== 'function'))) {
9251
+ return JSON.stringify(value, replacer, 2);
9252
+ }
9253
+ return String(value);
9254
+ }
9255
+
9177
9256
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
9178
9257
  /**
9179
9258
  * List of standard JavaScript global objects that should be available in expressions.
@@ -9294,8 +9373,9 @@
9294
9373
  return GLOBAL_OBJECTS[name];
9295
9374
  return undefined;
9296
9375
  });
9297
- // Evaluate the expression and replace {{...}} in the text
9298
- result = result.replace(matches[i][0], String(evaluator.func(...values)));
9376
+ // Evaluate the expression and replace {{...}} in the text.
9377
+ // A function replacement keeps `$`-patterns in the value literal.
9378
+ result = result.replace(matches[i][0], () => toDisplayString(evaluator.func(...values)));
9299
9379
  });
9300
9380
  return result;
9301
9381
  };
@@ -13056,8 +13136,7 @@
13056
13136
  },
13057
13137
  applyToDOM() {
13058
13138
  const element = vNode.node;
13059
- const textContent = evaluator.evaluate();
13060
- element.textContent = textContent ?? '';
13139
+ element.textContent = toDisplayString(evaluator.evaluate());
13061
13140
  }
13062
13141
  };
13063
13142
  return updater;
@@ -13347,6 +13426,240 @@
13347
13426
  }
13348
13427
  }
13349
13428
 
13429
+ // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
13430
+ /**
13431
+ * Directive for registering template references (Vue's `ref` / `$refs`).
13432
+ *
13433
+ * Usage:
13434
+ * <input ref="search"> Static name. $refs.search === the element.
13435
+ * <my-card ref="card"></my-card> On a component: $refs.card === the host element.
13436
+ * <li v-for="i in items" ref="rows"> Inside v-for: $refs.rows === array of elements.
13437
+ * <input :ref="dynamicName"> Dynamic name from an expression (evaluated at mount).
13438
+ * <input :ref="el => collect(el)"> Function ref: called with the element on mount,
13439
+ * and with null on unmount (great for v-for / dynamic).
13440
+ *
13441
+ * Behavior notes:
13442
+ * - The reference is registered during the mount phase (before any `@mounted` hook fires) and
13443
+ * removed during the unmount phase, mirroring Vue's lifecycle for `$refs`.
13444
+ * - `$refs` is intentionally NON-reactive: registering or clearing a ref never schedules a render
13445
+ * and must not be used to drive reactive template output (this matches Vue).
13446
+ * - When the same `ref` name appears inside a `v-for` (i.e. one of the element's ancestors is a
13447
+ * `v-for` template), the entries are collected into an array, in registration order. Reused
13448
+ * v-for rows are not re-registered, so the array order is not guaranteed to match DOM order after
13449
+ * reordering — the same caveat Vue documents.
13450
+ * - Cleanup is identity-safe: a ref slot (or array entry) is only cleared when it still points at
13451
+ * THIS element, so a freshly mounted element that reused the name is never clobbered by the
13452
+ * teardown of the element it replaced.
13453
+ *
13454
+ * The directive is purely lifecycle-driven: it has no DOM updater and never templatizes the node.
13455
+ */
13456
+ class VRefDirective {
13457
+ /**
13458
+ * The virtual node to which this directive is applied.
13459
+ */
13460
+ #vNode;
13461
+ /**
13462
+ * The static ref name parsed from `ref="name"`. Undefined for the dynamic `:ref` form.
13463
+ */
13464
+ #staticName;
13465
+ /**
13466
+ * Expression evaluator for the dynamic `:ref` / `v-bind:ref` form. Undefined for static refs.
13467
+ */
13468
+ #evaluator;
13469
+ /**
13470
+ * The name under which this element was actually registered, retained so unmount removes the
13471
+ * exact same slot it created (the dynamic name is evaluated once at mount and may differ from
13472
+ * the source expression). Undefined when nothing was registered (function ref, or empty value).
13473
+ */
13474
+ #registeredName;
13475
+ /**
13476
+ * The function supplied by a function ref (`:ref="el => ..."`), retained so it can be invoked
13477
+ * with null on unmount. Undefined unless a function ref was used.
13478
+ */
13479
+ #functionRef;
13480
+ /**
13481
+ * Whether this ref is registered in array mode (collected because it lives inside a v-for).
13482
+ * Resolved lazily at mount time via {@link #isInsideForLoop}.
13483
+ */
13484
+ #asArray = false;
13485
+ /**
13486
+ * @param context The context for parsing the directive.
13487
+ */
13488
+ constructor(context) {
13489
+ this.#vNode = context.vNode;
13490
+ const attrName = context.attribute.name;
13491
+ const isDynamic = attrName !== StandardDirectiveName.REF; // ":ref" or "v-bind:ref"
13492
+ if (isDynamic) {
13493
+ // Dynamic / function ref: the value is an expression.
13494
+ const expression = context.attribute.value;
13495
+ if (expression && context.vNode.bindings) {
13496
+ this.#evaluator = ExpressionEvaluator.create(expression, context.vNode.bindings, context.vNode.vApplication.functionDependencies);
13497
+ }
13498
+ }
13499
+ else {
13500
+ // Static ref: the value is the literal name.
13501
+ const name = context.attribute.value.trim();
13502
+ this.#staticName = name.length > 0 ? name : undefined;
13503
+ }
13504
+ // Remove the directive attribute from the element so it does not linger in the DOM.
13505
+ this.#vNode.node.removeAttribute(attrName);
13506
+ }
13507
+ /**
13508
+ * @inheritdoc
13509
+ */
13510
+ get name() {
13511
+ return StandardDirectiveName.REF;
13512
+ }
13513
+ /**
13514
+ * @inheritdoc
13515
+ */
13516
+ get vNode() {
13517
+ return this.#vNode;
13518
+ }
13519
+ /**
13520
+ * @inheritdoc
13521
+ */
13522
+ get needsAnchor() {
13523
+ return false;
13524
+ }
13525
+ /**
13526
+ * @inheritdoc
13527
+ */
13528
+ get bindingsPreparer() {
13529
+ return undefined;
13530
+ }
13531
+ /**
13532
+ * @inheritdoc
13533
+ */
13534
+ get domUpdater() {
13535
+ // Refs are resolved once at mount; they are deliberately not reactive (see class docs).
13536
+ return undefined;
13537
+ }
13538
+ /**
13539
+ * @inheritdoc
13540
+ */
13541
+ get templatize() {
13542
+ return false;
13543
+ }
13544
+ /**
13545
+ * @inheritdoc
13546
+ */
13547
+ get dependentIdentifiers() {
13548
+ // This directive never drives reactive updates, so it contributes no dependencies.
13549
+ return [];
13550
+ }
13551
+ /**
13552
+ * @inheritdoc
13553
+ */
13554
+ get onMount() {
13555
+ // Register during the mount phase so the reference is available before any `@mounted`
13556
+ // hook runs (those fire a frame later via requestAnimationFrame).
13557
+ return () => this.#register();
13558
+ }
13559
+ /**
13560
+ * @inheritdoc
13561
+ */
13562
+ get onMounted() {
13563
+ return undefined;
13564
+ }
13565
+ /**
13566
+ * @inheritdoc
13567
+ */
13568
+ get onUpdate() {
13569
+ return undefined;
13570
+ }
13571
+ /**
13572
+ * @inheritdoc
13573
+ */
13574
+ get onUpdated() {
13575
+ return undefined;
13576
+ }
13577
+ /**
13578
+ * @inheritdoc
13579
+ */
13580
+ get onUnmount() {
13581
+ return undefined;
13582
+ }
13583
+ /**
13584
+ * @inheritdoc
13585
+ */
13586
+ get onUnmounted() {
13587
+ // Clear during the unmount phase. The element reference is still valid here.
13588
+ return () => this.#unregister();
13589
+ }
13590
+ /**
13591
+ * @inheritdoc
13592
+ */
13593
+ destroy() {
13594
+ // No additional cleanup beyond onUnmounted; kept for interface completeness.
13595
+ }
13596
+ /**
13597
+ * Registers this element under its resolved ref name (or invokes its function ref).
13598
+ */
13599
+ #register() {
13600
+ const element = this.#vNode.node;
13601
+ if (this.#evaluator) {
13602
+ // Dynamic / function ref.
13603
+ let value;
13604
+ try {
13605
+ value = this.#evaluator.evaluate();
13606
+ }
13607
+ catch {
13608
+ // Evaluation errors are already logged by ExpressionEvaluator; nothing to register.
13609
+ return;
13610
+ }
13611
+ if (typeof value === 'function') {
13612
+ this.#functionRef = value;
13613
+ this.#functionRef(element);
13614
+ return;
13615
+ }
13616
+ if (value === null || value === undefined || value === '') {
13617
+ return; // Nothing to register under an empty dynamic name.
13618
+ }
13619
+ this.#registeredName = String(value);
13620
+ }
13621
+ else if (this.#staticName) {
13622
+ this.#registeredName = this.#staticName;
13623
+ }
13624
+ if (!this.#registeredName) {
13625
+ return;
13626
+ }
13627
+ this.#asArray = this.#isInsideForLoop();
13628
+ this.#vNode.vApplication.registerRef(this.#registeredName, element, this.#asArray);
13629
+ }
13630
+ /**
13631
+ * Removes this element's reference (or invokes its function ref with null).
13632
+ */
13633
+ #unregister() {
13634
+ if (this.#functionRef) {
13635
+ this.#functionRef(null);
13636
+ this.#functionRef = undefined;
13637
+ return;
13638
+ }
13639
+ if (this.#registeredName) {
13640
+ this.#vNode.vApplication.unregisterRef(this.#registeredName, this.#vNode.node);
13641
+ this.#registeredName = undefined;
13642
+ }
13643
+ }
13644
+ /**
13645
+ * Determines whether this element is rendered inside a `v-for` by walking up the VNode tree.
13646
+ * A `v-for` multiplies its template into N rows, so a static ref name on (or within) a row is
13647
+ * shared by N elements and must be collected into an array — matching Vue's documented
13648
+ * "ref inside v-for yields an array" behavior. The walk inspects ancestors only: the directive
13649
+ * lives on a cloned row (or a descendant of one), and the clone's parent chain leads back to the
13650
+ * `v-for` template node, which still carries the `v-for` directive.
13651
+ */
13652
+ #isInsideForLoop() {
13653
+ for (let ancestor = this.#vNode.parentVNode; ancestor; ancestor = ancestor.parentVNode) {
13654
+ const hasFor = ancestor.directiveManager?.directives?.some(d => d.name === StandardDirectiveName.V_FOR);
13655
+ if (hasFor) {
13656
+ return true;
13657
+ }
13658
+ }
13659
+ return false;
13660
+ }
13661
+ }
13662
+
13350
13663
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
13351
13664
  /**
13352
13665
  * The directive parser for standard directives.
@@ -13391,15 +13704,33 @@
13391
13704
  context.attribute.name === StandardDirectiveName.V_TEXT ||
13392
13705
  // v-focus, v-focus.<modifier>
13393
13706
  context.attribute.name === StandardDirectiveName.V_FOCUS ||
13394
- context.attribute.name.startsWith(StandardDirectiveName.V_FOCUS + ".")) {
13707
+ context.attribute.name.startsWith(StandardDirectiveName.V_FOCUS + ".") ||
13708
+ // ref, :ref, v-bind:ref
13709
+ this.#isRefAttribute(context.attribute.name)) {
13395
13710
  return true;
13396
13711
  }
13397
13712
  return false;
13398
13713
  }
13714
+ /**
13715
+ * Determines whether the attribute is a template reference (`ref`, `:ref`, or `v-bind:ref`).
13716
+ * The dynamic forms also match the generic v-bind shorthand, so {@link parse} dispatches them
13717
+ * to VRefDirective explicitly before the v-bind branch is reached.
13718
+ */
13719
+ #isRefAttribute(name) {
13720
+ return name === StandardDirectiveName.REF ||
13721
+ name === ":" + StandardDirectiveName.REF ||
13722
+ name === StandardDirectiveName.V_BIND + ":" + StandardDirectiveName.REF;
13723
+ }
13399
13724
  /**
13400
13725
  * @inheritdoc
13401
13726
  */
13402
13727
  parse(context) {
13728
+ // ref, :ref, v-bind:ref
13729
+ // Checked before the v-bind branch below, since ":ref" / "v-bind:ref" also match the
13730
+ // generic v-bind shorthand but must be handled as template references, not attribute binds.
13731
+ if (this.#isRefAttribute(context.attribute.name)) {
13732
+ return new VRefDirective(context);
13733
+ }
13403
13734
  if (context.attribute.name === StandardDirectiveName.V_IF) {
13404
13735
  return new VIfDirective(context);
13405
13736
  }
@@ -13836,6 +14167,17 @@
13836
14167
  * The watcher manager for this application.
13837
14168
  */
13838
14169
  #watcher;
14170
+ /**
14171
+ * Template references registered by the `ref` directive, exposed to data(), methods, and
14172
+ * expressions as `$refs` (Vue's `$refs`). Each entry is either a single Element or, for a
14173
+ * `ref` used inside `v-for`, an array of Elements.
14174
+ *
14175
+ * This object is intentionally marked raw (non-reactive): mutating it when an element mounts or
14176
+ * unmounts must never schedule a render, and it must not be used to drive reactive template
14177
+ * output — matching Vue's semantics for `$refs`. It is held as a direct field (in addition to
14178
+ * being injected as the `$refs` binding) so registration is a plain property write.
14179
+ */
14180
+ #refs = {};
13839
14181
  /**
13840
14182
  * Flag to indicate if an update is already scheduled.
13841
14183
  */
@@ -13936,6 +14278,13 @@
13936
14278
  get functionDependencies() {
13937
14279
  return this.#functionDependencies;
13938
14280
  }
14281
+ /**
14282
+ * Gets the template references registered via the `ref` directive (Vue's `$refs`). The same
14283
+ * object is exposed to data(), methods, and expressions as `$refs`. Non-reactive by design.
14284
+ */
14285
+ get refs() {
14286
+ return this.#refs;
14287
+ }
13939
14288
  /**
13940
14289
  * Mounts the application.
13941
14290
  * @param target The CSS selector string or HTMLElement to mount the application to.
@@ -14064,6 +14413,56 @@
14064
14413
  }
14065
14414
  return identifiers;
14066
14415
  }
14416
+ /**
14417
+ * Registers a template reference under the given name. Called by the `ref` directive during the
14418
+ * mount phase. When {@param asArray} is true (the element lives inside a `v-for`), references
14419
+ * accumulate into an array in registration order; otherwise the name holds a single element.
14420
+ *
14421
+ * Mutates the raw (non-reactive) `$refs` object directly, so this never schedules a render.
14422
+ *
14423
+ * @param name The ref name.
14424
+ * @param element The element (or component host element) to register.
14425
+ * @param asArray Whether to collect into an array (refs used inside a v-for).
14426
+ */
14427
+ registerRef(name, element, asArray) {
14428
+ if (asArray) {
14429
+ const current = this.#refs[name];
14430
+ if (Array.isArray(current)) {
14431
+ if (!current.includes(element)) {
14432
+ current.push(element);
14433
+ }
14434
+ }
14435
+ else {
14436
+ this.#refs[name] = [element];
14437
+ }
14438
+ return;
14439
+ }
14440
+ this.#refs[name] = element;
14441
+ }
14442
+ /**
14443
+ * Removes a template reference registered under the given name. Called by the `ref` directive
14444
+ * during the unmount phase. The removal is identity-safe: an entry is only cleared when it still
14445
+ * points at {@param element}, so a newly mounted element that reused the name is never clobbered
14446
+ * by the teardown of the element it replaced.
14447
+ *
14448
+ * @param name The ref name.
14449
+ * @param element The element whose registration should be removed.
14450
+ */
14451
+ unregisterRef(name, element) {
14452
+ const current = this.#refs[name];
14453
+ if (Array.isArray(current)) {
14454
+ const index = current.indexOf(element);
14455
+ if (index !== -1) {
14456
+ current.splice(index, 1);
14457
+ }
14458
+ if (current.length === 0) {
14459
+ delete this.#refs[name];
14460
+ }
14461
+ }
14462
+ else if (current === element) {
14463
+ delete this.#refs[name];
14464
+ }
14465
+ }
14067
14466
  /**
14068
14467
  * Initializes bindings from data, computed properties, and methods.
14069
14468
  * @returns The initialized bindings object.
@@ -14084,6 +14483,10 @@
14084
14483
  this.#bindings.set('$nextTick', (callback) => this.#nextTick(callback));
14085
14484
  this.#bindings.set('$markRaw', (obj) => ReactiveProxy.markRaw(obj));
14086
14485
  this.#bindings.set('$emit', (name, detail, options) => this.#emit(name, detail, options));
14486
+ // Inject the (non-reactive) $refs container. Marking it raw prevents VBindings from wrapping
14487
+ // it in a reactive proxy, so populating it from the ref directive never schedules a render.
14488
+ ReactiveProxy.markRaw(this.#refs);
14489
+ this.#bindings.set('$refs', this.#refs);
14087
14490
  // Add methods
14088
14491
  if (this.#options.methods) {
14089
14492
  for (const [key, method] of Object.entries(this.#options.methods)) {
@@ -14720,6 +15123,7 @@
14720
15123
  exports.VComponentRegistry = VComponentRegistry;
14721
15124
  exports.VDOM = VDOM;
14722
15125
  exports.defineComponent = defineComponent;
15126
+ exports.toDisplayString = toDisplayString;
14723
15127
 
14724
15128
  }));
14725
15129
  //# sourceMappingURL=ichigo.umd.js.map