@fusion.dev/helix-core 0.180.0 → 0.181.0

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.
@@ -2539,7 +2539,7 @@
2539
2539
  "declarations": [
2540
2540
  {
2541
2541
  "kind": "class",
2542
- "description": "Passes a click on the host itself to the control inside that answers clicks.\n\n**A click dispatched on the host reaches nothing that toggles.** A switch,\na checkbox or a radio renders its control in its shadow root, and only a\nclick that lands there moves it. `element.click()`, a test's or an\nautomation's, is dispatched on the host, and so is the click a `<label for>`\non the page sends the element it names: `helix-switch` left `checked` false\nunder `click()` while a pointer press on it turned it on. A native checkbox\ntoggles on both, and code written against one expects it.\n\n**Only a click that starts at the host**, whose path begins there. A press\ninside the shadow root reaches the host too, retargeted, and has already\ndone its work where it landed; the click this sends to the control reaches\nthe host the same way, and goes no further.",
2542
+ "description": "Passes a click on the host itself to the control inside that answers clicks.\n\n**A click dispatched on the host reaches nothing that toggles.** A switch,\na checkbox or a radio renders its control in its shadow root, and only a\nclick that lands there moves it. `element.click()`, a test's or an\nautomation's, is dispatched on the host, and so is the click a `<label for>`\non the page sends the element it names: `helix-switch` left `checked` false\nunder `click()` while a pointer press on it turned it on. A native checkbox\ntoggles on both, and code written against one expects it.\n\n**One click reaches the page for each press, as from a native control.**\nThe control's click used to be sent composed, so it crossed the shadow root\nand reached the host after the host's own: a listener on the host heard two\nclicks for one `click()` or one press on a `<label for>`, where one on a\nnative checkbox hears one. So there are two paths, and each leaves exactly\none click outside the shadow root:\n\n- **The host's `click()`** calls {@link click}, which clicks the control\n instead. That click is the only one: it reaches the host retargeted, as a\n click inside a native control does, after the control has moved, so a\n listener reads the new state and `preventDefault()` puts the old one back.\n- **A click that starts at the host** some other way, from a `<label for>`,\n a press on the host's own box, or `dispatchEvent`, is the click the page\n hears. The control is sent a copy that is not composed, so it moves the\n control and stops at the shadow root. The copy goes in the capture phase,\n so the host's own listeners already read the new state, as a native\n checkbox's do. A press inside the shadow root has already landed on its\n control and is left alone.",
2543
2543
  "name": "HostClickController",
2544
2544
  "members": [
2545
2545
  {
@@ -2560,6 +2560,16 @@
2560
2560
  }
2561
2561
  }
2562
2562
  },
2563
+ {
2564
+ "kind": "method",
2565
+ "name": "click",
2566
+ "return": {
2567
+ "type": {
2568
+ "text": "boolean"
2569
+ }
2570
+ },
2571
+ "description": "Clicks the control, for the host's own `click()`. Answers `false` when\nthere is no control yet, before the first render, so the host can fall\nback to its own click."
2572
+ },
2563
2573
  {
2564
2574
  "kind": "field",
2565
2575
  "name": "forward",
@@ -34084,7 +34094,7 @@
34084
34094
  "type": {
34085
34095
  "text": "string"
34086
34096
  },
34087
- "default": "'0.180.0'",
34097
+ "default": "'0.181.0'",
34088
34098
  "description": "The version of the package this file ships in.\n\n**Written by `fusion/scripts/version.mjs`** wherever it writes the package's\n`package.json`, and stamped back into the repository beside it after a\nrelease, so the two never disagree; a spec beside this holds them to it.\nNothing else in the running code knows which release it is."
34089
34099
  }
34090
34100
  ],
@@ -34720,6 +34730,6 @@
34720
34730
  ],
34721
34731
  "helix": {
34722
34732
  "package": "@fusion.dev/helix-core",
34723
- "version": "0.180.0"
34733
+ "version": "0.181.0"
34724
34734
  }
34725
34735
  }
@@ -10,10 +10,24 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
10
10
  * under `click()` while a pointer press on it turned it on. A native checkbox
11
11
  * toggles on both, and code written against one expects it.
12
12
  *
13
- * **Only a click that starts at the host**, whose path begins there. A press
14
- * inside the shadow root reaches the host too, retargeted, and has already
15
- * done its work where it landed; the click this sends to the control reaches
16
- * the host the same way, and goes no further.
13
+ * **One click reaches the page for each press, as from a native control.**
14
+ * The control's click used to be sent composed, so it crossed the shadow root
15
+ * and reached the host after the host's own: a listener on the host heard two
16
+ * clicks for one `click()` or one press on a `<label for>`, where one on a
17
+ * native checkbox hears one. So there are two paths, and each leaves exactly
18
+ * one click outside the shadow root:
19
+ *
20
+ * - **The host's `click()`** calls {@link click}, which clicks the control
21
+ * instead. That click is the only one: it reaches the host retargeted, as a
22
+ * click inside a native control does, after the control has moved, so a
23
+ * listener reads the new state and `preventDefault()` puts the old one back.
24
+ * - **A click that starts at the host** some other way, from a `<label for>`,
25
+ * a press on the host's own box, or `dispatchEvent`, is the click the page
26
+ * hears. The control is sent a copy that is not composed, so it moves the
27
+ * control and stops at the shadow root. The copy goes in the capture phase,
28
+ * so the host's own listeners already read the new state, as a native
29
+ * checkbox's do. A press inside the shadow root has already landed on its
30
+ * control and is left alone.
17
31
  */
18
32
  export declare class HostClickController implements ReactiveController {
19
33
  private readonly host;
@@ -21,5 +35,11 @@ export declare class HostClickController implements ReactiveController {
21
35
  constructor(host: ReactiveControllerHost & HTMLElement, target: () => HTMLElement | null | undefined);
22
36
  hostConnected(): void;
23
37
  hostDisconnected(): void;
38
+ /**
39
+ * Clicks the control, for the host's own `click()`. Answers `false` when
40
+ * there is no control yet, before the first render, so the host can fall
41
+ * back to its own click.
42
+ */
43
+ click(): boolean;
24
44
  private readonly forward;
25
45
  }
@@ -3,17 +3,44 @@ export class HostClickController {
3
3
  this.host = host;
4
4
  this.target = target;
5
5
  this.forward = (event) => {
6
- if (event.composedPath()[0] !== this.host) {
6
+ if (event.composedPath()[0] !== this.host || event.defaultPrevented) {
7
7
  return;
8
8
  }
9
- this.target()?.click();
9
+ const control = this.target();
10
+ if (!control || control.matches(':disabled')) {
11
+ return;
12
+ }
13
+ control.dispatchEvent(new MouseEvent('click', {
14
+ altKey: event.altKey,
15
+ bubbles: true,
16
+ button: event.button,
17
+ buttons: event.buttons,
18
+ cancelable: true,
19
+ clientX: event.clientX,
20
+ clientY: event.clientY,
21
+ composed: false,
22
+ ctrlKey: event.ctrlKey,
23
+ detail: event.detail,
24
+ metaKey: event.metaKey,
25
+ screenX: event.screenX,
26
+ screenY: event.screenY,
27
+ shiftKey: event.shiftKey,
28
+ }));
10
29
  };
11
30
  host.addController(this);
12
31
  }
13
32
  hostConnected() {
14
- this.host.addEventListener('click', this.forward);
33
+ this.host.addEventListener('click', this.forward, { capture: true });
15
34
  }
16
35
  hostDisconnected() {
17
- this.host.removeEventListener('click', this.forward);
36
+ this.host.removeEventListener('click', this.forward, { capture: true });
37
+ }
38
+ click() {
39
+ const control = this.target();
40
+ if (!control) {
41
+ return false;
42
+ }
43
+ control.click();
44
+ return true;
18
45
  }
19
46
  }
package/dist/version.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * release, so the two never disagree; a spec beside this holds them to it.
7
7
  * Nothing else in the running code knows which release it is.
8
8
  */
9
- export declare const VERSION = "0.180.0";
9
+ export declare const VERSION = "0.181.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.180.0';
1
+ export const VERSION = '0.181.0';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-core",
3
3
  "description": "Helix element primitives: base classes, mixins, decorators, utilities.",
4
- "version": "0.180.0",
4
+ "version": "0.181.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -74,11 +74,11 @@
74
74
  "bugs": "https://github.com/project-fusion/fusion/issues",
75
75
  "dependencies": {
76
76
  "@floating-ui/dom": "^1.8.0",
77
- "@fusion.dev/helix-icons": "0.180.0"
77
+ "@fusion.dev/helix-icons": "0.181.0"
78
78
  },
79
79
  "devDependencies": {
80
- "@fusion.dev/helix-cem": "0.180.0",
81
- "@fusion.dev/helix-testing": "0.180.0",
80
+ "@fusion.dev/helix-cem": "0.181.0",
81
+ "@fusion.dev/helix-testing": "0.181.0",
82
82
  "@lit-labs/ssr": "^4.1.0",
83
83
  "lit": "^3.2.1"
84
84
  },