@at-flux/astro-feature-flags 1.0.0 → 1.0.3

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 CHANGED
@@ -24,10 +24,10 @@ Feature flags for Astro with a declarative config:
24
24
 
25
25
  ## Terminology (short)
26
26
 
27
- | Term | Meaning |
28
- | ---- | ------- |
29
- | **Astro dev** | Local `astro dev` — the built-in dev layer is active; all flags on at resolve time; dev toolbar only affects the browser. |
30
- | **Non-dev build** | `astro build` / preview / deploy with a shipped layer (`prod`, `staging`, …): `environments.<key>.flags` drives SSR, HTML culling, and route pruning. |
27
+ | Term | Meaning |
28
+ | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29
+ | **Astro dev** | Local `astro dev` — the built-in dev layer is active; all flags on at resolve time; dev toolbar only affects the browser. |
30
+ | **Non-dev build** | `astro build` / preview / deploy with a shipped layer (`prod`, `staging`, …): `environments.<key>.flags` drives SSR, HTML culling, and route pruning. |
31
31
  | **`isAstroDev`** (virtual module) | `import.meta.env.DEV` — Vite’s compile-time flag for components. Layer selection still comes from `environments` / `forceEnvironment`; use `isAstroDev` when you need Astro’s literal dev detection. |
32
32
 
33
33
  You cannot add an environment key named **`dev`** — that name is reserved and merged automatically (including its `when`; you do not set it in config).
@@ -180,18 +180,18 @@ The dev toolbar changes client-side preview state only.
180
180
 
181
181
  ### Top-level options
182
182
 
183
- | Option | Type | Default | Notes |
184
- | ------------------ | ----------------------------------- | --------------- | --------------------------------------------------------------------- |
185
- | `configRoot` | `string` | `process.cwd()` | Resolves relative `jsonConfigPath` values (root + per-environment). |
186
- | `jsonConfigPath` | `string` | unset | Optional **root** JSON file merged after inline config (see merge order for per-environment files). |
187
- | `forceEnvironment` | `string` | unset | Pin the active layer (skips `when` / `AFF_ENVIRONMENT` validation). |
188
- | `mode` | `string` | `NODE_ENV` | Optional advanced override for runtime resolution (mainly tests/tooling). Most apps should omit this and rely on `NODE_ENV` + `environments.when`. |
189
- | `env` | `Record<string, string \| undefined>` | `process.env` | `AFF_FEATURE_*` / `ASTRO_FEATURE_FLAGS` (not applied in `dev` layer). |
190
- | `tokenNamespace` | `string` | `'ff'` | CSS var namespace (`--ff-c-*`). |
191
- | `flags` | `Record<string, FlagConfig>` | `{}` | Flag declarations. |
192
- | `environments` | `Record<string, EnvironmentConfig>` | _(see below)_ | Declare non-`dev` layers only; reserved `dev` is injected. At least one other key; exactly one `when: true` unless forced. |
193
- | `css` | `DevOutlineCssOptions` | defaults | Global badge/outline layout and styling. |
194
- | `staticMinify` | `boolean` | `true` | For static builds: route-prune disabled prefixes + cull gated HTML in `dist/`. Set `false` to keep emitted files untouched. |
183
+ | Option | Type | Default | Notes |
184
+ | ------------------ | ------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
185
+ | `configRoot` | `string` | `process.cwd()` | Resolves relative `jsonConfigPath` values (root + per-environment). |
186
+ | `jsonConfigPath` | `string` | unset | Optional **root** JSON file merged after inline config (see merge order for per-environment files). |
187
+ | `forceEnvironment` | `string` | unset | Pin the active layer (skips `when` / `AFF_ENVIRONMENT` validation). |
188
+ | `mode` | `string` | `NODE_ENV` | Optional advanced override for runtime resolution (mainly tests/tooling). Most apps should omit this and rely on `NODE_ENV` + `environments.when`. |
189
+ | `env` | `Record<string, string \| undefined>` | `process.env` | `AFF_FEATURE_*` / `ASTRO_FEATURE_FLAGS` (not applied in `dev` layer). |
190
+ | `tokenNamespace` | `string` | `'ff'` | CSS var namespace (`--ff-c-*`). |
191
+ | `flags` | `Record<string, FlagConfig>` | `{}` | Flag declarations. |
192
+ | `environments` | `Record<string, EnvironmentConfig>` | _(see below)_ | Declare non-`dev` layers only; reserved `dev` is injected. At least one other key; exactly one `when: true` unless forced. |
193
+ | `css` | `DevOutlineCssOptions` | defaults | Global badge/outline layout and styling. |
194
+ | `staticMinify` | `boolean` | `true` | For static builds: route-prune disabled prefixes + cull gated HTML in `dist/`. Set `false` to keep emitted files untouched. |
195
195
 
196
196
  If you omit `environments`, the integration injects a minimal reserved `dev` plus **`prod`** tied to `NODE_ENV` (or `mode` only if you explicitly override it) so `astroFeatureFlags()` still runs in small demos.
197
197
 
@@ -210,10 +210,10 @@ The key **`dev`** is reserved: do not list it under `environments`. The integrat
210
210
 
211
211
  ### `EnvironmentConfig`
212
212
 
213
- | Field | Type | Default | Notes |
214
- | ---------------- | ------------------------- | ------- | --------------------------------------------------------------------- |
215
- | `when` | `boolean` | unset | Exactly one environment must have `when: true` (unless forced). |
216
- | `flags` | `Record<string, boolean>` | `{}` | For non-`dev` layers: booleans per flag. Ignored for reserved `dev`. |
213
+ | Field | Type | Default | Notes |
214
+ | ---------------- | ------------------------- | ------- | -------------------------------------------------------------------------------------------- |
215
+ | `when` | `boolean` | unset | Exactly one environment must have `when: true` (unless forced). |
216
+ | `flags` | `Record<string, boolean>` | `{}` | For non-`dev` layers: booleans per flag. Ignored for reserved `dev`. |
217
217
  | `jsonConfigPath` | `string` | unset | Optional JSON merged when this environment is the active layer (not used on reserved `dev`). |
218
218
 
219
219
  Merge order:
@@ -227,12 +227,12 @@ Layer select override: `AFF_ENVIRONMENT=prod`. **`forceEnvironment`** on the int
227
227
 
228
228
  ### DevOutlineCssOptions
229
229
 
230
- | Field | Default | Meaning |
231
- | ------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
232
- | `elementBadgeHorizontalAlign` | `'end'` | `'start'` \| `'center'` \| `'end'` — LTR: **`end`** = top-right. |
233
- | `elementBadgeHorizontalPercent` | _(unset)_ | 0–100: horizontal anchor with pill centred (`translateX(-50%)`); overrides align. |
234
- | `elementBadgeVerticalShiftPercent` | `80` | Vertical shift as **% of the pill height** (default keeps most of the label above the element). |
235
- | `elementBadgeVerticalAnchor` | `'top'` | `'top'` or `'bottom'`. |
230
+ | Field | Default | Meaning |
231
+ | ---------------------------------- | --------- | ----------------------------------------------------------------------------------------------- |
232
+ | `elementBadgeHorizontalAlign` | `'end'` | `'start'` \| `'center'` \| `'end'` — LTR: **`end`** = top-right. |
233
+ | `elementBadgeHorizontalPercent` | _(unset)_ | 0–100: horizontal anchor with pill centred (`translateX(-50%)`); overrides align. |
234
+ | `elementBadgeVerticalShiftPercent` | `80` | Vertical shift as **% of the pill height** (default keeps most of the label above the element). |
235
+ | `elementBadgeVerticalAnchor` | `'top'` | `'top'` or `'bottom'`. |
236
236
 
237
237
  Per-element badge overrides (markup):
238
238
 
@@ -250,12 +250,12 @@ If your editor misses virtual module types, add one reference in `src/env.d.ts`.
250
250
 
251
251
  ## Dev toolbar
252
252
 
253
- | Control | Effect |
254
- | ----------- | ------------------------------------------------------ |
253
+ | Control | Effect |
254
+ | ----------- | ---------------------------------------------------------------------------------------------------------------- |
255
255
  | **Enabled** | Off → hide nodes carrying that token in `data-ff` (including combined hosts/pages when any member token is Off). |
256
- | **Outline** | Toggle outlines only (element and route frame). For combined hosts this is a non-layout-shifting overlay ring. |
257
- | **Badges** | Toggle badges only (element pills + route pill). |
258
- | **Colour** | `--<namespace>-c-<token>` (persisted). |
256
+ | **Outline** | Toggle outlines only (element and route frame). For combined hosts this is a non-layout-shifting overlay ring. |
257
+ | **Badges** | Toggle badges only (element pills + route pill). |
258
+ | **Colour** | `--<namespace>-c-<token>` (persisted). |
259
259
 
260
260
  ## Virtual module
261
261
 
@@ -275,7 +275,7 @@ Remote percentage rollouts, per-user experiment assignment, analytics, or a host
275
275
 
276
276
  ## How-to
277
277
 
278
- - `docs/how-to/hide-from-sitemaps.md`
278
+ - [Hide feature locked pages from sitemaps in production builds](docs/how-to/hide-from-sitemaps.md)
279
279
 
280
280
  ## Example Pages
281
281
 
@@ -1,5 +1,4 @@
1
1
  import { DevToolbarApp } from "astro";
2
-
3
2
  //#region src/dev-toolbar-app.d.ts
4
3
  declare global {
5
4
  interface Window {
@@ -1 +1 @@
1
- {"version":3,"file":"dev-toolbar-app.d.mts","names":[],"sources":["../src/dev-toolbar-app.ts"],"mappings":";;;QASQ,MAAA;EAAA,UACI,MAAA;IACR,OAAA;MACE,MAAA;MACA,MAAA,GAAS,MAAA;MACT,SAAA;IAAA;EAAA;AAAA;AAAA,cAqHA,gBAAA,EAAkB,aAAA"}
1
+ {"version":3,"file":"dev-toolbar-app.d.mts","names":[],"sources":["../src/dev-toolbar-app.ts"],"mappings":";;QASQ;YACI;IACR;MACE;MACA,SAAS;MACT;;;;cAmHA,kBAAkB"}
@@ -1,4 +1,4 @@
1
- import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-BHCQJ53N.mjs";
1
+ import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-MQsIFekv.mjs";
2
2
  import { defineToolbarApp } from "astro/toolbar";
3
3
  import { closeOnOutsideClick, createWindowElement, synchronizePlacementOnUpdate } from "astro/runtime/client/dev-toolbar/apps/utils/window.js";
4
4
  //#region src/dev-toolbar-app.ts
@@ -1 +1 @@
1
- {"version":3,"file":"dev-toolbar-app.mjs","names":[],"sources":["../src/dev-toolbar-app.ts"],"sourcesContent":["import type { DevToolbarApp } from \"astro\";\nimport { defineToolbarApp } from \"astro/toolbar\";\nimport {\n closeOnOutsideClick,\n createWindowElement,\n synchronizePlacementOnUpdate,\n} from \"astro/runtime/client/dev-toolbar/apps/utils/window.js\";\nimport { DEV_TOOLBAR_FLAG_ICON_SVG } from \"./dev-toolbar-flag-icon\";\n\ndeclare global {\n interface Window {\n __AFF__?: {\n tokens: string[];\n colors?: Record<string, string>;\n namespace?: string;\n };\n }\n}\n\ntype AffState = {\n enabled: Record<string, boolean>;\n outline: Record<string, boolean>;\n badge: Record<string, boolean>;\n colors: Record<string, string>;\n};\n\nfunction toHexColor(raw: string | undefined, fallback: string): string {\n if (!raw) return fallback;\n const s = raw.trim();\n const hex7 = s.match(/^#([0-9a-fA-F]{6})$/);\n if (hex7) return `#${hex7[1]!.toLowerCase()}`;\n const rgb = s.match(/rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);\n if (rgb) {\n const r = Math.min(255, Math.round(Number(rgb[1])));\n const g = Math.min(255, Math.round(Number(rgb[2])));\n const b = Math.min(255, Math.round(Number(rgb[3])));\n return `#${[r, g, b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return fallback;\n}\n\nfunction readMeta(): {\n tokens: string[];\n colors: Record<string, string>;\n namespace: string;\n} {\n try {\n const raw = window.__AFF__;\n const tokens = raw && Array.isArray(raw.tokens) ? raw.tokens : [];\n const colors =\n raw?.colors && typeof raw.colors === \"object\" ? raw.colors : {};\n const namespace = raw?.namespace?.trim() ? raw.namespace.trim() : \"ff\";\n return { tokens, colors, namespace };\n } catch {\n return { tokens: [], colors: {}, namespace: \"ff\" };\n }\n}\n\nfunction storageKey(namespace: string): string {\n return `${namespace}.dev.v1`;\n}\n\nfunction loadState(tokens: string[], namespace: string): AffState {\n const next: AffState = {\n enabled: {},\n outline: {},\n badge: {},\n colors: {},\n };\n for (const t of tokens) {\n next.enabled[t] = true;\n next.outline[t] = true;\n next.badge[t] = true;\n }\n try {\n const raw =\n localStorage.getItem(storageKey(namespace)) ||\n localStorage.getItem(\"aff.dev.v1\");\n if (!raw) return next;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const outline = (parsed.outline ?? parsed.chrome) as\n | Record<string, boolean>\n | undefined;\n const enabled = (parsed.enabled ?? parsed.render) as\n | Record<string, boolean>\n | undefined;\n const badge = parsed.badge as Record<string, boolean> | undefined;\n const colors = parsed.colors as Record<string, string> | undefined;\n if (outline) Object.assign(next.outline, outline);\n if (enabled) Object.assign(next.enabled, enabled);\n if (badge) Object.assign(next.badge, badge);\n if (colors) Object.assign(next.colors, colors);\n } catch {\n /* ignore */\n }\n return next;\n}\n\nfunction persist(state: AffState, namespace: string) {\n try {\n localStorage.setItem(storageKey(namespace), JSON.stringify(state));\n } catch {\n /* ignore */\n }\n}\n\nfunction applyDom(\n tokens: string[],\n state: AffState,\n defaults: Record<string, string>,\n namespace: string,\n) {\n const root = document.documentElement;\n for (const t of tokens) {\n if (state.outline[t] !== false)\n root.removeAttribute(`data-ff-outline-${t}`);\n else root.setAttribute(`data-ff-outline-${t}`, \"off\");\n\n if (state.enabled[t] !== false)\n root.removeAttribute(`data-ff-enabled-${t}`);\n else root.setAttribute(`data-ff-enabled-${t}`, \"off\");\n\n if (state.badge[t] !== false) root.removeAttribute(`data-ff-badge-${t}`);\n else root.setAttribute(`data-ff-badge-${t}`, \"off\");\n\n const col = state.colors[t] ?? defaults[t];\n if (col) root.style.setProperty(`--${namespace}-c-${t}`, col);\n else root.style.removeProperty(`--${namespace}-c-${t}`);\n }\n}\n\nconst affDevToolbarApp: DevToolbarApp = defineToolbarApp({\n init(canvas, eventTarget) {\n let state = loadState([], \"ff\");\n let tokens: string[] = [];\n let namespace = \"ff\";\n\n function mount() {\n canvas.querySelector(\"astro-dev-toolbar-window\")?.remove();\n const {\n tokens: nextTokens,\n colors: defaultColors,\n namespace: ns,\n } = readMeta();\n tokens = nextTokens;\n namespace = ns;\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n\n const windowElement = createWindowElement(`\n\t\t\t\t<style>\n\t\t\t\t\t:host astro-dev-toolbar-window {\n\t\t\t\t\t\theight: min(520px, 78vh);\n\t\t\t\t\t\toverflow-y: auto;\n\t\t\t\t\t\tcolor-scheme: dark;\n\t\t\t\t\t\t--color-muted: rgba(191, 193, 201, 1);\n\t\t\t\t\t\t--color-purple: rgba(224, 204, 250, 1);\n\t\t\t\t\t}\n\t\t\t\t\theader {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t}\n\t\t\t\t\th1 {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 22px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\twidth: 1em;\n\t\t\t\t\t\theight: 1em;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon svg {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-intro {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n margin-bottom: 1rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: flex-start;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 1rem 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top-left {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tmin-width: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help {\n\t\t\t\t\t\tdisplay: grid;\n\t\t\t\t\t\tgrid-template-columns: minmax(7.5rem, auto) 1fr;\n\t\t\t\t\t\tgap: 0.45rem 0.75rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code,\n\t\t\t\t\t.aff-help span {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tline-height: 1.35;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code {\n\t\t\t\t\t\tjustify-self: start;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-actions {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-reset {\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.5rem;\n\t\t\t\t\t\tbackground: #24262d;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tpadding: 0.3rem 0.55rem;\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t}\n\t\t\t\t\thr {\n\t\t\t\t\t\tborder: 1px solid rgba(27, 30, 36, 1);\n\t\t\t\t\t\tmargin: 1em 0;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 0 0 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row section { flex: 1; min-width: 0; max-width: 54%; }\n\t\t\t\t\th3.aff-row-title {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tletter-spacing: 0.05em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios label {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 4px;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tletter-spacing: 0.04em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios input { accent-color: var(--color-purple); cursor: pointer; }\n\t\t\t\t\t.aff-flag {\n\t\t\t\t\t\tmargin: 0 0 12px;\n\t\t\t\t\t\tborder: 1px solid rgba(52, 56, 65, 1);\n\t\t\t\t\t\tborder-radius: 10px;\n\t\t\t\t\t\tpadding: 0 10px 10px;\n\t\t\t\t\t\tbackground: rgba(19, 21, 26, 0.55);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary {\n\t\t\t\t\t\tlist-style: none;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tuser-select: none;\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tpadding: 10px 0;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tletter-spacing: 0.06em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary::-webkit-details-marker { display: none; }\n\t\t\t\t\t.aff-flag summary::after {\n\t\t\t\t\t\tcontent: '▸';\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t\tmargin-left: auto;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\ttransform: rotate(0deg);\n\t\t\t\t\t\ttransition: transform 0.12s ease;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag[open] summary::after {\n\t\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-summary-left {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-swatch {\n\t\t\t\t\t\twidth: 10px;\n\t\t\t\t\t\theight: 10px;\n\t\t\t\t\t\tborder-radius: 9999px;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\tbox-shadow: inset 0 0 0 1px rgba(255,255,255,0.12);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-body { padding-top: 2px; }\n\t\t\t\t\t.aff-empty {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t}\n\t\t\t\t\tcode {\n\t\t\t\t\t\tcolor: var(--color-purple);\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.35em;\n\t\t\t\t\t\tbackground-color: #24262d;\n\t\t\t\t\t\tpadding: 0.15em 0.35em;\n\t\t\t\t\t\tfont-size: 0.9em;\n\t\t\t\t\t}\n\t\t\t\t\tinput[type=\"color\"].aff-color {\n\t\t\t\t\t\twidth: 2.25rem;\n\t\t\t\t\t\theight: 1.75rem;\n\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 6px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tbackground: transparent;\n\t\t\t\t\t}\n\t\t\t\t</style>\n\t\t\t\t<header>\n\t\t\t\t\t<h1><span class=\"aff-flag-toolbar-icon\">${DEV_TOOLBAR_FLAG_ICON_SVG}</span> Feature flags</h1>\n\t\t\t\t\t<div class=\"aff-actions\"><button id=\"aff-reset\" class=\"aff-reset\" type=\"button\" title=\"Reset all flags to defaults\">Reset all</button></div>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"aff-top\">\n\t\t\t\t\t<div class=\"aff-top-left\">\n\t\t\t\t\t\t<p class=\"aff-intro\">Per-flag preview for <code>data-${namespace}</code> regions.</p>\n\t\t\t\t<div class=\"aff-help\">\n\t\t\t\t\t<code>Enabled</code><span>Show/hide flagged regions in preview.</span>\n\t\t\t\t\t<code>Outline</code><span>Toggle flag outlines without changing layout.</span>\n\t\t\t\t\t<code>Badges</code><span>Toggle element and route labels.</span>\n\t\t\t\t\t<code>Colour</code><span>Set <code>--${namespace}-c-&lt;token&gt;</code> on <code>&lt;html&gt;</code>.</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<hr />\n\t\t\t\t<div id=\"aff-body\"></div>\n\t\t\t`);\n\n const body = windowElement.querySelector(\"#aff-body\");\n const resetBtn = windowElement.querySelector(\n \"#aff-reset\",\n ) as HTMLButtonElement | null;\n\n if (body) {\n if (tokens.length === 0) {\n const p = document.createElement(\"p\");\n p.className = \"aff-empty\";\n p.textContent = \"No flags in ff.json.\";\n body.append(p);\n } else {\n for (const token of tokens) {\n const details = document.createElement(\"details\");\n details.className = \"aff-flag\";\n details.open = tokens.length <= 8;\n\n const summary = document.createElement(\"summary\");\n const summaryLeft = document.createElement(\"span\");\n summaryLeft.className = \"aff-flag-summary-left\";\n const swatch = document.createElement(\"span\");\n swatch.className = \"aff-swatch\";\n const name = document.createElement(\"span\");\n name.textContent = token;\n summaryLeft.append(swatch, name);\n summary.append(summaryLeft);\n\n const syncSwatch = () => {\n const c =\n state.colors[token] ?? defaultColors[token] ?? \"#888888\";\n swatch.style.background = c;\n };\n syncSwatch();\n\n const inner = document.createElement(\"div\");\n inner.className = \"aff-flag-body\";\n\n const row = (\n title: string,\n tooltip: string,\n key: keyof Pick<AffState, \"enabled\" | \"outline\" | \"badge\">,\n onLab: string,\n offLab: string,\n ) => {\n const label = document.createElement(\"label\");\n label.className = \"setting-row\";\n const section = document.createElement(\"section\");\n const h = document.createElement(\"h3\");\n h.className = \"aff-row-title\";\n h.textContent = title;\n section.append(h);\n const radios = document.createElement(\"div\");\n radios.className = \"aff-radios\";\n const mk = (on: boolean, lab: string) => {\n const wrap = document.createElement(\"label\");\n const inp = document.createElement(\"input\");\n inp.type = \"radio\";\n inp.name = `aff-${token}-${key}`;\n const cur = state[key][token] !== false;\n inp.checked = on ? cur : !cur;\n inp.addEventListener(\"change\", () => {\n if (!inp.checked) return;\n state[key][token] = on;\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n wrap.append(inp, document.createTextNode(lab));\n return wrap;\n };\n radios.append(mk(true, onLab), mk(false, offLab));\n label.append(section, radios);\n return label;\n };\n\n inner.append(\n row(\n \"Enabled\",\n \"When off, nodes with this flag token are hidden in preview.\",\n \"enabled\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Outline\",\n \"Toggles dashed outline around flagged elements.\",\n \"outline\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Badges\",\n \"Toggles element badges and top-right route pill.\",\n \"badge\",\n \"On\",\n \"Off\",\n ),\n );\n\n const colorRow = document.createElement(\"label\");\n colorRow.className = \"setting-row\";\n const csection = document.createElement(\"section\");\n const ch = document.createElement(\"h3\");\n ch.className = \"aff-row-title\";\n ch.textContent = \"Colour\";\n ch.title = `Maps to CSS --${namespace}-c-<token> on <html>.`;\n csection.append(ch);\n const cinp = document.createElement(\"input\");\n cinp.type = \"color\";\n cinp.className = \"aff-color\";\n cinp.title = `Set --${namespace}-c-${token}`;\n cinp.value = toHexColor(\n state.colors[token] ?? defaultColors[token],\n \"#888888\",\n );\n cinp.addEventListener(\"input\", () => {\n state.colors[token] = cinp.value;\n syncSwatch();\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n colorRow.append(csection, cinp);\n inner.append(colorRow);\n\n details.append(summary, inner);\n body.append(details);\n }\n }\n }\n\n if (resetBtn) {\n resetBtn.addEventListener(\"click\", () => {\n try {\n localStorage.removeItem(storageKey(namespace));\n localStorage.removeItem(\"aff.dev.v1\");\n } catch {\n // ignore\n }\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n mount();\n });\n }\n\n canvas.append(windowElement);\n }\n\n mount();\n document.addEventListener(\"astro:after-swap\", mount);\n closeOnOutsideClick(eventTarget);\n synchronizePlacementOnUpdate(eventTarget, canvas);\n },\n});\n\nexport default affDevToolbarApp;\n"],"mappings":"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"}
1
+ {"version":3,"file":"dev-toolbar-app.mjs","names":[],"sources":["../src/dev-toolbar-app.ts"],"sourcesContent":["import type { DevToolbarApp } from \"astro\";\nimport { defineToolbarApp } from \"astro/toolbar\";\nimport {\n closeOnOutsideClick,\n createWindowElement,\n synchronizePlacementOnUpdate,\n} from \"astro/runtime/client/dev-toolbar/apps/utils/window.js\";\nimport { DEV_TOOLBAR_FLAG_ICON_SVG } from \"./dev-toolbar-flag-icon\";\n\ndeclare global {\n interface Window {\n __AFF__?: {\n tokens: string[];\n colors?: Record<string, string>;\n namespace?: string;\n };\n }\n}\n\ntype AffState = {\n enabled: Record<string, boolean>;\n outline: Record<string, boolean>;\n badge: Record<string, boolean>;\n colors: Record<string, string>;\n};\n\nfunction toHexColor(raw: string | undefined, fallback: string): string {\n if (!raw) return fallback;\n const s = raw.trim();\n const hex7 = s.match(/^#([0-9a-fA-F]{6})$/);\n if (hex7) return `#${hex7[1]!.toLowerCase()}`;\n const rgb = s.match(/rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);\n if (rgb) {\n const r = Math.min(255, Math.round(Number(rgb[1])));\n const g = Math.min(255, Math.round(Number(rgb[2])));\n const b = Math.min(255, Math.round(Number(rgb[3])));\n return `#${[r, g, b].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return fallback;\n}\n\nfunction readMeta(): {\n tokens: string[];\n colors: Record<string, string>;\n namespace: string;\n} {\n try {\n const raw = window.__AFF__;\n const tokens = raw && Array.isArray(raw.tokens) ? raw.tokens : [];\n const colors =\n raw?.colors && typeof raw.colors === \"object\" ? raw.colors : {};\n const namespace = raw?.namespace?.trim() ? raw.namespace.trim() : \"ff\";\n return { tokens, colors, namespace };\n } catch {\n return { tokens: [], colors: {}, namespace: \"ff\" };\n }\n}\n\nfunction storageKey(namespace: string): string {\n return `${namespace}.dev.v1`;\n}\n\nfunction loadState(tokens: string[], namespace: string): AffState {\n const next: AffState = {\n enabled: {},\n outline: {},\n badge: {},\n colors: {},\n };\n for (const t of tokens) {\n next.enabled[t] = true;\n next.outline[t] = true;\n next.badge[t] = true;\n }\n try {\n const raw =\n localStorage.getItem(storageKey(namespace)) ||\n localStorage.getItem(\"aff.dev.v1\");\n if (!raw) return next;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const outline = (parsed.outline ?? parsed.chrome) as\n Record<string, boolean> | undefined;\n const enabled = (parsed.enabled ?? parsed.render) as\n Record<string, boolean> | undefined;\n const badge = parsed.badge as Record<string, boolean> | undefined;\n const colors = parsed.colors as Record<string, string> | undefined;\n if (outline) Object.assign(next.outline, outline);\n if (enabled) Object.assign(next.enabled, enabled);\n if (badge) Object.assign(next.badge, badge);\n if (colors) Object.assign(next.colors, colors);\n } catch {\n /* ignore */\n }\n return next;\n}\n\nfunction persist(state: AffState, namespace: string) {\n try {\n localStorage.setItem(storageKey(namespace), JSON.stringify(state));\n } catch {\n /* ignore */\n }\n}\n\nfunction applyDom(\n tokens: string[],\n state: AffState,\n defaults: Record<string, string>,\n namespace: string,\n) {\n const root = document.documentElement;\n for (const t of tokens) {\n if (state.outline[t] !== false)\n root.removeAttribute(`data-ff-outline-${t}`);\n else root.setAttribute(`data-ff-outline-${t}`, \"off\");\n\n if (state.enabled[t] !== false)\n root.removeAttribute(`data-ff-enabled-${t}`);\n else root.setAttribute(`data-ff-enabled-${t}`, \"off\");\n\n if (state.badge[t] !== false) root.removeAttribute(`data-ff-badge-${t}`);\n else root.setAttribute(`data-ff-badge-${t}`, \"off\");\n\n const col = state.colors[t] ?? defaults[t];\n if (col) root.style.setProperty(`--${namespace}-c-${t}`, col);\n else root.style.removeProperty(`--${namespace}-c-${t}`);\n }\n}\n\nconst affDevToolbarApp: DevToolbarApp = defineToolbarApp({\n init(canvas, eventTarget) {\n let state = loadState([], \"ff\");\n let tokens: string[] = [];\n let namespace = \"ff\";\n\n function mount() {\n canvas.querySelector(\"astro-dev-toolbar-window\")?.remove();\n const {\n tokens: nextTokens,\n colors: defaultColors,\n namespace: ns,\n } = readMeta();\n tokens = nextTokens;\n namespace = ns;\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n\n const windowElement = createWindowElement(`\n\t\t\t\t<style>\n\t\t\t\t\t:host astro-dev-toolbar-window {\n\t\t\t\t\t\theight: min(520px, 78vh);\n\t\t\t\t\t\toverflow-y: auto;\n\t\t\t\t\t\tcolor-scheme: dark;\n\t\t\t\t\t\t--color-muted: rgba(191, 193, 201, 1);\n\t\t\t\t\t\t--color-purple: rgba(224, 204, 250, 1);\n\t\t\t\t\t}\n\t\t\t\t\theader {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t}\n\t\t\t\t\th1 {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 22px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\twidth: 1em;\n\t\t\t\t\t\theight: 1em;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-toolbar-icon svg {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-intro {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n margin-bottom: 1rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: flex-start;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 1rem 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-top-left {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tmin-width: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help {\n\t\t\t\t\t\tdisplay: grid;\n\t\t\t\t\t\tgrid-template-columns: minmax(7.5rem, auto) 1fr;\n\t\t\t\t\t\tgap: 0.45rem 0.75rem;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code,\n\t\t\t\t\t.aff-help span {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tline-height: 1.35;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-help code {\n\t\t\t\t\t\tjustify-self: start;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-actions {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-reset {\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.5rem;\n\t\t\t\t\t\tbackground: #24262d;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tpadding: 0.3rem 0.55rem;\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t}\n\t\t\t\t\thr {\n\t\t\t\t\t\tborder: 1px solid rgba(27, 30, 36, 1);\n\t\t\t\t\t\tmargin: 1em 0;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 12px;\n\t\t\t\t\t\tmargin: 0 0 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.setting-row section { flex: 1; min-width: 0; max-width: 54%; }\n\t\t\t\t\th3.aff-row-title {\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tletter-spacing: 0.05em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios label {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 4px;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tletter-spacing: 0.04em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-radios input { accent-color: var(--color-purple); cursor: pointer; }\n\t\t\t\t\t.aff-flag {\n\t\t\t\t\t\tmargin: 0 0 12px;\n\t\t\t\t\t\tborder: 1px solid rgba(52, 56, 65, 1);\n\t\t\t\t\t\tborder-radius: 10px;\n\t\t\t\t\t\tpadding: 0 10px 10px;\n\t\t\t\t\t\tbackground: rgba(19, 21, 26, 0.55);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary {\n\t\t\t\t\t\tlist-style: none;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tuser-select: none;\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t\tpadding: 10px 0;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t\tletter-spacing: 0.06em;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t\tcolor: #fff;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag summary::-webkit-details-marker { display: none; }\n\t\t\t\t\t.aff-flag summary::after {\n\t\t\t\t\t\tcontent: '▸';\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t\tmargin-left: auto;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\ttransform: rotate(0deg);\n\t\t\t\t\t\ttransition: transform 0.12s ease;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag[open] summary::after {\n\t\t\t\t\t\ttransform: rotate(90deg);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-summary-left {\n\t\t\t\t\t\tdisplay: inline-flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tgap: 8px;\n\t\t\t\t\t}\n\t\t\t\t\t.aff-swatch {\n\t\t\t\t\t\twidth: 10px;\n\t\t\t\t\t\theight: 10px;\n\t\t\t\t\t\tborder-radius: 9999px;\n\t\t\t\t\t\tflex-shrink: 0;\n\t\t\t\t\t\tbox-shadow: inset 0 0 0 1px rgba(255,255,255,0.12);\n\t\t\t\t\t}\n\t\t\t\t\t.aff-flag-body { padding-top: 2px; }\n\t\t\t\t\t.aff-empty {\n\t\t\t\t\t\tmargin: 0;\n\t\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\t\tline-height: 1.5rem;\n\t\t\t\t\t\tcolor: var(--color-muted);\n\t\t\t\t\t}\n\t\t\t\t\tcode {\n\t\t\t\t\t\tcolor: var(--color-purple);\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 0.35em;\n\t\t\t\t\t\tbackground-color: #24262d;\n\t\t\t\t\t\tpadding: 0.15em 0.35em;\n\t\t\t\t\t\tfont-size: 0.9em;\n\t\t\t\t\t}\n\t\t\t\t\tinput[type=\"color\"].aff-color {\n\t\t\t\t\t\twidth: 2.25rem;\n\t\t\t\t\t\theight: 1.75rem;\n\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\tborder: 1px solid #343841;\n\t\t\t\t\t\tborder-radius: 6px;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t\tbackground: transparent;\n\t\t\t\t\t}\n\t\t\t\t</style>\n\t\t\t\t<header>\n\t\t\t\t\t<h1><span class=\"aff-flag-toolbar-icon\">${DEV_TOOLBAR_FLAG_ICON_SVG}</span> Feature flags</h1>\n\t\t\t\t\t<div class=\"aff-actions\"><button id=\"aff-reset\" class=\"aff-reset\" type=\"button\" title=\"Reset all flags to defaults\">Reset all</button></div>\n\t\t\t\t</header>\n\t\t\t\t<div class=\"aff-top\">\n\t\t\t\t\t<div class=\"aff-top-left\">\n\t\t\t\t\t\t<p class=\"aff-intro\">Per-flag preview for <code>data-${namespace}</code> regions.</p>\n\t\t\t\t<div class=\"aff-help\">\n\t\t\t\t\t<code>Enabled</code><span>Show/hide flagged regions in preview.</span>\n\t\t\t\t\t<code>Outline</code><span>Toggle flag outlines without changing layout.</span>\n\t\t\t\t\t<code>Badges</code><span>Toggle element and route labels.</span>\n\t\t\t\t\t<code>Colour</code><span>Set <code>--${namespace}-c-&lt;token&gt;</code> on <code>&lt;html&gt;</code>.</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<hr />\n\t\t\t\t<div id=\"aff-body\"></div>\n\t\t\t`);\n\n const body = windowElement.querySelector(\"#aff-body\");\n const resetBtn = windowElement.querySelector(\n \"#aff-reset\",\n ) as HTMLButtonElement | null;\n\n if (body) {\n if (tokens.length === 0) {\n const p = document.createElement(\"p\");\n p.className = \"aff-empty\";\n p.textContent = \"No flags in ff.json.\";\n body.append(p);\n } else {\n for (const token of tokens) {\n const details = document.createElement(\"details\");\n details.className = \"aff-flag\";\n details.open = tokens.length <= 8;\n\n const summary = document.createElement(\"summary\");\n const summaryLeft = document.createElement(\"span\");\n summaryLeft.className = \"aff-flag-summary-left\";\n const swatch = document.createElement(\"span\");\n swatch.className = \"aff-swatch\";\n const name = document.createElement(\"span\");\n name.textContent = token;\n summaryLeft.append(swatch, name);\n summary.append(summaryLeft);\n\n const syncSwatch = () => {\n const c =\n state.colors[token] ?? defaultColors[token] ?? \"#888888\";\n swatch.style.background = c;\n };\n syncSwatch();\n\n const inner = document.createElement(\"div\");\n inner.className = \"aff-flag-body\";\n\n const row = (\n title: string,\n tooltip: string,\n key: keyof Pick<AffState, \"enabled\" | \"outline\" | \"badge\">,\n onLab: string,\n offLab: string,\n ) => {\n const label = document.createElement(\"label\");\n label.className = \"setting-row\";\n const section = document.createElement(\"section\");\n const h = document.createElement(\"h3\");\n h.className = \"aff-row-title\";\n h.textContent = title;\n section.append(h);\n const radios = document.createElement(\"div\");\n radios.className = \"aff-radios\";\n const mk = (on: boolean, lab: string) => {\n const wrap = document.createElement(\"label\");\n const inp = document.createElement(\"input\");\n inp.type = \"radio\";\n inp.name = `aff-${token}-${key}`;\n const cur = state[key][token] !== false;\n inp.checked = on ? cur : !cur;\n inp.addEventListener(\"change\", () => {\n if (!inp.checked) return;\n state[key][token] = on;\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n wrap.append(inp, document.createTextNode(lab));\n return wrap;\n };\n radios.append(mk(true, onLab), mk(false, offLab));\n label.append(section, radios);\n return label;\n };\n\n inner.append(\n row(\n \"Enabled\",\n \"When off, nodes with this flag token are hidden in preview.\",\n \"enabled\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Outline\",\n \"Toggles dashed outline around flagged elements.\",\n \"outline\",\n \"On\",\n \"Off\",\n ),\n row(\n \"Badges\",\n \"Toggles element badges and top-right route pill.\",\n \"badge\",\n \"On\",\n \"Off\",\n ),\n );\n\n const colorRow = document.createElement(\"label\");\n colorRow.className = \"setting-row\";\n const csection = document.createElement(\"section\");\n const ch = document.createElement(\"h3\");\n ch.className = \"aff-row-title\";\n ch.textContent = \"Colour\";\n ch.title = `Maps to CSS --${namespace}-c-<token> on <html>.`;\n csection.append(ch);\n const cinp = document.createElement(\"input\");\n cinp.type = \"color\";\n cinp.className = \"aff-color\";\n cinp.title = `Set --${namespace}-c-${token}`;\n cinp.value = toHexColor(\n state.colors[token] ?? defaultColors[token],\n \"#888888\",\n );\n cinp.addEventListener(\"input\", () => {\n state.colors[token] = cinp.value;\n syncSwatch();\n applyDom(tokens, state, defaultColors, namespace);\n persist(state, namespace);\n });\n colorRow.append(csection, cinp);\n inner.append(colorRow);\n\n details.append(summary, inner);\n body.append(details);\n }\n }\n }\n\n if (resetBtn) {\n resetBtn.addEventListener(\"click\", () => {\n try {\n localStorage.removeItem(storageKey(namespace));\n localStorage.removeItem(\"aff.dev.v1\");\n } catch {\n // ignore\n }\n state = loadState(tokens, namespace);\n applyDom(tokens, state, defaultColors, namespace);\n mount();\n });\n }\n\n canvas.append(windowElement);\n }\n\n mount();\n document.addEventListener(\"astro:after-swap\", mount);\n closeOnOutsideClick(eventTarget);\n synchronizePlacementOnUpdate(eventTarget, canvas);\n },\n});\n\nexport default affDevToolbarApp;\n"],"mappings":"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"}
@@ -7,4 +7,4 @@ const DEV_TOOLBAR_FLAG_ICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" vie
7
7
  //#endregion
8
8
  export { DEV_TOOLBAR_FLAG_ICON_SVG as t };
9
9
 
10
- //# sourceMappingURL=dev-toolbar-flag-icon-BHCQJ53N.mjs.map
10
+ //# sourceMappingURL=dev-toolbar-flag-icon-MQsIFekv.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev-toolbar-flag-icon-BHCQJ53N.mjs","names":[],"sources":["../src/dev-toolbar-flag-icon.ts"],"sourcesContent":["/**\n * Inline SVG for the dev toolbar app button and panel header (Heroicons-style flag, 20×20 in a 22×22 box).\n * `currentColor` matches built-in Astro toolbar icons.\n */\nexport const DEV_TOOLBAR_FLAG_ICON_SVG =\n '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\" fill=\"currentColor\" aria-hidden=\"true\">' +\n '<path d=\"M4.25 3.25v15.5a.75.75 0 1 1-1.5 0V3.25a.75.75 0 0 1 1.5 0Z\"/>' +\n '<path d=\"M6.5 4.75v9.2a1 1 0 0 0 1.5.87l7.5-4.6a1 1 0 0 0 0-1.74l-7.5-4.6a1 1 0 0 0-1.5.87Z\"/>' +\n \"</svg>\";\n"],"mappings":";;;;;AAIA,MAAa,4BACX"}
1
+ {"version":3,"file":"dev-toolbar-flag-icon-MQsIFekv.mjs","names":[],"sources":["../src/dev-toolbar-flag-icon.ts"],"sourcesContent":["/**\n * Inline SVG for the dev toolbar app button and panel header (Heroicons-style flag, 20×20 in a 22×22 box).\n * `currentColor` matches built-in Astro toolbar icons.\n */\nexport const DEV_TOOLBAR_FLAG_ICON_SVG =\n '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\" fill=\"currentColor\" aria-hidden=\"true\">' +\n '<path d=\"M4.25 3.25v15.5a.75.75 0 1 1-1.5 0V3.25a.75.75 0 0 1 1.5 0Z\"/>' +\n '<path d=\"M6.5 4.75v9.2a1 1 0 0 0 1.5.87l7.5-4.6a1 1 0 0 0 0-1.74l-7.5-4.6a1 1 0 0 0-1.5.87Z\"/>' +\n \"</svg>\";\n"],"mappings":";;;;;AAIA,MAAa,4BACX"}
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { FeatureFlagMap, ResolveFeatureRuntimeOptions, ResolvedFeatureRuntime, longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, resolveFeatureFlagsByEnvironment, routePatternToPrefix } from "./runtime.mjs";
2
-
3
2
  //#region src/badge-layout.d.ts
4
3
  type ElementBadgeVerticalAnchor = "top" | "bottom";
5
4
  /** Horizontal placement of the element pill: `end` = top-right (LTR), `start` = top-left, `center` = centred. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/badge-layout.ts","../src/dev-outline-css.ts","../src/production-html-cull.ts","../src/index.ts"],"mappings":";;;KAAY,0BAAA;;KAGA,2BAAA;AAHZ;AAAA,UAMiB,yBAAA;;;;AAHjB;EAQE,2BAAA,GAA8B,2BAAA;;;;AALhC;EAUE,6BAAA;;;;;EAKA,gCAAA;EAAA;EAEA,0BAAA,GAA6B,0BAAA;AAAA;AAAA,KAGnB,4BAAA;EAEN,IAAA;EACA,eAAA,EAAiB,2BAAA;EACjB,oBAAA;EACA,cAAA,EAAgB,0BAAA;AAAA;EAGhB,IAAA;EACA,iBAAA;EACA,oBAAA;EACA,cAAA,EAAgB,0BAAA;AAAA;AAAA,iBAQN,2BAAA,CACd,KAAA,EAAO,yBAAA,eACN,4BAAA;;;;iBA+Ba,yBAAA,CACd,MAAA,EAAQ,4BAAA;;;KCrEE,wBAAA;AAAA,UAEK,oBAAA,SAA6B,yBAAA;EAC5C,YAAA;;EAEA,YAAA;EDfoC;ECiBpC,mBAAA,GAAsB,MAAA;EACtB,aAAA;EACA,YAAA;EDhBqC;ECmBrC,aAAA;EDhBe;ECkBf,aAAA;EACA,iBAAA,GAAoB,MAAA;EAEpB,cAAA,GAAiB,wBAAA;EDhBjB;;;ECqBA,kBAAA;AAAA;;;;ADNF;;iBC0EgB,uBAAA,CACd,OAAA,EAAS,sBAAA,EACT,GAAA,GAAM,oBAAA;;;;;iBAuTQ,0BAAA,CACd,OAAA,EAAS,sBAAA;;;;;AD9ZX;;;iBEoFgB,kBAAA,CACd,IAAA,UACA,OAAA,EAAS,sBAAA;;AFnFX;;iBEwHgB,6BAAA,CACd,MAAA,UACA,OAAA,EAAS,sBAAA;;;UCtEM,wBAAA,SAAiC,4BAAA;EAChD,GAAA,GAAM,oBAAA;EHrD+B;EGuDrC,YAAA;AAAA;;iBAIc,2BAAA,CACd,kBAAA,EAAoB,MAAA,SAAe,cAAA;AAAA,iBAmCrB,yBAAA,CACd,OAAA,EAAS,sBAAA,EACT,GAAA,GAAM,oBAAA,EACN,kBAAA,GAAoB,MAAA,SAAe,cAAA,GACnC,YAAA;AAAA,iBA4Kc,mBAAA,CACd,OAAA,GAAS,4BAAA,GACR,sBAAA;AAAA,iBAIa,oBAAA,CACd,QAAA,UACA,OAAA,EAAS,sBAAA;AAAA,iBAkBa,iBAAA,CACtB,OAAA,GAAS,wBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/badge-layout.ts","../src/dev-outline-css.ts","../src/production-html-cull.ts","../src/index.ts"],"mappings":";;KAAY;;KAGA;;UAGK;;;;;EAKf,8BAA8B;;;;;EAK9B;;;;;EAKA;;EAEA,6BAA6B;;KAGnB;EAEN;EACA,iBAAiB;EACjB;EACA,gBAAgB;;EAGhB;EACA;EACA;EACA,gBAAgB;;iBAQN,4BACd,OAAO,wCACN;;;;iBA+Ba,0BACd,QAAQ;;;KCrEE;UAEK,6BAA6B;EAC5C;;EAEA;;EAEA,sBAAsB;EACtB;EACA;;EAGA;;EAEA;EACA,oBAAoB;EAEpB,iBAAiB;;;;EAKjB;;;;;;;iBAsEc,wBACd,SAAS,wBACT,MAAM;;;;;iBAyTQ,2BACd,SAAS;;;;;;;;iBC9UK,mBACd,cACA,SAAS;;;;iBAqCK,8BACd,gBACA,SAAS;;;UCtEM,iCAAiC;EAChD,MAAM;;EAEN;;;iBAIc,4BACd,oBAAoB,eAAe;iBAoCrB,0BACd,SAAS,wBACT,MAAM,sBACN,qBAAoB,eAAe,iBACnC;iBA8Kc,oBACd,UAAS,+BACR;iBAIa,qBACd,kBACA,SAAS;iBAkBa,kBACtB,UAAS"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-BHCQJ53N.mjs";
1
+ import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-MQsIFekv.mjs";
2
2
  import { longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, resolveFeatureFlagsByEnvironment, resolveFeatureRuntime, routePathsToPrune, routePatternToPrefix, shouldIncludeRoute, toEnumKey, toToken } from "./runtime.mjs";
3
3
  import { readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
@@ -102,10 +102,15 @@ function affHeadInlineRuntime(payload) {
102
102
  };
103
103
  try {
104
104
  const { featureFlagStyles, routeFlags: RF, flagNameToToken: M } = payload;
105
- const s = document.createElement("style");
106
- s.setAttribute("data-astro-feature-flags", "");
107
- s.textContent = featureFlagStyles;
108
- (document.head || document.documentElement).appendChild(s);
105
+ const ensureFeatureFlagStyles = () => {
106
+ if (document.querySelector("style[data-astro-feature-flags]")) return;
107
+ const s = document.createElement("style");
108
+ s.setAttribute("data-astro-feature-flags", "");
109
+ s.setAttribute("data-astro-transition-persist", "astro-feature-flags");
110
+ s.textContent = featureFlagStyles;
111
+ (document.head || document.documentElement).appendChild(s);
112
+ };
113
+ ensureFeatureFlagStyles();
109
114
  const affPath = () => {
110
115
  let p = typeof location !== "undefined" && location.pathname ? location.pathname : "/";
111
116
  if (!p.endsWith("/")) p += "/";
@@ -163,6 +168,7 @@ function affHeadInlineRuntime(payload) {
163
168
  pEl.textContent = `${disabled.length === 1 ? `Page disabled: ${flagsText} flag is disabled.` : `Page disabled: ${flagsText} flags are disabled.`}\nIn production builds, pages with disabled flags like this will not be emitted`;
164
169
  };
165
170
  const affApply = () => {
171
+ ensureFeatureFlagStyles();
166
172
  document.documentElement.setAttribute("data-ff-route", affTokens(affPath()).join(" "));
167
173
  affSyncDisabledOverlay();
168
174
  };
@@ -1068,11 +1074,14 @@ function astroFeatureFlags(options = {}) {
1068
1074
  icon: DEV_TOOLBAR_FLAG_ICON_SVG,
1069
1075
  entrypoint: fileURLToPath(new URL("./dev-toolbar-app.mjs", import.meta.url))
1070
1076
  });
1071
- if (command === "dev" && runtime.isDev && typeof injectScript === "function") injectScript("head-inline", buildAffDevHeadInline({
1072
- runtime,
1073
- featureFlagStyles: createFeatureFlagStyles(runtime, css),
1074
- affDevBootstrap: bootstrap
1075
- }));
1077
+ if (command === "dev" && runtime.isDev && typeof injectScript === "function") {
1078
+ const styles = createFeatureFlagStyles(runtime, css);
1079
+ injectScript("head-inline", buildAffDevHeadInline({
1080
+ runtime,
1081
+ featureFlagStyles: styles,
1082
+ affDevBootstrap: bootstrap
1083
+ }));
1084
+ }
1076
1085
  updateConfig({ vite: { plugins: [{
1077
1086
  name: "astro-feature-flags:virtual-module",
1078
1087
  resolveId(id) {