@mintjamsinc/ichigojs 0.1.64 → 0.1.66
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 +226 -39
- package/dist/ichigo.cjs.map +1 -1
- package/dist/ichigo.esm.js +226 -39
- 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 +226 -39
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/types/ichigo/directives/VBindDirective.d.ts +5 -0
- package/dist/types/ichigo/directives/VDirectiveManager.d.ts +9 -0
- package/package.json +1 -1
package/dist/ichigo.esm.js
CHANGED
|
@@ -6859,11 +6859,16 @@ class ExpressionUtils {
|
|
|
6859
6859
|
const ast = parse(source, { ecmaVersion: "latest" });
|
|
6860
6860
|
const dependencies = new Set();
|
|
6861
6861
|
const declaredVariables = new Set();
|
|
6862
|
+
const thisAliases = new Set();
|
|
6862
6863
|
// First, collect all declared variables (const, let, var, function params, etc.)
|
|
6863
6864
|
ancestor(ast, {
|
|
6864
6865
|
VariableDeclarator(node) {
|
|
6865
6866
|
if (node.id.type === 'Identifier') {
|
|
6866
6867
|
declaredVariables.add(node.id.name);
|
|
6868
|
+
// Detect aliases assigned from `this`, e.g. `let vm = this;`
|
|
6869
|
+
if (node.init && node.init.type === 'ThisExpression') {
|
|
6870
|
+
thisAliases.add(node.id.name);
|
|
6871
|
+
}
|
|
6867
6872
|
}
|
|
6868
6873
|
},
|
|
6869
6874
|
FunctionDeclaration(node) {
|
|
@@ -6915,9 +6920,14 @@ class ExpressionUtils {
|
|
|
6915
6920
|
}
|
|
6916
6921
|
},
|
|
6917
6922
|
MemberExpression(node) {
|
|
6918
|
-
// Handle 'this.propertyName' patterns
|
|
6919
|
-
if (node.
|
|
6920
|
-
|
|
6923
|
+
// Handle 'this.propertyName' patterns and aliases like 'vm.x' when 'vm = this'
|
|
6924
|
+
if (node.property.type === 'Identifier') {
|
|
6925
|
+
if (node.object.type === 'ThisExpression') {
|
|
6926
|
+
dependencies.add(node.property.name);
|
|
6927
|
+
}
|
|
6928
|
+
else if (node.object.type === 'Identifier' && thisAliases.has(node.object.name)) {
|
|
6929
|
+
dependencies.add(node.property.name);
|
|
6930
|
+
}
|
|
6921
6931
|
}
|
|
6922
6932
|
}
|
|
6923
6933
|
});
|
|
@@ -7011,7 +7021,7 @@ class ExpressionUtils {
|
|
|
7011
7021
|
return expression;
|
|
7012
7022
|
}
|
|
7013
7023
|
try {
|
|
7014
|
-
// Build a map of positions to replace: { start: number, end: number, name: string }[]
|
|
7024
|
+
// Build a map of positions to replace: { start: number, end: number, name: string, asThisAlias?: boolean }[]
|
|
7015
7025
|
const replacements = [];
|
|
7016
7026
|
// In script mode we must not wrap in parens (that would make multi-statement input invalid).
|
|
7017
7027
|
// Offsets from the parser therefore refer directly to the original expression, so no shift.
|
|
@@ -7020,13 +7030,17 @@ class ExpressionUtils {
|
|
|
7020
7030
|
const offsetShift = asScript ? 0 : 1;
|
|
7021
7031
|
const parsedAst = parse(source, { ecmaVersion: 'latest' });
|
|
7022
7032
|
// Track identifiers that are locally declared within the handler body (let/const/var, function
|
|
7023
|
-
// params) so we don't rewrite them to `this.xxx`.
|
|
7024
|
-
//
|
|
7033
|
+
// params) so we don't rewrite them to `this.xxx`. Additionally detect `this` aliases such as
|
|
7034
|
+
// `let vm = this;` so that `vm.x` can be rewritten to `this.x` even though `vm` is locally declared.
|
|
7025
7035
|
const locallyDeclared = new Set();
|
|
7036
|
+
const thisAliases = new Set();
|
|
7026
7037
|
if (asScript) {
|
|
7027
7038
|
full(parsedAst, (node) => {
|
|
7028
7039
|
if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier') {
|
|
7029
7040
|
locallyDeclared.add(node.id.name);
|
|
7041
|
+
if (node.init && node.init.type === 'ThisExpression') {
|
|
7042
|
+
thisAliases.add(node.id.name);
|
|
7043
|
+
}
|
|
7030
7044
|
}
|
|
7031
7045
|
else if (node.type === 'FunctionDeclaration' && node.id?.type === 'Identifier') {
|
|
7032
7046
|
locallyDeclared.add(node.id.name);
|
|
@@ -7043,20 +7057,34 @@ class ExpressionUtils {
|
|
|
7043
7057
|
if (!bindingIdentifiers.has(node.name)) {
|
|
7044
7058
|
return;
|
|
7045
7059
|
}
|
|
7046
|
-
//
|
|
7060
|
+
// Locally declared identifiers must not be rewritten to `this.xxx`. The one
|
|
7061
|
+
// exception is a `this`-alias (e.g. `let vm = this;`): when such an alias is
|
|
7062
|
+
// used as the OBJECT of a MemberExpression (`vm.x`), we replace just the
|
|
7063
|
+
// object with `this` so that `vm.x` becomes `this.x`. In every other position
|
|
7064
|
+
// (the declaration LHS, a bare reference such as `console.log(vm)`, an
|
|
7065
|
+
// argument, etc.) the alias must be left untouched — rewriting it to
|
|
7066
|
+
// `this.vm` would either produce a syntax error (`let this.vm = this;`) or
|
|
7067
|
+
// change the meaning at runtime.
|
|
7068
|
+
// walk.fullAncestor includes the visited node itself as the last entry of
|
|
7069
|
+
// `ancestors`, so the actual parent node is at length - 2.
|
|
7070
|
+
const parentNode = ancestors.length >= 2 ? ancestors[ancestors.length - 2] : undefined;
|
|
7071
|
+
const isAliasAsMemberObject = parentNode?.type === 'MemberExpression' && parentNode.object === node;
|
|
7047
7072
|
if (locallyDeclared.has(node.name)) {
|
|
7073
|
+
if (!thisAliases.has(node.name) || !isAliasAsMemberObject) {
|
|
7074
|
+
return;
|
|
7075
|
+
}
|
|
7076
|
+
replacements.push({
|
|
7077
|
+
start: node.start - offsetShift,
|
|
7078
|
+
end: node.end - offsetShift,
|
|
7079
|
+
name: node.name,
|
|
7080
|
+
asThisAlias: true
|
|
7081
|
+
});
|
|
7048
7082
|
return;
|
|
7049
7083
|
}
|
|
7050
|
-
//
|
|
7051
|
-
// (e.g., in
|
|
7052
|
-
if (
|
|
7053
|
-
|
|
7054
|
-
if (parent.type === 'MemberExpression') {
|
|
7055
|
-
// Skip if this identifier is the property (not the object) of a non-computed member access
|
|
7056
|
-
if (!parent.computed && parent.property === node) {
|
|
7057
|
-
return;
|
|
7058
|
-
}
|
|
7059
|
-
}
|
|
7084
|
+
// Skip identifiers that are the property (not the object) of a non-computed
|
|
7085
|
+
// member access (e.g., the `prop` in `obj.prop`).
|
|
7086
|
+
if (parentNode?.type === 'MemberExpression' && !parentNode.computed && parentNode.property === node) {
|
|
7087
|
+
return;
|
|
7060
7088
|
}
|
|
7061
7089
|
// Add to replacements list (adjust for the wrapping parentheses in expression mode)
|
|
7062
7090
|
replacements.push({
|
|
@@ -7070,9 +7098,8 @@ class ExpressionUtils {
|
|
|
7070
7098
|
// Apply replacements
|
|
7071
7099
|
let result = expression;
|
|
7072
7100
|
for (const replacement of replacements) {
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
result.substring(replacement.end);
|
|
7101
|
+
const insertion = replacement.asThisAlias ? 'this' : `this.${replacement.name}`;
|
|
7102
|
+
result = result.substring(0, replacement.start) + insertion + result.substring(replacement.end);
|
|
7076
7103
|
}
|
|
7077
7104
|
return result;
|
|
7078
7105
|
}
|
|
@@ -7437,6 +7464,13 @@ class VBindDirective {
|
|
|
7437
7464
|
get expression() {
|
|
7438
7465
|
return this.#expression;
|
|
7439
7466
|
}
|
|
7467
|
+
/**
|
|
7468
|
+
* Evaluates the bound expression and returns the current value.
|
|
7469
|
+
* Returns undefined when no evaluator is available (e.g., empty expression).
|
|
7470
|
+
*/
|
|
7471
|
+
evaluate() {
|
|
7472
|
+
return this.#evaluator?.evaluate();
|
|
7473
|
+
}
|
|
7440
7474
|
/**
|
|
7441
7475
|
* @inheritdoc
|
|
7442
7476
|
*/
|
|
@@ -8649,6 +8683,29 @@ class VDirectiveManager {
|
|
|
8649
8683
|
get keyDirective() {
|
|
8650
8684
|
return this.#keyDirective;
|
|
8651
8685
|
}
|
|
8686
|
+
/**
|
|
8687
|
+
* Finds a VBindDirective that binds the given attribute name (e.g. "value",
|
|
8688
|
+
* "true-value", "false-value"). Returns undefined if no matching v-bind
|
|
8689
|
+
* directive is registered on this node.
|
|
8690
|
+
*
|
|
8691
|
+
* Used by directives such as v-model that need to read the typed value of a
|
|
8692
|
+
* sibling v-bind without depending on the attribute order in the source HTML.
|
|
8693
|
+
*/
|
|
8694
|
+
findBindDirective(attrName) {
|
|
8695
|
+
if (!this.#directives || this.#directives.length === 0) {
|
|
8696
|
+
return undefined;
|
|
8697
|
+
}
|
|
8698
|
+
for (const directive of this.#directives) {
|
|
8699
|
+
if (directive.name !== StandardDirectiveName.V_BIND) {
|
|
8700
|
+
continue;
|
|
8701
|
+
}
|
|
8702
|
+
const bindDirective = directive;
|
|
8703
|
+
if (bindDirective.attributeName === attrName) {
|
|
8704
|
+
return bindDirective;
|
|
8705
|
+
}
|
|
8706
|
+
}
|
|
8707
|
+
return undefined;
|
|
8708
|
+
}
|
|
8652
8709
|
/**
|
|
8653
8710
|
* Gets the VBindDirective for options specific to the given directive name.
|
|
8654
8711
|
* Searches in order: `:options.{directive}` -> `:options`
|
|
@@ -11102,14 +11159,13 @@ class VModelDirective {
|
|
|
11102
11159
|
* @inheritdoc
|
|
11103
11160
|
*/
|
|
11104
11161
|
get domUpdater() {
|
|
11105
|
-
const
|
|
11106
|
-
// Create and return the DOM updater
|
|
11162
|
+
const self = this;
|
|
11107
11163
|
const updater = {
|
|
11108
11164
|
get dependentIdentifiers() {
|
|
11109
|
-
return
|
|
11165
|
+
return self.#collectDependentIdentifiers();
|
|
11110
11166
|
},
|
|
11111
11167
|
applyToDOM: () => {
|
|
11112
|
-
|
|
11168
|
+
self.#render();
|
|
11113
11169
|
}
|
|
11114
11170
|
};
|
|
11115
11171
|
return updater;
|
|
@@ -11124,7 +11180,38 @@ class VModelDirective {
|
|
|
11124
11180
|
* @inheritdoc
|
|
11125
11181
|
*/
|
|
11126
11182
|
get dependentIdentifiers() {
|
|
11127
|
-
return this.#
|
|
11183
|
+
return this.#collectDependentIdentifiers();
|
|
11184
|
+
}
|
|
11185
|
+
/**
|
|
11186
|
+
* Collects identifiers this directive's render depends on. For checkboxes
|
|
11187
|
+
* this includes the v-model expression itself plus the expressions bound to
|
|
11188
|
+
* `:value`, `:true-value`, and `:false-value`, since the rendered checked
|
|
11189
|
+
* state changes when any of these change.
|
|
11190
|
+
*/
|
|
11191
|
+
#collectDependentIdentifiers() {
|
|
11192
|
+
const ids = new Set(this.#evaluator?.dependentIdentifiers ?? []);
|
|
11193
|
+
const element = this.#vNode.node;
|
|
11194
|
+
if (element instanceof HTMLInputElement) {
|
|
11195
|
+
const manager = this.#vNode.directiveManager;
|
|
11196
|
+
// For checkboxes and radios, v-model's rendered state depends on
|
|
11197
|
+
// the element's typed `:value` binding (if present). Checkboxes
|
|
11198
|
+
// additionally depend on `:true-value` / `:false-value` bindings.
|
|
11199
|
+
if (element.type === 'checkbox' || element.type === 'radio') {
|
|
11200
|
+
const valueBind = manager?.findBindDirective('value');
|
|
11201
|
+
if (valueBind) {
|
|
11202
|
+
valueBind.dependentIdentifiers.forEach(id => ids.add(id));
|
|
11203
|
+
}
|
|
11204
|
+
}
|
|
11205
|
+
if (element.type === 'checkbox') {
|
|
11206
|
+
for (const attrName of ['true-value', 'false-value']) {
|
|
11207
|
+
const bindDirective = manager?.findBindDirective(attrName);
|
|
11208
|
+
if (bindDirective) {
|
|
11209
|
+
bindDirective.dependentIdentifiers.forEach(id => ids.add(id));
|
|
11210
|
+
}
|
|
11211
|
+
}
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
return Array.from(ids);
|
|
11128
11215
|
}
|
|
11129
11216
|
/**
|
|
11130
11217
|
* @inheritdoc
|
|
@@ -11204,14 +11291,16 @@ class VModelDirective {
|
|
|
11204
11291
|
// Update the element based on its type
|
|
11205
11292
|
if (element instanceof HTMLInputElement) {
|
|
11206
11293
|
if (element.type === 'checkbox') {
|
|
11207
|
-
element
|
|
11294
|
+
this.#renderCheckbox(element, value);
|
|
11208
11295
|
}
|
|
11209
11296
|
else if (element.type === 'radio') {
|
|
11210
|
-
// Prefer the
|
|
11211
|
-
//
|
|
11212
|
-
const
|
|
11213
|
-
|
|
11214
|
-
|
|
11297
|
+
// Prefer the typed value from a sibling :value binding when present,
|
|
11298
|
+
// falling back to any stored `_value` or the raw string `value`.
|
|
11299
|
+
const manager = this.#vNode.directiveManager;
|
|
11300
|
+
const bindDirective = manager?.findBindDirective('value');
|
|
11301
|
+
const radioValue = bindDirective !== undefined
|
|
11302
|
+
? bindDirective.evaluate()
|
|
11303
|
+
: (element._value !== undefined ? element._value : element.value);
|
|
11215
11304
|
element.checked = radioValue === value;
|
|
11216
11305
|
}
|
|
11217
11306
|
else {
|
|
@@ -11237,14 +11326,16 @@ class VModelDirective {
|
|
|
11237
11326
|
// Get the new value based on element type
|
|
11238
11327
|
if (target instanceof HTMLInputElement) {
|
|
11239
11328
|
if (target.type === 'checkbox') {
|
|
11240
|
-
newValue = target
|
|
11329
|
+
newValue = this.#computeCheckboxNewValue(target);
|
|
11241
11330
|
}
|
|
11242
11331
|
else if (target.type === 'radio') {
|
|
11243
|
-
// Prefer the
|
|
11244
|
-
//
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11332
|
+
// Prefer the typed value from a sibling :value binding when present,
|
|
11333
|
+
// falling back to any stored `_value` or the raw string `value`.
|
|
11334
|
+
const manager = this.#vNode.directiveManager;
|
|
11335
|
+
const bindDirective = manager?.findBindDirective('value');
|
|
11336
|
+
newValue = bindDirective !== undefined
|
|
11337
|
+
? bindDirective.evaluate()
|
|
11338
|
+
: (target._value !== undefined ? target._value : target.value);
|
|
11248
11339
|
}
|
|
11249
11340
|
else {
|
|
11250
11341
|
newValue = target.value;
|
|
@@ -11253,13 +11344,109 @@ class VModelDirective {
|
|
|
11253
11344
|
else if (target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
|
|
11254
11345
|
newValue = target.value;
|
|
11255
11346
|
}
|
|
11256
|
-
// Apply modifiers to the value
|
|
11257
|
-
|
|
11347
|
+
// Apply modifiers to the value (skip for checkboxes: their value
|
|
11348
|
+
// is either boolean, a custom true/false value, or an array, none
|
|
11349
|
+
// of which should be coerced by .trim or .number).
|
|
11350
|
+
const isCheckbox = target instanceof HTMLInputElement && target.type === 'checkbox';
|
|
11351
|
+
if (!isCheckbox) {
|
|
11352
|
+
newValue = this.#applyModifiers(newValue);
|
|
11353
|
+
}
|
|
11258
11354
|
// Update the binding
|
|
11259
11355
|
this.#updateBinding(newValue);
|
|
11260
11356
|
};
|
|
11261
11357
|
element.addEventListener(eventName, this.#listener);
|
|
11262
11358
|
}
|
|
11359
|
+
/**
|
|
11360
|
+
* Renders a checkbox in one of three modes (Vue-compatible):
|
|
11361
|
+
* 1. Array binding: the bound value is an array; the checkbox is checked
|
|
11362
|
+
* when its element-value is a member of that array.
|
|
11363
|
+
* 2. true-value/false-value binding: when `:true-value` (and optionally
|
|
11364
|
+
* `:false-value`) is provided via v-bind, the checkbox is checked
|
|
11365
|
+
* when the bound value strictly equals the resolved true-value.
|
|
11366
|
+
* 3. Boolean binding (default): the bound value is coerced to boolean.
|
|
11367
|
+
*/
|
|
11368
|
+
#renderCheckbox(element, value) {
|
|
11369
|
+
if (Array.isArray(value)) {
|
|
11370
|
+
const elementValue = this.#resolveCheckboxElementValue(element);
|
|
11371
|
+
element.checked = value.indexOf(elementValue) !== -1;
|
|
11372
|
+
return;
|
|
11373
|
+
}
|
|
11374
|
+
const trueValueDescriptor = this.#resolveCheckboxTrueFalseValues(element);
|
|
11375
|
+
if (trueValueDescriptor) {
|
|
11376
|
+
element.checked = value === trueValueDescriptor.trueValue;
|
|
11377
|
+
return;
|
|
11378
|
+
}
|
|
11379
|
+
element.checked = !!value;
|
|
11380
|
+
}
|
|
11381
|
+
/**
|
|
11382
|
+
* Computes the value to write back to the bound expression when a checkbox
|
|
11383
|
+
* change event fires. Mirrors the three-mode logic of #renderCheckbox.
|
|
11384
|
+
*
|
|
11385
|
+
* For array binding, the current value of the bound expression is read so
|
|
11386
|
+
* that a fresh array can be returned (the existing array is not mutated,
|
|
11387
|
+
* which preserves reactivity semantics).
|
|
11388
|
+
*/
|
|
11389
|
+
#computeCheckboxNewValue(target) {
|
|
11390
|
+
const currentValue = this.#evaluator?.evaluate();
|
|
11391
|
+
if (Array.isArray(currentValue)) {
|
|
11392
|
+
const elementValue = this.#resolveCheckboxElementValue(target);
|
|
11393
|
+
const next = currentValue.slice();
|
|
11394
|
+
const index = next.indexOf(elementValue);
|
|
11395
|
+
if (target.checked) {
|
|
11396
|
+
if (index === -1) {
|
|
11397
|
+
next.push(elementValue);
|
|
11398
|
+
}
|
|
11399
|
+
}
|
|
11400
|
+
else {
|
|
11401
|
+
if (index !== -1) {
|
|
11402
|
+
next.splice(index, 1);
|
|
11403
|
+
}
|
|
11404
|
+
}
|
|
11405
|
+
return next;
|
|
11406
|
+
}
|
|
11407
|
+
const trueValueDescriptor = this.#resolveCheckboxTrueFalseValues(target);
|
|
11408
|
+
if (trueValueDescriptor) {
|
|
11409
|
+
return target.checked ? trueValueDescriptor.trueValue : trueValueDescriptor.falseValue;
|
|
11410
|
+
}
|
|
11411
|
+
return target.checked;
|
|
11412
|
+
}
|
|
11413
|
+
/**
|
|
11414
|
+
* Resolves the typed element value for a checkbox. Prefers the value bound
|
|
11415
|
+
* via `:value` (evaluated through the sibling VBindDirective so type is
|
|
11416
|
+
* preserved), then the typed value previously stored on the element by
|
|
11417
|
+
* VBindDirective, and finally the raw string `value` attribute.
|
|
11418
|
+
*/
|
|
11419
|
+
#resolveCheckboxElementValue(element) {
|
|
11420
|
+
const bindDirective = this.#vNode.directiveManager?.findBindDirective('value');
|
|
11421
|
+
if (bindDirective) {
|
|
11422
|
+
return bindDirective.evaluate();
|
|
11423
|
+
}
|
|
11424
|
+
if (element._value !== undefined) {
|
|
11425
|
+
return element._value;
|
|
11426
|
+
}
|
|
11427
|
+
return element.value;
|
|
11428
|
+
}
|
|
11429
|
+
/**
|
|
11430
|
+
* Resolves the (true-value, false-value) pair for a checkbox if either is
|
|
11431
|
+
* bound via `:true-value` or `:false-value`. Returns undefined when no
|
|
11432
|
+
* true/false value binding is present, signalling that the default boolean
|
|
11433
|
+
* mode should be used.
|
|
11434
|
+
*
|
|
11435
|
+
* If only one of the two is bound, the other defaults match Vue: an unbound
|
|
11436
|
+
* true-value defaults to literal `true`, an unbound false-value to `false`.
|
|
11437
|
+
*/
|
|
11438
|
+
#resolveCheckboxTrueFalseValues(element) {
|
|
11439
|
+
const manager = this.#vNode.directiveManager;
|
|
11440
|
+
const trueBind = manager?.findBindDirective('true-value');
|
|
11441
|
+
const falseBind = manager?.findBindDirective('false-value');
|
|
11442
|
+
if (!trueBind && !falseBind) {
|
|
11443
|
+
return undefined;
|
|
11444
|
+
}
|
|
11445
|
+
return {
|
|
11446
|
+
trueValue: trueBind ? trueBind.evaluate() : true,
|
|
11447
|
+
falseValue: falseBind ? falseBind.evaluate() : false,
|
|
11448
|
+
};
|
|
11449
|
+
}
|
|
11263
11450
|
/**
|
|
11264
11451
|
* Applies modifiers to the input value.
|
|
11265
11452
|
* @param value The value to process.
|