@getvitops/astro 4.1.0 → 6.0.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.
@@ -1,32 +1,50 @@
1
1
  ---
2
2
  /**
3
- * Subgrid — re-emits slotted children as `<li>` items of a `.subgrid` grid, so
4
- * the tranches inside each one align on shared row lines. Every declaration
5
- * lives in the framework's `subgrid` pattern (patterns/subgrid.css), which is
6
- * emitted in all three output formats; this file is markup only.
3
+ * Subgrid — a grid whose items re-declare the parent's row tracks, so the tranches
4
+ * inside every item (head, body, footer) land on the same row lines no matter how
5
+ * much content each one holds. Every declaration lives in the framework's `subgrid`
6
+ * pattern (patterns/subgrid.css); this file is markup only.
7
7
  *
8
- * Nothing here may reach for a Tailwind utility: Tailwind doesn't scan
9
- * node_modules, so a class only it can emit is silently never generated — and
10
- * classes in the generator's TW_CLASH list are stripped from the framework's
11
- * own tailwind bundle by design.
8
+ * **The slot is rendered verbatim — author the items yourself.** This used to parse
9
+ * the slotted HTML and replace each child with an `<li>`, carrying over its class
10
+ * and style. That existed only because a `<ul>` may contain nothing but `<li>`, and
11
+ * it cost more than it bought: the child's tag was discarded, and every attribute
12
+ * had to be copied by hand, so `id`, `data-*` and `aria-*` were silently dropped and
13
+ * `href` could not survive at all. Passing the slot through means there is nothing to
14
+ * copy and nothing to lose.
12
15
  *
13
- * Size it from the consumer's own CSS, which keeps media-query overrides
14
- * working: `--subgrid-cols`, `--subgrid-row-span`, `--subgrid-gap`,
15
- * `--subgrid-row-gap`. `--num-items` is set here for nth-child-style maths.
16
+ * <Subgrid>
17
+ * <li class="card subgrid-card">…</li>
18
+ * </Subgrid>
19
+ *
20
+ * `as` picks the container. It defaults to `ul`, because a set of cards IS a list and
21
+ * that is what tells assistive tech how many there are and where you are among them.
22
+ * Use `as="div"` when the items are genuinely not a list.
23
+ *
24
+ * **`role="list"` is restored automatically for `ul`/`ol`.** `.subgrid` sets
25
+ * `list-style: none`, and that stops Safari + VoiceOver announcing a list at all — so
26
+ * the marker reset silently costs the semantics the `<ul>` was chosen for. Passing
27
+ * your own `role` wins.
28
+ *
29
+ * Size it from the consumer's own CSS, which keeps media-query overrides working:
30
+ * `--subgrid-cols`, `--subgrid-row-span`, `--subgrid-gap`, `--subgrid-row-gap`.
31
+ *
32
+ * For a card whose whole area should be clickable, see `<Cards />` (selectable text,
33
+ * needs JS) or `.stretched-link` (no JS, text not selectable).
16
34
  */
17
35
  import type { HTMLAttributes } from 'astro/types';
18
- import { parseRenderedSlots, getAttribute, serialize, styleList } from '@getvitops/utils';
19
36
 
20
- export interface Props extends HTMLAttributes<'ul'> {};
37
+ export interface Props extends HTMLAttributes<'ul'> {
38
+ /** Container element. `ul`/`ol` also get `role="list"`. */
39
+ as?: 'ul' | 'ol' | 'div';
40
+ }
21
41
 
22
- const { class: className, style } = Astro.props;
42
+ const { as: Tag = 'ul', class: className, role, ...rest } = Astro.props;
23
43
 
24
- const nodes = parseRenderedSlots(await Astro.slots.render('default'));
25
- const slots = nodes.filter(node => 'tagName' in node).map(node => ({ html: serialize(node), attrs: node.attrs }));
44
+ // Only lists lose semantics to `list-style: none`, so only lists get the role back.
45
+ const listRole = Tag === 'ul' || Tag === 'ol' ? 'list' : undefined;
26
46
  ---
27
47
 
28
- <ul class:list={["subgrid", className]} style={styleList({'--num-items': slots.length}, style)}>
29
- {slots.map((slot) => (
30
- <li class:list={[getAttribute(slot, 'class')]} style={styleList(getAttribute(slot, 'style'))} set:html={slot.html}></li>
31
- ))}
32
- </ul>
48
+ <Tag class:list={['subgrid', className]} role={role ?? listRole} {...rest}>
49
+ <slot />
50
+ </Tag>
@@ -65,14 +65,14 @@ const {
65
65
  {
66
66
  enhance ? (
67
67
  <wc-tree label={label} {...rest}>
68
- <ul class="tree tree--lines" aria-label={ariaLabel}>
68
+ <ul class="tree tree--lines" role="list" aria-label={ariaLabel}>
69
69
  {items.map((item) => (
70
70
  <TreeNode item={item} depth={0} openDepth={openDepth} />
71
71
  ))}
72
72
  </ul>
73
73
  </wc-tree>
74
74
  ) : (
75
- <ul class="tree tree--lines" aria-label={ariaLabel}>
75
+ <ul class="tree tree--lines" role="list" aria-label={ariaLabel}>
76
76
  {items.map((item) => (
77
77
  <TreeNode item={item} depth={0} openDepth={openDepth} />
78
78
  ))}
@@ -41,7 +41,7 @@ const isBranch = kids.length > 0;
41
41
  </span>
42
42
  </summary>
43
43
  {item.descriptionHtml && <p class="tree__desc" set:html={item.descriptionHtml} />}
44
- <ul class="tree">
44
+ <ul class="tree" role="list">
45
45
  {kids.map((kid) => (
46
46
  <Astro.self item={kid} depth={depth + 1} openDepth={openDepth} />
47
47
  ))}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `@getvitops/astro/tracking` — the attribution vocabulary, re-exported.
3
+ *
4
+ * The documented conversion flow spans two packages: `<Tracking />` captures the
5
+ * click ID from `@getvitops/astro`, and the route reads it back with
6
+ * `parseTrackingCookie` from `@getvitops/utils/tracking`. Under strict pnpm a
7
+ * consumer cannot resolve the second from app code without adding
8
+ * `@getvitops/utils` as a direct dependency — so the flow as written in the
9
+ * changelog needed two installs, and the obvious workaround (importing the same
10
+ * symbols from `@getvitops/astro`'s index) is the one that drags the whole
11
+ * integration, and its Node builtins, toward a Worker bundle.
12
+ *
13
+ * This entry is the third option and the correct one: one install, and a module
14
+ * graph that reaches only `@getvitops/utils/tracking` — no `sharp`, no
15
+ * `node:child_process`, nothing that cannot run in a Worker. It is a separate
16
+ * build entry for exactly the reason `./routes` is.
17
+ */
18
+ export * from '@getvitops/utils/tracking';
19
+
20
+ /**
21
+ * Where the capture script beacons `tel:` conversions.
22
+ *
23
+ * Re-exported here so a consumer writing the route can assert against the same
24
+ * constant the script uses, rather than retyping the path.
25
+ */
26
+ export { TRACKING_ENDPOINT } from '../tracking.ts';
@@ -26,6 +26,16 @@ const COOKIE_NAME = '_ac';
26
26
  const COOKIE_DAYS = 90;
27
27
  const AB_COOKIE = '_ab';
28
28
 
29
+ // Where the `tel:` conversion beacon goes. Mirrors TRACKING_ENDPOINT in
30
+ // ../tracking.ts (pinned by `tracking.test.ts`), copied for the same reason as
31
+ // the parameter lists below — this file is inlined into the document.
32
+ //
33
+ // It was previously a bare literal at the call site, which made the contract
34
+ // between this script and the route the consumer writes invisible: name the
35
+ // file anything else and every beacon 404s, conversions vanish, and nothing
36
+ // errors anywhere. The integration now warns at build when the route is absent.
37
+ const TRACK_ENDPOINT = '/api/track';
38
+
29
39
  // Mirrors CLICK_ID_PARAMS/UTM_PARAMS in @getvitops/utils/tracking, deliberately
30
40
  // copied rather than imported: this file is inlined into the document by
31
41
  // <Tracking />, so a bare import would either fail or drag a module graph into
@@ -144,6 +154,6 @@ document.addEventListener('click', (e) => {
144
154
 
145
155
  // Fire-and-forget beacon; don't block the tel: navigation
146
156
  if (navigator.sendBeacon) {
147
- navigator.sendBeacon('/api/track', JSON.stringify({ event: 'call', phone: link.href }));
157
+ navigator.sendBeacon(TRACK_ENDPOINT, JSON.stringify({ event: 'call', phone: link.href }));
148
158
  }
149
159
  });
package/src/tracking.ts CHANGED
@@ -27,6 +27,19 @@ export interface GetvitopsTrackingOptions {
27
27
  /** The cookie the capture script writes. Mirrors `TRACKING_COOKIE` in utils. */
28
28
  export const TRACKING_COOKIE = '_ac';
29
29
 
30
+ /**
31
+ * Where the capture script sends its `tel:` conversion beacon.
32
+ *
33
+ * The route itself is the consumer's to write (`createConversionRoute()` in
34
+ * `@getvitops/astro/routes`), so this is the one place the two halves agree on
35
+ * a path. It was a bare literal inside the inlined script: a consumer who named
36
+ * the file anything else got a silent 404 on every beacon and lost conversions
37
+ * with no error anywhere to explain it. Exported so a consumer can assert
38
+ * against it, mirrored in `scripts/tracking.ts` because that file is inlined
39
+ * into the document and cannot import, and pinned by `tracking.test.ts`.
40
+ */
41
+ export const TRACKING_ENDPOINT = '/api/track';
42
+
30
43
  export interface ResolvedTracking {
31
44
  enabled: boolean;
32
45
  category: TrackingCategory;