@mintjamsinc/ichigojs 0.1.59 → 0.1.61
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 +309 -4
- package/dist/ichigo.cjs.map +1 -1
- package/dist/ichigo.esm.js +309 -4
- 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 +309 -4
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/types/ichigo/VApplicationOptions.d.ts +15 -2
- package/dist/types/ichigo/VBindings.d.ts +8 -0
- package/dist/types/ichigo/directives/StandardDirectiveName.d.ts +3 -1
- package/dist/types/ichigo/directives/VFocusDirective.d.ts +87 -0
- package/package.json +1 -1
package/dist/ichigo.esm.js
CHANGED
|
@@ -171,6 +171,8 @@ var StandardDirectiveName;
|
|
|
171
171
|
StandardDirectiveName["V_HTML"] = "v-html";
|
|
172
172
|
/** Text content directive. */
|
|
173
173
|
StandardDirectiveName["V_TEXT"] = "v-text";
|
|
174
|
+
/** Focus management directive. */
|
|
175
|
+
StandardDirectiveName["V_FOCUS"] = "v-focus";
|
|
174
176
|
})(StandardDirectiveName || (StandardDirectiveName = {}));
|
|
175
177
|
|
|
176
178
|
// This file was generated. Do not modify manually!
|
|
@@ -8235,6 +8237,13 @@ class VBindings {
|
|
|
8235
8237
|
* their own alias (e.g. "file" -> "files[0]") without overwriting each other.
|
|
8236
8238
|
*/
|
|
8237
8239
|
#localAliases = new Map();
|
|
8240
|
+
/**
|
|
8241
|
+
* Setters for writable computed properties. When a key registered here is assigned
|
|
8242
|
+
* through the bindings proxy (e.g. via v-model or `bindings.set`), the setter is invoked
|
|
8243
|
+
* instead of writing directly to the local store. The cached value of the computed property
|
|
8244
|
+
* is updated by the recompute cycle through `setSilent`, which bypasses this routing.
|
|
8245
|
+
*/
|
|
8246
|
+
#writableComputeds = new Map();
|
|
8238
8247
|
/**
|
|
8239
8248
|
* Creates a new instance of VBindings.
|
|
8240
8249
|
* @param parent The parent bindings, if any.
|
|
@@ -8254,6 +8263,14 @@ class VBindings {
|
|
|
8254
8263
|
return this.#parent?.raw[key];
|
|
8255
8264
|
},
|
|
8256
8265
|
set: (obj, key, value) => {
|
|
8266
|
+
// If this key is a writable computed, route the assignment through its setter.
|
|
8267
|
+
// `setSilent` (used to update the cached value during recompute) sets `suppressOnChange`,
|
|
8268
|
+
// which bypasses this routing so the cached value can be written directly.
|
|
8269
|
+
if (!this.#suppressOnChange && this.#writableComputeds.has(key)) {
|
|
8270
|
+
const setter = this.#writableComputeds.get(key);
|
|
8271
|
+
setter(value);
|
|
8272
|
+
return true;
|
|
8273
|
+
}
|
|
8257
8274
|
let target = obj;
|
|
8258
8275
|
if (!Reflect.has(target, key)) {
|
|
8259
8276
|
for (let parent = this.#parent; parent; parent = parent.#parent) {
|
|
@@ -8440,6 +8457,16 @@ class VBindings {
|
|
|
8440
8457
|
this.#suppressOnChange = false;
|
|
8441
8458
|
}
|
|
8442
8459
|
}
|
|
8460
|
+
/**
|
|
8461
|
+
* Registers a setter for a writable computed property. When the given key is assigned
|
|
8462
|
+
* through the bindings proxy, the setter will be invoked instead of writing directly to
|
|
8463
|
+
* the local store.
|
|
8464
|
+
* @param key The computed property name.
|
|
8465
|
+
* @param setter The setter function to invoke on assignment.
|
|
8466
|
+
*/
|
|
8467
|
+
registerWritableComputed(key, setter) {
|
|
8468
|
+
this.#writableComputeds.set(key, setter);
|
|
8469
|
+
}
|
|
8443
8470
|
/**
|
|
8444
8471
|
* Manually adds an identifier to the set of changed identifiers.
|
|
8445
8472
|
* This is useful for computed properties that need to mark themselves as changed
|
|
@@ -12379,6 +12406,235 @@ class VTextDirective {
|
|
|
12379
12406
|
}
|
|
12380
12407
|
}
|
|
12381
12408
|
|
|
12409
|
+
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
|
12410
|
+
/**
|
|
12411
|
+
* Directive for managing focus on form elements.
|
|
12412
|
+
*
|
|
12413
|
+
* Usage:
|
|
12414
|
+
* <input v-focus> Focus once on mount.
|
|
12415
|
+
* <input v-focus.select> Focus + select all on mount.
|
|
12416
|
+
* <input v-focus="isOpen"> Focus when expression transitions from falsy to truthy.
|
|
12417
|
+
* <input v-focus.select="isOpen"> Conditional focus + select all.
|
|
12418
|
+
* <input v-focus.cursor-end="isOpen"> Conditional focus + place caret at end.
|
|
12419
|
+
*
|
|
12420
|
+
* Behavior notes:
|
|
12421
|
+
* - Without an expression, the element is focused exactly once after mount.
|
|
12422
|
+
* - With an expression, focus fires only on the falsy -> truthy edge,
|
|
12423
|
+
* so the user is not repeatedly re-focused on every reactive update.
|
|
12424
|
+
* - If the value is already truthy on mount, the element is focused.
|
|
12425
|
+
* - Focus is deferred via requestAnimationFrame so that elements which
|
|
12426
|
+
* become visible just before this directive runs (e.g. inside v-if /
|
|
12427
|
+
* display:none containers) can still receive focus reliably.
|
|
12428
|
+
*/
|
|
12429
|
+
class VFocusDirective {
|
|
12430
|
+
/**
|
|
12431
|
+
* The virtual node to which this directive is applied.
|
|
12432
|
+
*/
|
|
12433
|
+
#vNode;
|
|
12434
|
+
/**
|
|
12435
|
+
* Optional expression evaluator. When absent, the directive focuses once on mount.
|
|
12436
|
+
*/
|
|
12437
|
+
#evaluator;
|
|
12438
|
+
/**
|
|
12439
|
+
* Modifiers extracted from the directive name (e.g. "select", "cursor-end").
|
|
12440
|
+
*/
|
|
12441
|
+
#modifiers = new Set();
|
|
12442
|
+
/**
|
|
12443
|
+
* Last evaluated boolean value, used for falsy -> truthy edge detection.
|
|
12444
|
+
*/
|
|
12445
|
+
#previousValue = false;
|
|
12446
|
+
/**
|
|
12447
|
+
* @param context The context for parsing the directive.
|
|
12448
|
+
*/
|
|
12449
|
+
constructor(context) {
|
|
12450
|
+
this.#vNode = context.vNode;
|
|
12451
|
+
// Extract modifiers from the directive name
|
|
12452
|
+
// e.g., "v-focus.select" -> modifiers = {"select"}
|
|
12453
|
+
const attrName = context.attribute.name;
|
|
12454
|
+
if (attrName.startsWith(StandardDirectiveName.V_FOCUS + '.')) {
|
|
12455
|
+
const parts = attrName.split('.');
|
|
12456
|
+
parts.slice(1).forEach(mod => this.#modifiers.add(mod));
|
|
12457
|
+
}
|
|
12458
|
+
// Parse the expression and create the evaluator (optional)
|
|
12459
|
+
const expression = context.attribute.value;
|
|
12460
|
+
if (expression) {
|
|
12461
|
+
if (!context.vNode.bindings) {
|
|
12462
|
+
throw new Error('VFocusDirective requires bindings when an expression is provided');
|
|
12463
|
+
}
|
|
12464
|
+
this.#evaluator = ExpressionEvaluator.create(expression, context.vNode.bindings, context.vNode.vApplication.functionDependencies);
|
|
12465
|
+
}
|
|
12466
|
+
// Remove the directive attribute from the element
|
|
12467
|
+
this.#vNode.node.removeAttribute(context.attribute.name);
|
|
12468
|
+
}
|
|
12469
|
+
/**
|
|
12470
|
+
* @inheritdoc
|
|
12471
|
+
*/
|
|
12472
|
+
get name() {
|
|
12473
|
+
return StandardDirectiveName.V_FOCUS;
|
|
12474
|
+
}
|
|
12475
|
+
/**
|
|
12476
|
+
* @inheritdoc
|
|
12477
|
+
*/
|
|
12478
|
+
get vNode() {
|
|
12479
|
+
return this.#vNode;
|
|
12480
|
+
}
|
|
12481
|
+
/**
|
|
12482
|
+
* @inheritdoc
|
|
12483
|
+
*/
|
|
12484
|
+
get needsAnchor() {
|
|
12485
|
+
return false;
|
|
12486
|
+
}
|
|
12487
|
+
/**
|
|
12488
|
+
* @inheritdoc
|
|
12489
|
+
*/
|
|
12490
|
+
get bindingsPreparer() {
|
|
12491
|
+
return undefined;
|
|
12492
|
+
}
|
|
12493
|
+
/**
|
|
12494
|
+
* @inheritdoc
|
|
12495
|
+
*/
|
|
12496
|
+
get domUpdater() {
|
|
12497
|
+
// Without an expression, there is nothing reactive to track.
|
|
12498
|
+
if (!this.#evaluator) {
|
|
12499
|
+
return undefined;
|
|
12500
|
+
}
|
|
12501
|
+
const identifiers = this.#evaluator.dependentIdentifiers;
|
|
12502
|
+
const evaluator = this.#evaluator;
|
|
12503
|
+
const focusElement = () => this.#focus();
|
|
12504
|
+
const updater = {
|
|
12505
|
+
get dependentIdentifiers() {
|
|
12506
|
+
return identifiers;
|
|
12507
|
+
},
|
|
12508
|
+
applyToDOM: () => {
|
|
12509
|
+
const value = evaluator.evaluateAsBoolean();
|
|
12510
|
+
const previous = this.#previousValue;
|
|
12511
|
+
this.#previousValue = value;
|
|
12512
|
+
// Edge: falsy -> truthy
|
|
12513
|
+
if (!previous && value) {
|
|
12514
|
+
focusElement();
|
|
12515
|
+
}
|
|
12516
|
+
}
|
|
12517
|
+
};
|
|
12518
|
+
return updater;
|
|
12519
|
+
}
|
|
12520
|
+
/**
|
|
12521
|
+
* @inheritdoc
|
|
12522
|
+
*/
|
|
12523
|
+
get templatize() {
|
|
12524
|
+
return false;
|
|
12525
|
+
}
|
|
12526
|
+
/**
|
|
12527
|
+
* @inheritdoc
|
|
12528
|
+
*/
|
|
12529
|
+
get dependentIdentifiers() {
|
|
12530
|
+
return this.#evaluator?.dependentIdentifiers ?? [];
|
|
12531
|
+
}
|
|
12532
|
+
/**
|
|
12533
|
+
* @inheritdoc
|
|
12534
|
+
*/
|
|
12535
|
+
get onMount() {
|
|
12536
|
+
return undefined;
|
|
12537
|
+
}
|
|
12538
|
+
/**
|
|
12539
|
+
* @inheritdoc
|
|
12540
|
+
*/
|
|
12541
|
+
get onMounted() {
|
|
12542
|
+
return () => {
|
|
12543
|
+
if (!this.#evaluator) {
|
|
12544
|
+
// Unconditional: focus once on mount.
|
|
12545
|
+
this.#focus();
|
|
12546
|
+
return;
|
|
12547
|
+
}
|
|
12548
|
+
// Conditional: seed previous value and focus if already truthy on mount.
|
|
12549
|
+
const value = this.#evaluator.evaluateAsBoolean();
|
|
12550
|
+
this.#previousValue = value;
|
|
12551
|
+
if (value) {
|
|
12552
|
+
this.#focus();
|
|
12553
|
+
}
|
|
12554
|
+
};
|
|
12555
|
+
}
|
|
12556
|
+
/**
|
|
12557
|
+
* @inheritdoc
|
|
12558
|
+
*/
|
|
12559
|
+
get onUpdate() {
|
|
12560
|
+
return undefined;
|
|
12561
|
+
}
|
|
12562
|
+
/**
|
|
12563
|
+
* @inheritdoc
|
|
12564
|
+
*/
|
|
12565
|
+
get onUpdated() {
|
|
12566
|
+
return undefined;
|
|
12567
|
+
}
|
|
12568
|
+
/**
|
|
12569
|
+
* @inheritdoc
|
|
12570
|
+
*/
|
|
12571
|
+
get onUnmount() {
|
|
12572
|
+
return undefined;
|
|
12573
|
+
}
|
|
12574
|
+
/**
|
|
12575
|
+
* @inheritdoc
|
|
12576
|
+
*/
|
|
12577
|
+
get onUnmounted() {
|
|
12578
|
+
return undefined;
|
|
12579
|
+
}
|
|
12580
|
+
/**
|
|
12581
|
+
* @inheritdoc
|
|
12582
|
+
*/
|
|
12583
|
+
destroy() {
|
|
12584
|
+
// No specific cleanup needed for this directive.
|
|
12585
|
+
}
|
|
12586
|
+
/**
|
|
12587
|
+
* Focuses the element, applying any modifier-driven post-focus behavior.
|
|
12588
|
+
* Deferred via requestAnimationFrame so that elements transitioning out
|
|
12589
|
+
* of display:none (e.g. inside v-if) can receive focus reliably.
|
|
12590
|
+
*/
|
|
12591
|
+
#focus() {
|
|
12592
|
+
const element = this.#vNode.node;
|
|
12593
|
+
if (!element || typeof element.focus !== 'function') {
|
|
12594
|
+
return;
|
|
12595
|
+
}
|
|
12596
|
+
const applyModifiers = () => this.#applyModifiers();
|
|
12597
|
+
requestAnimationFrame(() => {
|
|
12598
|
+
// The element may have been unmounted between scheduling and execution.
|
|
12599
|
+
if (!element.isConnected) {
|
|
12600
|
+
return;
|
|
12601
|
+
}
|
|
12602
|
+
element.focus();
|
|
12603
|
+
applyModifiers();
|
|
12604
|
+
});
|
|
12605
|
+
}
|
|
12606
|
+
/**
|
|
12607
|
+
* Applies modifier-driven behavior after focus.
|
|
12608
|
+
*/
|
|
12609
|
+
#applyModifiers() {
|
|
12610
|
+
const element = this.#vNode.node;
|
|
12611
|
+
if (this.#modifiers.has('select')) {
|
|
12612
|
+
const inputEl = element;
|
|
12613
|
+
if (typeof inputEl.select === 'function') {
|
|
12614
|
+
try {
|
|
12615
|
+
inputEl.select();
|
|
12616
|
+
}
|
|
12617
|
+
catch {
|
|
12618
|
+
// Some input types (e.g. number) reject select(); ignore.
|
|
12619
|
+
}
|
|
12620
|
+
}
|
|
12621
|
+
return;
|
|
12622
|
+
}
|
|
12623
|
+
if (this.#modifiers.has('cursor-end')) {
|
|
12624
|
+
const inputEl = element;
|
|
12625
|
+
if (typeof inputEl.setSelectionRange === 'function') {
|
|
12626
|
+
const len = (inputEl.value ?? '').length;
|
|
12627
|
+
try {
|
|
12628
|
+
inputEl.setSelectionRange(len, len);
|
|
12629
|
+
}
|
|
12630
|
+
catch {
|
|
12631
|
+
// Some input types (e.g. number) do not support selection ranges; ignore.
|
|
12632
|
+
}
|
|
12633
|
+
}
|
|
12634
|
+
}
|
|
12635
|
+
}
|
|
12636
|
+
}
|
|
12637
|
+
|
|
12382
12638
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
|
12383
12639
|
/**
|
|
12384
12640
|
* The directive parser for standard directives.
|
|
@@ -12420,7 +12676,10 @@ class VStandardDirectiveParser {
|
|
|
12420
12676
|
// v-html
|
|
12421
12677
|
context.attribute.name === StandardDirectiveName.V_HTML ||
|
|
12422
12678
|
// v-text
|
|
12423
|
-
context.attribute.name === StandardDirectiveName.V_TEXT
|
|
12679
|
+
context.attribute.name === StandardDirectiveName.V_TEXT ||
|
|
12680
|
+
// v-focus, v-focus.<modifier>
|
|
12681
|
+
context.attribute.name === StandardDirectiveName.V_FOCUS ||
|
|
12682
|
+
context.attribute.name.startsWith(StandardDirectiveName.V_FOCUS + ".")) {
|
|
12424
12683
|
return true;
|
|
12425
12684
|
}
|
|
12426
12685
|
return false;
|
|
@@ -12484,6 +12743,11 @@ class VStandardDirectiveParser {
|
|
|
12484
12743
|
if (context.attribute.name === StandardDirectiveName.V_TEXT) {
|
|
12485
12744
|
return new VTextDirective(context);
|
|
12486
12745
|
}
|
|
12746
|
+
// v-focus, v-focus.<modifier>
|
|
12747
|
+
if (context.attribute.name === StandardDirectiveName.V_FOCUS ||
|
|
12748
|
+
context.attribute.name.startsWith(StandardDirectiveName.V_FOCUS + ".")) {
|
|
12749
|
+
return new VFocusDirective(context);
|
|
12750
|
+
}
|
|
12487
12751
|
throw new Error(`The attribute "${context.attribute.name}" cannot be parsed by ${this.name}.`);
|
|
12488
12752
|
}
|
|
12489
12753
|
}
|
|
@@ -12879,8 +13143,15 @@ class VApplication {
|
|
|
12879
13143
|
this.#logger = this.#logManager.getLogger('VApplication');
|
|
12880
13144
|
// Analyze function dependencies
|
|
12881
13145
|
this.#functionDependencies = ExpressionUtils.analyzeFunctionDependencies(options.methods || {});
|
|
12882
|
-
// Analyze computed dependencies
|
|
12883
|
-
|
|
13146
|
+
// Analyze computed dependencies based on getter functions only.
|
|
13147
|
+
// Writable computeds (defined as { get, set }) contribute their getter for dependency analysis.
|
|
13148
|
+
const computedGetters = {};
|
|
13149
|
+
if (options.computed) {
|
|
13150
|
+
for (const [key, def] of Object.entries(options.computed)) {
|
|
13151
|
+
computedGetters[key] = VApplication.#getComputedGetter(def);
|
|
13152
|
+
}
|
|
13153
|
+
}
|
|
13154
|
+
this.#computedDependencies = ExpressionUtils.analyzeFunctionDependencies(computedGetters);
|
|
12884
13155
|
// Initialize watcher manager
|
|
12885
13156
|
this.#watcher = new VWatcher(this.#logger);
|
|
12886
13157
|
// Initialize bindings from data, computed, and methods
|
|
@@ -13023,6 +13294,26 @@ class VApplication {
|
|
|
13023
13294
|
}
|
|
13024
13295
|
}
|
|
13025
13296
|
}
|
|
13297
|
+
/**
|
|
13298
|
+
* Extracts the getter function from a computed property definition.
|
|
13299
|
+
* Supports both bare function form and { get, set } object form.
|
|
13300
|
+
*/
|
|
13301
|
+
static #getComputedGetter(def) {
|
|
13302
|
+
if (typeof def === 'function') {
|
|
13303
|
+
return def;
|
|
13304
|
+
}
|
|
13305
|
+
return def.get;
|
|
13306
|
+
}
|
|
13307
|
+
/**
|
|
13308
|
+
* Extracts the setter function from a computed property definition, if any.
|
|
13309
|
+
* Returns undefined for read-only (function-form) computed properties.
|
|
13310
|
+
*/
|
|
13311
|
+
static #getComputedSetter(def) {
|
|
13312
|
+
if (typeof def === 'function') {
|
|
13313
|
+
return undefined;
|
|
13314
|
+
}
|
|
13315
|
+
return def.set;
|
|
13316
|
+
}
|
|
13026
13317
|
/**
|
|
13027
13318
|
* Computes dependent identifiers for a given computed property and value.
|
|
13028
13319
|
* This is used to track dependencies in directives like v-for.
|
|
@@ -13085,6 +13376,20 @@ class VApplication {
|
|
|
13085
13376
|
}
|
|
13086
13377
|
}
|
|
13087
13378
|
}
|
|
13379
|
+
// Register setters for writable computed properties so that assignments to them
|
|
13380
|
+
// (e.g. via v-model or direct mutation through bindings.raw) route through the user-provided
|
|
13381
|
+
// setter, which typically writes back to underlying reactive properties.
|
|
13382
|
+
if (this.#options.computed) {
|
|
13383
|
+
for (const [key, def] of Object.entries(this.#options.computed)) {
|
|
13384
|
+
const setter = VApplication.#getComputedSetter(def);
|
|
13385
|
+
if (setter) {
|
|
13386
|
+
const bindings = this.#bindings;
|
|
13387
|
+
this.#bindings.registerWritableComputed(key, (value) => {
|
|
13388
|
+
setter.call(bindings.raw, value);
|
|
13389
|
+
});
|
|
13390
|
+
}
|
|
13391
|
+
}
|
|
13392
|
+
}
|
|
13088
13393
|
// Add computed properties (initialization mode)
|
|
13089
13394
|
this.#recomputeProperties(true);
|
|
13090
13395
|
}
|
|
@@ -13243,7 +13548,7 @@ class VApplication {
|
|
|
13243
13548
|
return;
|
|
13244
13549
|
}
|
|
13245
13550
|
// Now compute this property
|
|
13246
|
-
const computedFn = this.#options.computed[key];
|
|
13551
|
+
const computedFn = VApplication.#getComputedGetter(this.#options.computed[key]);
|
|
13247
13552
|
try {
|
|
13248
13553
|
const oldValue = this.#bindings?.get(key);
|
|
13249
13554
|
const newValue = computedFn.call(this.#bindings?.raw);
|