@fusion.dev/helix-core 0.179.0 → 0.180.1

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.
@@ -378,6 +378,15 @@
378
378
  "default": "'inline'",
379
379
  "description": "Which axis the readings are laid out along.\n\n**An axis and not a boolean, because two of the four charts do not use x.**\nA horizontal bar chart runs its categories *down* the box, so resolving\nevery reading from the pointer's x leaves a move from the first row to the\nlast never changing the answer: measured on four rows, all four reported\nthe third, which is the slot the middle of the box falls in. It looks like\na tooltip that will not follow and is a tooltip reading the wrong axis.\n\n`none` is the ring. Its slices are real elements at every angle, so the\nreading under the pointer is the one the pointer is over, and arithmetic on\neither axis would answer with whichever slice shared a coordinate: at three\no'clock and at nine o'clock, x alone gives the same slice for opposite\nsides. The slices call `set` and this only holds the answer."
380
380
  },
381
+ {
382
+ "kind": "field",
383
+ "name": "drawnLtr",
384
+ "type": {
385
+ "text": "boolean"
386
+ },
387
+ "default": "false",
388
+ "description": "Whether the plot is drawn left to right on every page, as Scatter Chart's\nplane is, so the arrows follow the drawing rather than the page's\ndirection. Off, the arrow pointing along the line of text moves on."
389
+ },
381
390
  {
382
391
  "kind": "field",
383
392
  "name": "onReading",
@@ -2530,7 +2539,7 @@
2530
2539
  "declarations": [
2531
2540
  {
2532
2541
  "kind": "class",
2533
- "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.",
2534
2543
  "name": "HostClickController",
2535
2544
  "members": [
2536
2545
  {
@@ -2551,6 +2560,16 @@
2551
2560
  }
2552
2561
  }
2553
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
+ },
2554
2573
  {
2555
2574
  "kind": "field",
2556
2575
  "name": "forward",
@@ -12852,6 +12871,14 @@
12852
12871
  "path": "src/index.ts",
12853
12872
  "declarations": [],
12854
12873
  "exports": [
12874
+ {
12875
+ "kind": "js",
12876
+ "name": "auditChildren",
12877
+ "declaration": {
12878
+ "name": "auditChildren",
12879
+ "package": "#audit-children"
12880
+ }
12881
+ },
12855
12882
  {
12856
12883
  "kind": "js",
12857
12884
  "name": "auditName",
@@ -24587,6 +24614,86 @@
24587
24614
  }
24588
24615
  ]
24589
24616
  },
24617
+ {
24618
+ "kind": "javascript-module",
24619
+ "path": "src/utils/audit-children/audit-children-off.ts",
24620
+ "declarations": [
24621
+ {
24622
+ "kind": "function",
24623
+ "name": "auditChildren",
24624
+ "description": "The unregistered-child warning in a production build: nothing.\n\n`#audit-children` resolves here unless the bundler or runtime asks for the\n`development` condition, so a production build carries none of the audit's\ncode. The signature is the audit's, so a call site reads the same in both\nbuilds. See `audit-children.ts`.",
24625
+ "type": {
24626
+ "text": "typeof audit"
24627
+ }
24628
+ }
24629
+ ],
24630
+ "exports": [
24631
+ {
24632
+ "kind": "js",
24633
+ "name": "auditChildren",
24634
+ "declaration": {
24635
+ "name": "auditChildren",
24636
+ "module": "src/utils/audit-children/audit-children-off.ts"
24637
+ }
24638
+ }
24639
+ ]
24640
+ },
24641
+ {
24642
+ "kind": "javascript-module",
24643
+ "path": "src/utils/audit-children/audit-children.ts",
24644
+ "declarations": [
24645
+ {
24646
+ "kind": "function",
24647
+ "name": "auditChildren",
24648
+ "return": {
24649
+ "type": {
24650
+ "text": "void"
24651
+ }
24652
+ },
24653
+ "parameters": [
24654
+ {
24655
+ "name": "host",
24656
+ "type": {
24657
+ "text": "HTMLElement"
24658
+ }
24659
+ },
24660
+ {
24661
+ "name": "part",
24662
+ "type": {
24663
+ "text": "string"
24664
+ }
24665
+ },
24666
+ {
24667
+ "name": "entry",
24668
+ "type": {
24669
+ "text": "string"
24670
+ }
24671
+ },
24672
+ {
24673
+ "name": "consequence",
24674
+ "type": {
24675
+ "text": "string"
24676
+ }
24677
+ },
24678
+ {
24679
+ "name": "drawn",
24680
+ "default": "false"
24681
+ }
24682
+ ],
24683
+ "description": "Reports a child from another family that a container holds and nobody\nregistered, naming the entry to import.\n\n**A container does not register another family's children.** An option\nbelongs to the Option family, and a page writing one inside a Select imports\n`@fusion.dev/helix/option` itself, as it imports the Select. Left out, the\noption is an unknown element: the list reads no choices and nothing throws.\nThis says so, once for each container tag and child tag on the page, with\nthe line to add.\n\nAsked once the page has loaded and painted, so an entry a later module\nimports is not taken for a missing one.\n\n**In a development build only**, as `missing-name` is: every container would\npay for a sentence meant for the person writing the page. Core imports it as\n`#audit-children`, which `package.json` resolves here under the\n`development` condition and to `audit-children-off.ts`, which does nothing,\neverywhere else.\n\n`part` is the child's tag after the tier, such as `option`, and `entry` its\nfamily's entry, such as `option`; the host's `tier` gives the rest of both.\n`consequence` says what the missing child costs, after \"so\". `drawn` is for\nchildren the host draws from data, such as the options of Listbox's `items`:\nthe page wrote none, so an undefined tag is reported without one in the\nhost's light DOM."
24684
+ }
24685
+ ],
24686
+ "exports": [
24687
+ {
24688
+ "kind": "js",
24689
+ "name": "auditChildren",
24690
+ "declaration": {
24691
+ "name": "auditChildren",
24692
+ "module": "src/utils/audit-children/audit-children.ts"
24693
+ }
24694
+ }
24695
+ ]
24696
+ },
24590
24697
  {
24591
24698
  "kind": "javascript-module",
24592
24699
  "path": "src/utils/audit-name/audit-name-off.ts",
@@ -33987,7 +34094,7 @@
33987
34094
  "type": {
33988
34095
  "text": "string"
33989
34096
  },
33990
- "default": "'0.179.0'",
34097
+ "default": "'0.180.1'",
33991
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."
33992
34099
  }
33993
34100
  ],
@@ -34623,6 +34730,6 @@
34623
34730
  ],
34624
34731
  "helix": {
34625
34732
  "package": "@fusion.dev/helix-core",
34626
- "version": "0.179.0"
34733
+ "version": "0.180.1"
34627
34734
  }
34628
34735
  }
@@ -162,6 +162,12 @@ export declare class ChartPointerController implements ReactiveController {
162
162
  * sides. The slices call `set` and this only holds the answer.
163
163
  */
164
164
  axis: 'block' | 'inline' | 'none';
165
+ /**
166
+ * Whether the plot is drawn left to right on every page, as Scatter Chart's
167
+ * plane is, so the arrows follow the drawing rather than the page's
168
+ * direction. Off, the arrow pointing along the line of text moves on.
169
+ */
170
+ drawnLtr: boolean;
165
171
  /**
166
172
  * Hears the reading: `change` each time it moves or is put away, and
167
173
  * `press` when it is clicked, tapped, or pressed with Enter or Space.
@@ -10,6 +10,7 @@ export class ChartPointerController {
10
10
  this.keyed = false;
11
11
  this.series = -1;
12
12
  this.axis = 'inline';
13
+ this.drawnLtr = false;
13
14
  this.by = 'mouse';
14
15
  this.said = { at: -1, series: -1 };
15
16
  this.tapped = false;
@@ -87,7 +88,7 @@ export class ChartPointerController {
87
88
  count,
88
89
  grid: this.grid,
89
90
  page: this.page(count),
90
- rtl: isRtl(this.host),
91
+ rtl: !this.drawnLtr && isRtl(this.host),
91
92
  series: this.columns().length,
92
93
  });
93
94
  if (next === undefined) {
@@ -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/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { auditChildren } from '@fusion.dev/helix-core/audits/children';
1
2
  export { auditName } from '@fusion.dev/helix-core/audits/name';
2
3
  export { auditNesting } from '@fusion.dev/helix-core/audits/nesting';
3
4
  export { auditServed } from '@fusion.dev/helix-core/audits/served';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { auditChildren } from '@fusion.dev/helix-core/audits/children';
1
2
  export { auditName } from '@fusion.dev/helix-core/audits/name';
2
3
  export { auditNesting } from '@fusion.dev/helix-core/audits/nesting';
3
4
  export { auditServed } from '@fusion.dev/helix-core/audits/served';
@@ -0,0 +1,10 @@
1
+ import type { auditChildren as audit } from './audit-children.js';
2
+ /**
3
+ * The unregistered-child warning in a production build: nothing.
4
+ *
5
+ * `#audit-children` resolves here unless the bundler or runtime asks for the
6
+ * `development` condition, so a production build carries none of the audit's
7
+ * code. The signature is the audit's, so a call site reads the same in both
8
+ * builds. See `audit-children.ts`.
9
+ */
10
+ export declare const auditChildren: typeof audit;
@@ -0,0 +1 @@
1
+ export const auditChildren = () => { };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Reports a child from another family that a container holds and nobody
3
+ * registered, naming the entry to import.
4
+ *
5
+ * **A container does not register another family's children.** An option
6
+ * belongs to the Option family, and a page writing one inside a Select imports
7
+ * `@fusion.dev/helix/option` itself, as it imports the Select. Left out, the
8
+ * option is an unknown element: the list reads no choices and nothing throws.
9
+ * This says so, once for each container tag and child tag on the page, with
10
+ * the line to add.
11
+ *
12
+ * Asked once the page has loaded and painted, so an entry a later module
13
+ * imports is not taken for a missing one.
14
+ *
15
+ * **In a development build only**, as `missing-name` is: every container would
16
+ * pay for a sentence meant for the person writing the page. Core imports it as
17
+ * `#audit-children`, which `package.json` resolves here under the
18
+ * `development` condition and to `audit-children-off.ts`, which does nothing,
19
+ * everywhere else.
20
+ *
21
+ * `part` is the child's tag after the tier, such as `option`, and `entry` its
22
+ * family's entry, such as `option`; the host's `tier` gives the rest of both.
23
+ * `consequence` says what the missing child costs, after "so". `drawn` is for
24
+ * children the host draws from data, such as the options of Listbox's `items`:
25
+ * the page wrote none, so an undefined tag is reported without one in the
26
+ * host's light DOM.
27
+ */
28
+ export declare const auditChildren: (host: HTMLElement, part: string, entry: string, consequence: string, drawn?: boolean) => void;
@@ -0,0 +1,37 @@
1
+ import { reportRuntime } from '../report-runtime/report-runtime.js';
2
+ const reported = new Set();
3
+ export const auditChildren = (host, part, entry, consequence, drawn = false) => {
4
+ if (typeof window === 'undefined') {
5
+ return;
6
+ }
7
+ const tier = host.constructor.tier ?? 'helix-headless-';
8
+ const tag = `${tier}${part}`;
9
+ const key = `${host.localName}>${tag}`;
10
+ if (reported.has(key)) {
11
+ return;
12
+ }
13
+ const check = () => {
14
+ if (reported.has(key) ||
15
+ !host.isConnected ||
16
+ customElements.get(tag) !== undefined ||
17
+ (!drawn && host.querySelector(tag) === null)) {
18
+ return;
19
+ }
20
+ reported.add(key);
21
+ const pkg = tier === 'helix-' ? '@fusion.dev/helix' : '@fusion.dev/helix-headless';
22
+ reportRuntime({
23
+ kind: 'unregistered-part',
24
+ message: `<${tag}> in <${host.localName}> is not registered, so ${consequence}. Import '${pkg}/${entry}'.`,
25
+ tag: host.localName,
26
+ });
27
+ };
28
+ const frame = () => {
29
+ requestAnimationFrame(check);
30
+ };
31
+ if (document.readyState === 'complete') {
32
+ frame();
33
+ }
34
+ else {
35
+ window.addEventListener('load', frame, { once: true });
36
+ }
37
+ };
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.179.0";
9
+ export declare const VERSION = "0.180.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.179.0';
1
+ export const VERSION = '0.180.1';
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.179.0",
4
+ "version": "0.180.1",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -17,6 +17,12 @@
17
17
  "types": "./dist/index.d.ts",
18
18
  "default": "./dist/index.js"
19
19
  },
20
+ "./audits/children": {
21
+ "types": "./dist/utils/audit-children/audit-children.d.ts",
22
+ "development": "./dist/utils/audit-children/audit-children.js",
23
+ "helix-development": "./dist/utils/audit-children/audit-children.js",
24
+ "default": "./dist/utils/audit-children/audit-children-off.js"
25
+ },
20
26
  "./audits/name": {
21
27
  "types": "./dist/utils/audit-name/audit-name.d.ts",
22
28
  "development": "./dist/utils/audit-name/audit-name.js",
@@ -68,16 +74,21 @@
68
74
  "bugs": "https://github.com/project-fusion/fusion/issues",
69
75
  "dependencies": {
70
76
  "@floating-ui/dom": "^1.8.0",
71
- "@fusion.dev/helix-icons": "0.179.0"
77
+ "@fusion.dev/helix-icons": "0.180.1"
72
78
  },
73
79
  "devDependencies": {
74
- "@fusion.dev/helix-cem": "0.179.0",
75
- "@fusion.dev/helix-testing": "0.179.0",
80
+ "@fusion.dev/helix-cem": "0.180.1",
81
+ "@fusion.dev/helix-testing": "0.180.1",
76
82
  "@lit-labs/ssr": "^4.1.0",
77
83
  "lit": "^3.2.1"
78
84
  },
79
85
  "homepage": "https://fusion.dev",
80
86
  "imports": {
87
+ "#audit-children": {
88
+ "development": "./src/utils/audit-children/audit-children.ts",
89
+ "helix-development": "./src/utils/audit-children/audit-children.ts",
90
+ "default": "./src/utils/audit-children/audit-children-off.ts"
91
+ },
81
92
  "#audit-name": {
82
93
  "development": "./src/utils/audit-name/audit-name.ts",
83
94
  "helix-development": "./src/utils/audit-name/audit-name.ts",