@mintjamsinc/ichigojs 0.1.59 → 0.1.60

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/dist/ichigo.cjs CHANGED
@@ -177,6 +177,8 @@
177
177
  StandardDirectiveName["V_HTML"] = "v-html";
178
178
  /** Text content directive. */
179
179
  StandardDirectiveName["V_TEXT"] = "v-text";
180
+ /** Focus management directive. */
181
+ StandardDirectiveName["V_FOCUS"] = "v-focus";
180
182
  })(StandardDirectiveName || (StandardDirectiveName = {}));
181
183
 
182
184
  // This file was generated. Do not modify manually!
@@ -12385,6 +12387,235 @@
12385
12387
  }
12386
12388
  }
12387
12389
 
12390
+ // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
12391
+ /**
12392
+ * Directive for managing focus on form elements.
12393
+ *
12394
+ * Usage:
12395
+ * <input v-focus> Focus once on mount.
12396
+ * <input v-focus.select> Focus + select all on mount.
12397
+ * <input v-focus="isOpen"> Focus when expression transitions from falsy to truthy.
12398
+ * <input v-focus.select="isOpen"> Conditional focus + select all.
12399
+ * <input v-focus.cursor-end="isOpen"> Conditional focus + place caret at end.
12400
+ *
12401
+ * Behavior notes:
12402
+ * - Without an expression, the element is focused exactly once after mount.
12403
+ * - With an expression, focus fires only on the falsy -> truthy edge,
12404
+ * so the user is not repeatedly re-focused on every reactive update.
12405
+ * - If the value is already truthy on mount, the element is focused.
12406
+ * - Focus is deferred via requestAnimationFrame so that elements which
12407
+ * become visible just before this directive runs (e.g. inside v-if /
12408
+ * display:none containers) can still receive focus reliably.
12409
+ */
12410
+ class VFocusDirective {
12411
+ /**
12412
+ * The virtual node to which this directive is applied.
12413
+ */
12414
+ #vNode;
12415
+ /**
12416
+ * Optional expression evaluator. When absent, the directive focuses once on mount.
12417
+ */
12418
+ #evaluator;
12419
+ /**
12420
+ * Modifiers extracted from the directive name (e.g. "select", "cursor-end").
12421
+ */
12422
+ #modifiers = new Set();
12423
+ /**
12424
+ * Last evaluated boolean value, used for falsy -> truthy edge detection.
12425
+ */
12426
+ #previousValue = false;
12427
+ /**
12428
+ * @param context The context for parsing the directive.
12429
+ */
12430
+ constructor(context) {
12431
+ this.#vNode = context.vNode;
12432
+ // Extract modifiers from the directive name
12433
+ // e.g., "v-focus.select" -> modifiers = {"select"}
12434
+ const attrName = context.attribute.name;
12435
+ if (attrName.startsWith(StandardDirectiveName.V_FOCUS + '.')) {
12436
+ const parts = attrName.split('.');
12437
+ parts.slice(1).forEach(mod => this.#modifiers.add(mod));
12438
+ }
12439
+ // Parse the expression and create the evaluator (optional)
12440
+ const expression = context.attribute.value;
12441
+ if (expression) {
12442
+ if (!context.vNode.bindings) {
12443
+ throw new Error('VFocusDirective requires bindings when an expression is provided');
12444
+ }
12445
+ this.#evaluator = ExpressionEvaluator.create(expression, context.vNode.bindings, context.vNode.vApplication.functionDependencies);
12446
+ }
12447
+ // Remove the directive attribute from the element
12448
+ this.#vNode.node.removeAttribute(context.attribute.name);
12449
+ }
12450
+ /**
12451
+ * @inheritdoc
12452
+ */
12453
+ get name() {
12454
+ return StandardDirectiveName.V_FOCUS;
12455
+ }
12456
+ /**
12457
+ * @inheritdoc
12458
+ */
12459
+ get vNode() {
12460
+ return this.#vNode;
12461
+ }
12462
+ /**
12463
+ * @inheritdoc
12464
+ */
12465
+ get needsAnchor() {
12466
+ return false;
12467
+ }
12468
+ /**
12469
+ * @inheritdoc
12470
+ */
12471
+ get bindingsPreparer() {
12472
+ return undefined;
12473
+ }
12474
+ /**
12475
+ * @inheritdoc
12476
+ */
12477
+ get domUpdater() {
12478
+ // Without an expression, there is nothing reactive to track.
12479
+ if (!this.#evaluator) {
12480
+ return undefined;
12481
+ }
12482
+ const identifiers = this.#evaluator.dependentIdentifiers;
12483
+ const evaluator = this.#evaluator;
12484
+ const focusElement = () => this.#focus();
12485
+ const updater = {
12486
+ get dependentIdentifiers() {
12487
+ return identifiers;
12488
+ },
12489
+ applyToDOM: () => {
12490
+ const value = evaluator.evaluateAsBoolean();
12491
+ const previous = this.#previousValue;
12492
+ this.#previousValue = value;
12493
+ // Edge: falsy -> truthy
12494
+ if (!previous && value) {
12495
+ focusElement();
12496
+ }
12497
+ }
12498
+ };
12499
+ return updater;
12500
+ }
12501
+ /**
12502
+ * @inheritdoc
12503
+ */
12504
+ get templatize() {
12505
+ return false;
12506
+ }
12507
+ /**
12508
+ * @inheritdoc
12509
+ */
12510
+ get dependentIdentifiers() {
12511
+ return this.#evaluator?.dependentIdentifiers ?? [];
12512
+ }
12513
+ /**
12514
+ * @inheritdoc
12515
+ */
12516
+ get onMount() {
12517
+ return undefined;
12518
+ }
12519
+ /**
12520
+ * @inheritdoc
12521
+ */
12522
+ get onMounted() {
12523
+ return () => {
12524
+ if (!this.#evaluator) {
12525
+ // Unconditional: focus once on mount.
12526
+ this.#focus();
12527
+ return;
12528
+ }
12529
+ // Conditional: seed previous value and focus if already truthy on mount.
12530
+ const value = this.#evaluator.evaluateAsBoolean();
12531
+ this.#previousValue = value;
12532
+ if (value) {
12533
+ this.#focus();
12534
+ }
12535
+ };
12536
+ }
12537
+ /**
12538
+ * @inheritdoc
12539
+ */
12540
+ get onUpdate() {
12541
+ return undefined;
12542
+ }
12543
+ /**
12544
+ * @inheritdoc
12545
+ */
12546
+ get onUpdated() {
12547
+ return undefined;
12548
+ }
12549
+ /**
12550
+ * @inheritdoc
12551
+ */
12552
+ get onUnmount() {
12553
+ return undefined;
12554
+ }
12555
+ /**
12556
+ * @inheritdoc
12557
+ */
12558
+ get onUnmounted() {
12559
+ return undefined;
12560
+ }
12561
+ /**
12562
+ * @inheritdoc
12563
+ */
12564
+ destroy() {
12565
+ // No specific cleanup needed for this directive.
12566
+ }
12567
+ /**
12568
+ * Focuses the element, applying any modifier-driven post-focus behavior.
12569
+ * Deferred via requestAnimationFrame so that elements transitioning out
12570
+ * of display:none (e.g. inside v-if) can receive focus reliably.
12571
+ */
12572
+ #focus() {
12573
+ const element = this.#vNode.node;
12574
+ if (!element || typeof element.focus !== 'function') {
12575
+ return;
12576
+ }
12577
+ const applyModifiers = () => this.#applyModifiers();
12578
+ requestAnimationFrame(() => {
12579
+ // The element may have been unmounted between scheduling and execution.
12580
+ if (!element.isConnected) {
12581
+ return;
12582
+ }
12583
+ element.focus();
12584
+ applyModifiers();
12585
+ });
12586
+ }
12587
+ /**
12588
+ * Applies modifier-driven behavior after focus.
12589
+ */
12590
+ #applyModifiers() {
12591
+ const element = this.#vNode.node;
12592
+ if (this.#modifiers.has('select')) {
12593
+ const inputEl = element;
12594
+ if (typeof inputEl.select === 'function') {
12595
+ try {
12596
+ inputEl.select();
12597
+ }
12598
+ catch {
12599
+ // Some input types (e.g. number) reject select(); ignore.
12600
+ }
12601
+ }
12602
+ return;
12603
+ }
12604
+ if (this.#modifiers.has('cursor-end')) {
12605
+ const inputEl = element;
12606
+ if (typeof inputEl.setSelectionRange === 'function') {
12607
+ const len = (inputEl.value ?? '').length;
12608
+ try {
12609
+ inputEl.setSelectionRange(len, len);
12610
+ }
12611
+ catch {
12612
+ // Some input types (e.g. number) do not support selection ranges; ignore.
12613
+ }
12614
+ }
12615
+ }
12616
+ }
12617
+ }
12618
+
12388
12619
  // Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
12389
12620
  /**
12390
12621
  * The directive parser for standard directives.
@@ -12426,7 +12657,10 @@
12426
12657
  // v-html
12427
12658
  context.attribute.name === StandardDirectiveName.V_HTML ||
12428
12659
  // v-text
12429
- context.attribute.name === StandardDirectiveName.V_TEXT) {
12660
+ context.attribute.name === StandardDirectiveName.V_TEXT ||
12661
+ // v-focus, v-focus.<modifier>
12662
+ context.attribute.name === StandardDirectiveName.V_FOCUS ||
12663
+ context.attribute.name.startsWith(StandardDirectiveName.V_FOCUS + ".")) {
12430
12664
  return true;
12431
12665
  }
12432
12666
  return false;
@@ -12490,6 +12724,11 @@
12490
12724
  if (context.attribute.name === StandardDirectiveName.V_TEXT) {
12491
12725
  return new VTextDirective(context);
12492
12726
  }
12727
+ // v-focus, v-focus.<modifier>
12728
+ if (context.attribute.name === StandardDirectiveName.V_FOCUS ||
12729
+ context.attribute.name.startsWith(StandardDirectiveName.V_FOCUS + ".")) {
12730
+ return new VFocusDirective(context);
12731
+ }
12493
12732
  throw new Error(`The attribute "${context.attribute.name}" cannot be parsed by ${this.name}.`);
12494
12733
  }
12495
12734
  }