@escape-game-over/atlas 0.1.67 → 0.1.68

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/docs/checks.md CHANGED
@@ -124,7 +124,7 @@ dependencies at all. Vitest type-checks them itself when it runs them.
124
124
  | `breadcrumbs.test.ts` | the trail, a dropped ancestor, `orphanSegments`, and how a gap is reported |
125
125
  | `jsonld/*.test.ts` | that a `</script>` in any value cannot close the block, each node's shape and `@id`, and how a price table becomes offers |
126
126
  | `analytics.test.ts` | Umami's attributes, its three-state booleans, the `domains` list that would record nothing, and that `consentRequired` answers exactly when a tag was emitted |
127
- | `google-analytics.test.ts` | that consent is denied first and precedes every tag, one loader for many ids, dead `UA-` properties, the preconnect — once, ahead of the block that writes the loader's URL, never `crossorigin` — and where `googleEvent` sends, per setting |
127
+ | `google-analytics.test.ts` | that consent is denied first and precedes every tag, one loader for many ids, dead `UA-` properties, and the preconnect — once, ahead of the block that writes the loader's URL, never `crossorigin` |
128
128
  | `contact.test.ts` | the E.164 a `tel:` needs — trunk zero dropped, spacing stripped — and the displayed form kept |
129
129
  | `hours.test.ts` | collapsing a week into runs, week start changing the answer, and every impossible week that throws |
130
130
  | `money.test.ts` | a bare count widened to a band, the span of a table, and the gaps and overlaps that throw |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
@@ -54,14 +54,14 @@
54
54
  "@types/node": ">=22",
55
55
  "astro": ">=7",
56
56
  "typescript": ">=5",
57
- "vite": ">=8"
57
+ "vite": ">= 8"
58
58
  },
59
59
  "devDependencies": {
60
- "@biomejs/biome": "2.5.14",
61
- "@types/node": "26.6.2",
62
- "@vitest/coverage-istanbul": "5.0.1",
60
+ "@biomejs/biome": "2.5.15",
61
+ "@types/node": "26.6.3",
62
+ "@vitest/coverage-istanbul": "5.0.3",
63
63
  "astro": "7.3.5",
64
64
  "typescript": "6.0.3",
65
- "vitest": "5.0.1"
65
+ "vitest": "5.0.3"
66
66
  }
67
67
  }
@@ -5,9 +5,12 @@
5
5
  * (`closedby="any"`). Close it with `<form method="dialog"><button>…</button></form>`.
6
6
  *
7
7
  * ```astro
8
- * <AutoDialog id="promo" aria-label="…" after={2000} once="session">…</AutoDialog>
8
+ * <AutoDialog id="promo" aria-label="…" when={{ on: "delay", ms: 2000 }} once="session">…</AutoDialog>
9
9
  * ```
10
10
  *
11
+ * With `when={{ on: "visible" }}` it opens when the place it is written
12
+ * scrolls into view.
13
+ *
11
14
  * A dialog opened only by a button or a script needs none of this: write
12
15
  * `<dialog closedby="any">`.
13
16
  */
@@ -20,8 +23,8 @@ type Props = AutoOpen &
20
23
  readonly id: string;
21
24
  };
22
25
 
23
- const { after, once, waitForConsent, modal, ...attrs } = Astro.props;
24
- const auto: AutoOpen = { after, once, waitForConsent, modal };
26
+ const { when, once, waitForConsent, modal, ...attrs } = Astro.props;
27
+ const auto: AutoOpen = { when, once, waitForConsent, modal };
25
28
  ---
26
29
 
27
30
  <script src="./auto-dialog.ts" />
@@ -31,6 +34,11 @@ const auto: AutoOpen = { after, once, waitForConsent, modal };
31
34
  data-auto={JSON.stringify(auto)}
32
35
  style="display:contents"
33
36
  >
37
+ <!-- Out of the flow, so it adds no gap to a flex or grid parent. -->
38
+ <span
39
+ data-spot
40
+ style="position:absolute"
41
+ ></span>
34
42
  <dialog
35
43
  closedby="any"
36
44
  {...attrs}
@@ -78,11 +78,11 @@ if (meta.consentVendors.length > 0 && !Astro.slots.has("consent")) {
78
78
  <MetaTags tags={meta.tags} />
79
79
  {rememberLocale && <RememberLocale />}
80
80
  {Object.entries(fonts).map(([key, font]) => (
81
- <Font
82
- cssVariable={`--${key}`}
83
- preload={font.preload === true}
84
- />
85
- ))}
81
+ <Font
82
+ cssVariable={`--${key}`}
83
+ preload={font.preload === true}
84
+ />
85
+ ))}
86
86
  <slot name="head" />
87
87
  </head>
88
88
  <body {...body}>
@@ -1,17 +1,29 @@
1
1
  import { CONSENT_EVENT, CONSENT_TAG, readConsent } from "./consent.ts";
2
2
  import { element } from "./element.ts";
3
+ import { type LoadTrigger, triggered } from "./load-script.ts";
3
4
  import { data, ref } from "./ref.ts";
4
5
 
6
+ /**
7
+ * `loadScript`'s triggers, as a dialog uses them. `visible` watches where the
8
+ * `<AutoDialog>` sits in the page, so placing it in a section opens it as the
9
+ * visitor scrolls there. A dialog opened by a click is a plain `<dialog>`.
10
+ */
11
+ export type OpenTrigger =
12
+ | { readonly on: "idle" }
13
+ | { readonly on: "delay"; readonly ms: number }
14
+ | { readonly on: "visible"; readonly margin?: string }
15
+ | { readonly on: "interaction" };
16
+
5
17
  /** When an `<AutoDialog>` opens by itself. */
6
18
  export interface AutoOpen {
7
- /** Milliseconds after load, or after the consent answer with `waitForConsent`. */
8
- readonly after: number;
19
+ /** Counted from `load`, or from the consent answer with `waitForConsent`. */
20
+ readonly when: OpenTrigger;
9
21
  /**
10
22
  * Once closed, stays closed for the tab (`session`) or for good (`visitor`).
11
23
  * Omitted, it opens on every page.
12
24
  */
13
25
  readonly once?: "session" | "visitor";
14
- /** Holds the timer until the consent banner is answered, where it asks. */
26
+ /** Holds the trigger until the consent banner is answered, where it asks. */
15
27
  readonly waitForConsent?: boolean;
16
28
  /** Defaults to `true`; `false` leaves the page usable behind it. */
17
29
  readonly modal?: boolean;
@@ -20,10 +32,14 @@ export interface AutoOpen {
20
32
  const storage = (once: "session" | "visitor"): Storage =>
21
33
  once === "session" ? sessionStorage : localStorage;
22
34
 
23
- /** Opens `dialog` as `options` say, until `signal` aborts. */
35
+ /**
36
+ * Opens `dialog` as `options` say, until `signal` aborts. `spot` is where the
37
+ * dialog sits in the page, which a `visible` trigger watches.
38
+ */
24
39
  export function autoOpen(
25
40
  dialog: HTMLDialogElement,
26
- { after, once, waitForConsent = false, modal = true }: AutoOpen,
41
+ spot: Element,
42
+ { when, once, waitForConsent = false, modal = true }: AutoOpen,
27
43
  signal: AbortSignal
28
44
  ): void {
29
45
  const key = `atlas-dialog:${dialog.id}`;
@@ -44,13 +60,14 @@ export function autoOpen(
44
60
  );
45
61
  }
46
62
 
63
+ const trigger: LoadTrigger =
64
+ when.on === "visible" ? { ...when, element: spot } : when;
47
65
  const start = () => {
48
- const timer = setTimeout(() => {
66
+ void triggered(trigger, signal).then(() => {
49
67
  if (dialog.open) return;
50
68
  if (modal) dialog.showModal();
51
69
  else dialog.show();
52
- }, after);
53
- signal.addEventListener("abort", () => clearTimeout(timer));
70
+ });
54
71
  };
55
72
 
56
73
  const asking =
@@ -66,6 +83,7 @@ export function autoOpen(
66
83
  export const autoDialog = element("atlas-dialog", ({ root, signal }) => {
67
84
  autoOpen(
68
85
  ref<HTMLDialogElement>(root, "dialog"),
86
+ ref(root, "[data-spot]"),
69
87
  JSON.parse(data(root, "auto")),
70
88
  signal
71
89
  );