@fusion.dev/helix-core 0.0.9 → 0.0.10

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 ADDED
@@ -0,0 +1,30 @@
1
+ # @fusion.dev/helix-core
2
+
3
+ The primitives both Helix tiers are built from: base classes, mixins, decorators
4
+ and utilities.
5
+
6
+ ```sh
7
+ npm install @fusion.dev/helix-core
8
+ ```
9
+
10
+ Useful directly if you are writing custom elements that need to sit alongside
11
+ Helix and behave the same way — controlled properties that answer, a single
12
+ page-wide ticker rather than one timer per element, scroll-parent resolution that
13
+ crosses shadow boundaries, and duplicate-registration detection.
14
+
15
+ ```js
16
+ import { duplicateRegistrations } from '@fusion.dev/helix-core'
17
+
18
+ // Two copies of a library on one page is silent: the first registration keeps
19
+ // the tag and `instanceof` against the second is quietly false.
20
+ console.log(duplicateRegistrations())
21
+ ```
22
+
23
+ Most consumers get this transitively through
24
+ [`@fusion.dev/helix`](https://www.npmjs.com/package/@fusion.dev/helix) or
25
+ [`@fusion.dev/helix-headless`](https://www.npmjs.com/package/@fusion.dev/helix-headless)
26
+ and never import it by name.
27
+
28
+ [Documentation](https://helix.fusion.dev)
29
+
30
+ MIT
@@ -7782,6 +7782,24 @@
7782
7782
  "kind": "javascript-module",
7783
7783
  "path": "src/utils/ticker/ticker.ts",
7784
7784
  "declarations": [
7785
+ {
7786
+ "kind": "function",
7787
+ "name": "withClock",
7788
+ "return": {
7789
+ "type": {
7790
+ "text": "(() => void)"
7791
+ }
7792
+ },
7793
+ "parameters": [
7794
+ {
7795
+ "name": "clock",
7796
+ "type": {
7797
+ "text": "Clock"
7798
+ }
7799
+ }
7800
+ ],
7801
+ "description": "Replaces the ticker with an empty one on the given clock, and hands back the\nway to put the real one back.\n\n**On `globalThis` with the rest of it, deliberately.** The spec bundler emits\none bundle per family and the runner loads them all into one page, so a\nmodule-level clock would have the reach of one bundle while the timer it\nschedules has the reach of the document. That is the fault the arrangement\nabove exists to avoid, and this must not reintroduce it."
7802
+ },
7785
7803
  {
7786
7804
  "kind": "function",
7787
7805
  "name": "subscribe",
@@ -7808,6 +7826,14 @@
7808
7826
  }
7809
7827
  ],
7810
7828
  "exports": [
7829
+ {
7830
+ "kind": "js",
7831
+ "name": "withClock",
7832
+ "declaration": {
7833
+ "name": "withClock",
7834
+ "module": "src/utils/ticker/ticker.ts"
7835
+ }
7836
+ },
7811
7837
  {
7812
7838
  "kind": "js",
7813
7839
  "name": "subscribe",
@@ -1,3 +1,33 @@
1
+ /**
2
+ * The three platform calls this makes, taken as values so a test can drive them.
3
+ *
4
+ * **Not a configuration point.** It exists because the alternative is asserting
5
+ * on the machine: a spec that subscribes at 1000ms and waits 1400 is measuring
6
+ * how loaded the runner is, and a loaded runner eats 400ms. One did, on a green
7
+ * branch, and blocked a merge that had nothing wrong with it. A bigger margin is
8
+ * the same bug at a lower rate.
9
+ *
10
+ * Bun 1.1.43 is what this workspace pins, and it has `jest.useFakeTimers()`
11
+ * without `jest.advanceTimersByTime`, so there is nothing to advance a faked
12
+ * timer with. Measured rather than assumed: a spec asserting that function
13
+ * exists throws.
14
+ */
15
+ export interface Clock {
16
+ clearTimer: (timer: ReturnType<typeof setTimeout>) => void;
17
+ now: () => number;
18
+ setTimer: (run: () => void, wait: number) => ReturnType<typeof setTimeout>;
19
+ }
20
+ /**
21
+ * Replaces the ticker with an empty one on the given clock, and hands back the
22
+ * way to put the real one back.
23
+ *
24
+ * **On `globalThis` with the rest of it, deliberately.** The spec bundler emits
25
+ * one bundle per family and the runner loads them all into one page, so a
26
+ * module-level clock would have the reach of one bundle while the timer it
27
+ * schedules has the reach of the document. That is the fault the arrangement
28
+ * above exists to avoid, and this must not reintroduce it.
29
+ */
30
+ export declare const withClock: (clock: Clock) => (() => void);
1
31
  /**
2
32
  * Asks to be run in `delay` milliseconds, and returns the way to stop asking.
3
33
  *
@@ -1,18 +1,43 @@
1
+ const REAL = {
2
+ clearTimer: (timer) => {
3
+ clearTimeout(timer);
4
+ },
5
+ now: () => Date.now(),
6
+ setTimer: (run, wait) => setTimeout(run, wait),
7
+ };
1
8
  const KEY = Symbol.for('helix.ticker');
2
9
  const ticker = () => {
3
10
  const global = globalThis;
4
11
  let found = global[KEY];
5
12
  if (!found) {
6
- found = { timer: undefined, waiting: new Set() };
13
+ found = { clock: REAL, timer: undefined, waiting: new Set() };
7
14
  global[KEY] = found;
8
15
  }
9
16
  return found;
10
17
  };
18
+ /**
19
+ * Replaces the ticker with an empty one on the given clock, and hands back the
20
+ * way to put the real one back.
21
+ *
22
+ * **On `globalThis` with the rest of it, deliberately.** The spec bundler emits
23
+ * one bundle per family and the runner loads them all into one page, so a
24
+ * module-level clock would have the reach of one bundle while the timer it
25
+ * schedules has the reach of the document. That is the fault the arrangement
26
+ * above exists to avoid, and this must not reintroduce it.
27
+ */
28
+ export const withClock = (clock) => {
29
+ const global = globalThis;
30
+ const before = global[KEY];
31
+ global[KEY] = { clock, timer: undefined, waiting: new Set() };
32
+ return () => {
33
+ global[KEY] = before;
34
+ };
35
+ };
11
36
  /** Sets the one timeout, for the soonest thing anybody is waiting on. */
12
37
  const schedule = () => {
13
38
  const held = ticker();
14
39
  if (held.timer !== undefined) {
15
- clearTimeout(held.timer);
40
+ held.clock.clearTimer(held.timer);
16
41
  held.timer = undefined;
17
42
  }
18
43
  if (held.waiting.size === 0) {
@@ -21,10 +46,10 @@ const schedule = () => {
21
46
  const soonest = Math.min(...[...held.waiting].map((one) => one.at));
22
47
  /* Floored at a second: a subscriber handed a zero would be re-armed inside the
23
48
  callback that fired it, which is a busy loop wearing a timer's clothes. */
24
- const wait = Math.max(1000, soonest - Date.now());
25
- held.timer = setTimeout(() => {
49
+ const wait = Math.max(1000, soonest - held.clock.now());
50
+ held.timer = held.clock.setTimer(() => {
26
51
  held.timer = undefined;
27
- const now = Date.now();
52
+ const now = held.clock.now();
28
53
  /* Copied before iterating, because every callback re-subscribes and would
29
54
  otherwise be walked again inside the same pass. */
30
55
  for (const one of [...held.waiting]) {
@@ -45,7 +70,7 @@ const schedule = () => {
45
70
  */
46
71
  export const subscribe = (delay, run) => {
47
72
  const held = ticker();
48
- const entry = { at: Date.now() + delay, run };
73
+ const entry = { at: held.clock.now() + delay, run };
49
74
  held.waiting.add(entry);
50
75
  schedule();
51
76
  return () => {
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.0.9",
4
+ "version": "0.0.10",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -33,12 +33,12 @@
33
33
  "bugs": "https://github.com/project-fusion/fusion/issues",
34
34
  "dependencies": {
35
35
  "@floating-ui/dom": "^1.8.0",
36
- "@fusion.dev/helix-icons": "0.0.9"
36
+ "@fusion.dev/helix-icons": "0.0.10"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@custom-elements-manifest/analyzer": "^0.11.0",
40
- "@fusion.dev/helix-cem": "0.0.9",
41
- "@fusion.dev/helix-testing": "0.0.9",
40
+ "@fusion.dev/helix-cem": "0.0.10",
41
+ "@fusion.dev/helix-testing": "0.0.10",
42
42
  "lit": "^3.2.1"
43
43
  },
44
44
  "homepage": "https://fusion.dev",