@mintjamsinc/ichigojs 0.1.60 → 0.1.62

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
@@ -277,7 +277,27 @@ Event handling with modifiers:
277
277
  <div @click.stop="handleClick">Stop propagation</div>
278
278
  ```
279
279
 
280
- Supported modifiers: `.stop`, `.prevent`, `.capture`, `.self`, `.once`
280
+ Event modifiers: `.stop`, `.prevent`, `.capture`, `.self`, `.once`
281
+
282
+ Key modifiers (KeyboardEvent): `.enter`, `.tab`, `.delete` (matches Delete/Backspace), `.esc` / `.escape`, `.space`, `.up`, `.down`, `.left`, `.right`
283
+
284
+ Mouse button modifiers (MouseEvent): `.left`, `.middle`, `.right`
285
+
286
+ System modifier keys (KeyboardEvent and MouseEvent): `.shift`, `.ctrl`, `.alt`, `.meta`. Add `.exact` to require that no other system modifiers are held.
287
+
288
+ ```html
289
+ <!-- Shift + Click -->
290
+ <button @click.shift="onShiftClick">Shift+Click</button>
291
+
292
+ <!-- Right-click without bringing up the browser menu -->
293
+ <div @contextmenu.prevent="onMenu">Right-click me</div>
294
+
295
+ <!-- Middle-click -->
296
+ <a @mousedown.middle="onMiddleClick">Middle-click</a>
297
+
298
+ <!-- Ctrl+Click only (no other modifiers held) -->
299
+ <button @click.ctrl.exact="onCtrlClickOnly">Ctrl+Click only</button>
300
+ ```
281
301
 
282
302
  **Event Handlers with Context:**
283
303
 
package/dist/ichigo.cjs CHANGED
@@ -8243,6 +8243,13 @@
8243
8243
  * their own alias (e.g. "file" -> "files[0]") without overwriting each other.
8244
8244
  */
8245
8245
  #localAliases = new Map();
8246
+ /**
8247
+ * Setters for writable computed properties. When a key registered here is assigned
8248
+ * through the bindings proxy (e.g. via v-model or `bindings.set`), the setter is invoked
8249
+ * instead of writing directly to the local store. The cached value of the computed property
8250
+ * is updated by the recompute cycle through `setSilent`, which bypasses this routing.
8251
+ */
8252
+ #writableComputeds = new Map();
8246
8253
  /**
8247
8254
  * Creates a new instance of VBindings.
8248
8255
  * @param parent The parent bindings, if any.
@@ -8262,6 +8269,14 @@
8262
8269
  return this.#parent?.raw[key];
8263
8270
  },
8264
8271
  set: (obj, key, value) => {
8272
+ // If this key is a writable computed, route the assignment through its setter.
8273
+ // `setSilent` (used to update the cached value during recompute) sets `suppressOnChange`,
8274
+ // which bypasses this routing so the cached value can be written directly.
8275
+ if (!this.#suppressOnChange && this.#writableComputeds.has(key)) {
8276
+ const setter = this.#writableComputeds.get(key);
8277
+ setter(value);
8278
+ return true;
8279
+ }
8265
8280
  let target = obj;
8266
8281
  if (!Reflect.has(target, key)) {
8267
8282
  for (let parent = this.#parent; parent; parent = parent.#parent) {
@@ -8448,6 +8463,16 @@
8448
8463
  this.#suppressOnChange = false;
8449
8464
  }
8450
8465
  }
8466
+ /**
8467
+ * Registers a setter for a writable computed property. When the given key is assigned
8468
+ * through the bindings proxy, the setter will be invoked instead of writing directly to
8469
+ * the local store.
8470
+ * @param key The computed property name.
8471
+ * @param setter The setter function to invoke on assignment.
8472
+ */
8473
+ registerWritableComputed(key, setter) {
8474
+ this.#writableComputeds.set(key, setter);
8475
+ }
8451
8476
  /**
8452
8477
  * Manually adds an identifier to the set of changed identifiers.
8453
8478
  * This is useful for computed properties that need to mark themselves as changed
@@ -11297,6 +11322,10 @@
11297
11322
  * The `v-on` directive supports event modifiers such as `.stop`, `.prevent`, `.capture`, `.self`, and `.once` to modify the behavior of the event listener.
11298
11323
  * For example, `v-on:click.stop="handleClick"` will stop the event from propagating up the DOM tree.
11299
11324
  *
11325
+ * Key modifiers (KeyboardEvent): `.enter`, `.tab`, `.delete` (Delete/Backspace), `.esc` / `.escape`, `.space`, `.up`, `.down`, `.left`, `.right`.
11326
+ * Mouse button modifiers (MouseEvent): `.left`, `.middle`, `.right`.
11327
+ * System modifiers (KeyboardEvent and MouseEvent): `.shift`, `.ctrl`, `.alt`, `.meta`, plus `.exact` to require that no other system modifiers are held.
11328
+ *
11300
11329
  * Additionally, this directive supports lifecycle hooks:
11301
11330
  * @mount="onMount" - Called before the VNode is mounted to the DOM element
11302
11331
  * @mounted="onMounted" - Called after the VNode is mounted to the DOM element
@@ -11489,14 +11518,14 @@
11489
11518
  const eventName = this.#eventName;
11490
11519
  const useCapture = this.#modifiers.has('capture');
11491
11520
  const isOnce = this.#modifiers.has('once');
11521
+ // System modifier keys (held during the event) shared by KeyboardEvent and MouseEvent.
11522
+ const systemModifiers = ['shift', 'ctrl', 'alt', 'meta'];
11492
11523
  // Create the event listener function
11493
11524
  this.#listener = (event) => {
11494
- // Check key modifiers for keyboard events
11525
+ // Check key modifiers for keyboard events.
11526
+ // The `.left` / `.right` aliases here mean ArrowLeft / ArrowRight; the same tokens
11527
+ // map to mouse buttons further below, dispatched by event type to avoid ambiguity.
11495
11528
  if (event instanceof KeyboardEvent) {
11496
- // Map of modifier alias -> KeyboardEvent.key values it matches.
11497
- // Multiple values allow a single modifier to match several physical keys
11498
- // (e.g. `.delete` matches both Delete and Backspace, matching Vue's behavior).
11499
- // Multiple aliases pointing to the same key are allowed (e.g. `.esc` / `.escape`).
11500
11529
  const keyMap = {
11501
11530
  'enter': ['Enter'],
11502
11531
  'tab': ['Tab'],
@@ -11524,6 +11553,46 @@
11524
11553
  }
11525
11554
  }
11526
11555
  }
11556
+ // Check mouse button modifiers for mouse events.
11557
+ // `.left` / `.middle` / `.right` map to MouseEvent.button values 0 / 1 / 2.
11558
+ if (event instanceof MouseEvent) {
11559
+ const buttonMap = {
11560
+ 'left': 0,
11561
+ 'middle': 1,
11562
+ 'right': 2
11563
+ };
11564
+ const hasButtonModifier = Object.keys(buttonMap).some(b => this.#modifiers.has(b));
11565
+ if (hasButtonModifier) {
11566
+ let buttonMatched = false;
11567
+ for (const [modifier, buttonValue] of Object.entries(buttonMap)) {
11568
+ if (this.#modifiers.has(modifier) && event.button === buttonValue) {
11569
+ buttonMatched = true;
11570
+ break;
11571
+ }
11572
+ }
11573
+ if (!buttonMatched) {
11574
+ return;
11575
+ }
11576
+ }
11577
+ }
11578
+ // Check system modifier keys (shift / ctrl / alt / meta) and `.exact`.
11579
+ // These properties exist on both KeyboardEvent and MouseEvent.
11580
+ if (event instanceof KeyboardEvent || event instanceof MouseEvent) {
11581
+ // Required system modifiers must be held.
11582
+ for (const mod of systemModifiers) {
11583
+ if (this.#modifiers.has(mod) && !event[`${mod}Key`]) {
11584
+ return;
11585
+ }
11586
+ }
11587
+ // `.exact` rejects events with extra system modifiers held that weren't listed.
11588
+ if (this.#modifiers.has('exact')) {
11589
+ for (const mod of systemModifiers) {
11590
+ if (event[`${mod}Key`] && !this.#modifiers.has(mod)) {
11591
+ return;
11592
+ }
11593
+ }
11594
+ }
11595
+ }
11527
11596
  // Apply event modifiers
11528
11597
  if (this.#modifiers.has('stop')) {
11529
11598
  event.stopPropagation();
@@ -13124,8 +13193,15 @@
13124
13193
  this.#logger = this.#logManager.getLogger('VApplication');
13125
13194
  // Analyze function dependencies
13126
13195
  this.#functionDependencies = ExpressionUtils.analyzeFunctionDependencies(options.methods || {});
13127
- // Analyze computed dependencies
13128
- this.#computedDependencies = ExpressionUtils.analyzeFunctionDependencies(options.computed || {});
13196
+ // Analyze computed dependencies based on getter functions only.
13197
+ // Writable computeds (defined as { get, set }) contribute their getter for dependency analysis.
13198
+ const computedGetters = {};
13199
+ if (options.computed) {
13200
+ for (const [key, def] of Object.entries(options.computed)) {
13201
+ computedGetters[key] = VApplication.#getComputedGetter(def);
13202
+ }
13203
+ }
13204
+ this.#computedDependencies = ExpressionUtils.analyzeFunctionDependencies(computedGetters);
13129
13205
  // Initialize watcher manager
13130
13206
  this.#watcher = new VWatcher(this.#logger);
13131
13207
  // Initialize bindings from data, computed, and methods
@@ -13268,6 +13344,26 @@
13268
13344
  }
13269
13345
  }
13270
13346
  }
13347
+ /**
13348
+ * Extracts the getter function from a computed property definition.
13349
+ * Supports both bare function form and { get, set } object form.
13350
+ */
13351
+ static #getComputedGetter(def) {
13352
+ if (typeof def === 'function') {
13353
+ return def;
13354
+ }
13355
+ return def.get;
13356
+ }
13357
+ /**
13358
+ * Extracts the setter function from a computed property definition, if any.
13359
+ * Returns undefined for read-only (function-form) computed properties.
13360
+ */
13361
+ static #getComputedSetter(def) {
13362
+ if (typeof def === 'function') {
13363
+ return undefined;
13364
+ }
13365
+ return def.set;
13366
+ }
13271
13367
  /**
13272
13368
  * Computes dependent identifiers for a given computed property and value.
13273
13369
  * This is used to track dependencies in directives like v-for.
@@ -13330,6 +13426,20 @@
13330
13426
  }
13331
13427
  }
13332
13428
  }
13429
+ // Register setters for writable computed properties so that assignments to them
13430
+ // (e.g. via v-model or direct mutation through bindings.raw) route through the user-provided
13431
+ // setter, which typically writes back to underlying reactive properties.
13432
+ if (this.#options.computed) {
13433
+ for (const [key, def] of Object.entries(this.#options.computed)) {
13434
+ const setter = VApplication.#getComputedSetter(def);
13435
+ if (setter) {
13436
+ const bindings = this.#bindings;
13437
+ this.#bindings.registerWritableComputed(key, (value) => {
13438
+ setter.call(bindings.raw, value);
13439
+ });
13440
+ }
13441
+ }
13442
+ }
13333
13443
  // Add computed properties (initialization mode)
13334
13444
  this.#recomputeProperties(true);
13335
13445
  }
@@ -13488,7 +13598,7 @@
13488
13598
  return;
13489
13599
  }
13490
13600
  // Now compute this property
13491
- const computedFn = this.#options.computed[key];
13601
+ const computedFn = VApplication.#getComputedGetter(this.#options.computed[key]);
13492
13602
  try {
13493
13603
  const oldValue = this.#bindings?.get(key);
13494
13604
  const newValue = computedFn.call(this.#bindings?.raw);