@at-flux/astro-feature-flags 1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/dist/dev-toolbar-app.d.mts +16 -0
  4. package/dist/dev-toolbar-app.d.mts.map +1 -0
  5. package/dist/dev-toolbar-app.mjs +407 -0
  6. package/dist/dev-toolbar-app.mjs.map +1 -0
  7. package/dist/dev-toolbar-flag-icon-BHCQJ53N.mjs +10 -0
  8. package/dist/dev-toolbar-flag-icon-BHCQJ53N.mjs.map +1 -0
  9. package/dist/index.d.mts +103 -0
  10. package/dist/index.d.mts.map +1 -0
  11. package/dist/index.mjs +1107 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/dist/runtime.d.mts +115 -0
  14. package/dist/runtime.d.mts.map +1 -0
  15. package/dist/runtime.mjs +280 -0
  16. package/dist/runtime.mjs.map +1 -0
  17. package/docs/assets/element-gating.png +0 -0
  18. package/docs/assets/page-gating.png +0 -0
  19. package/docs/assets/toolbar-feature-flags.png +0 -0
  20. package/docs/how-to/hide-from-sitemaps.md +49 -0
  21. package/docs/testing.md +40 -0
  22. package/example/README.md +18 -0
  23. package/example/astro.config.mjs +26 -0
  24. package/example/ff.json +26 -0
  25. package/example/package.json +22 -0
  26. package/example/pnpm-lock.yaml +4022 -0
  27. package/example/src/env.d.ts +2 -0
  28. package/example/src/layouts/Layout.astro +21 -0
  29. package/example/src/pages/hot/index.astro +23 -0
  30. package/example/src/pages/hot-dev/index.astro +18 -0
  31. package/example/src/pages/hot-dev/sub/index.astro +21 -0
  32. package/example/src/pages/hot-feature-1/index.astro +38 -0
  33. package/example/src/pages/index.astro +102 -0
  34. package/example/src/styles/global.css +22 -0
  35. package/example/tsconfig.json +4 -0
  36. package/package.json +68 -0
  37. package/src/badge-layout.ts +146 -0
  38. package/src/dev-head-inject.ts +26 -0
  39. package/src/dev-inline-runtimes.ts +397 -0
  40. package/src/dev-outline-css.ts +449 -0
  41. package/src/dev-toolbar-app.ts +516 -0
  42. package/src/dev-toolbar-flag-icon.ts +9 -0
  43. package/src/index.ts +417 -0
  44. package/src/inline-script.ts +14 -0
  45. package/src/production-html-cull.ts +133 -0
  46. package/src/route-prefix-js.ts +7 -0
  47. package/src/runtime.ts +575 -0
  48. package/virtual-astro-feature-flags.d.ts +67 -0
@@ -0,0 +1,407 @@
1
+ import { t as DEV_TOOLBAR_FLAG_ICON_SVG } from "./dev-toolbar-flag-icon-BHCQJ53N.mjs";
2
+ import { defineToolbarApp } from "astro/toolbar";
3
+ import { closeOnOutsideClick, createWindowElement, synchronizePlacementOnUpdate } from "astro/runtime/client/dev-toolbar/apps/utils/window.js";
4
+ //#region src/dev-toolbar-app.ts
5
+ function toHexColor(raw, fallback) {
6
+ if (!raw) return fallback;
7
+ const s = raw.trim();
8
+ const hex7 = s.match(/^#([0-9a-fA-F]{6})$/);
9
+ if (hex7) return `#${hex7[1].toLowerCase()}`;
10
+ const rgb = s.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
11
+ if (rgb) return `#${[
12
+ Math.min(255, Math.round(Number(rgb[1]))),
13
+ Math.min(255, Math.round(Number(rgb[2]))),
14
+ Math.min(255, Math.round(Number(rgb[3])))
15
+ ].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
16
+ return fallback;
17
+ }
18
+ function readMeta() {
19
+ try {
20
+ const raw = window.__AFF__;
21
+ return {
22
+ tokens: raw && Array.isArray(raw.tokens) ? raw.tokens : [],
23
+ colors: raw?.colors && typeof raw.colors === "object" ? raw.colors : {},
24
+ namespace: raw?.namespace?.trim() ? raw.namespace.trim() : "ff"
25
+ };
26
+ } catch {
27
+ return {
28
+ tokens: [],
29
+ colors: {},
30
+ namespace: "ff"
31
+ };
32
+ }
33
+ }
34
+ function storageKey(namespace) {
35
+ return `${namespace}.dev.v1`;
36
+ }
37
+ function loadState(tokens, namespace) {
38
+ const next = {
39
+ enabled: {},
40
+ outline: {},
41
+ badge: {},
42
+ colors: {}
43
+ };
44
+ for (const t of tokens) {
45
+ next.enabled[t] = true;
46
+ next.outline[t] = true;
47
+ next.badge[t] = true;
48
+ }
49
+ try {
50
+ const raw = localStorage.getItem(storageKey(namespace)) || localStorage.getItem("aff.dev.v1");
51
+ if (!raw) return next;
52
+ const parsed = JSON.parse(raw);
53
+ const outline = parsed.outline ?? parsed.chrome;
54
+ const enabled = parsed.enabled ?? parsed.render;
55
+ const badge = parsed.badge;
56
+ const colors = parsed.colors;
57
+ if (outline) Object.assign(next.outline, outline);
58
+ if (enabled) Object.assign(next.enabled, enabled);
59
+ if (badge) Object.assign(next.badge, badge);
60
+ if (colors) Object.assign(next.colors, colors);
61
+ } catch {}
62
+ return next;
63
+ }
64
+ function persist(state, namespace) {
65
+ try {
66
+ localStorage.setItem(storageKey(namespace), JSON.stringify(state));
67
+ } catch {}
68
+ }
69
+ function applyDom(tokens, state, defaults, namespace) {
70
+ const root = document.documentElement;
71
+ for (const t of tokens) {
72
+ if (state.outline[t] !== false) root.removeAttribute(`data-ff-outline-${t}`);
73
+ else root.setAttribute(`data-ff-outline-${t}`, "off");
74
+ if (state.enabled[t] !== false) root.removeAttribute(`data-ff-enabled-${t}`);
75
+ else root.setAttribute(`data-ff-enabled-${t}`, "off");
76
+ if (state.badge[t] !== false) root.removeAttribute(`data-ff-badge-${t}`);
77
+ else root.setAttribute(`data-ff-badge-${t}`, "off");
78
+ const col = state.colors[t] ?? defaults[t];
79
+ if (col) root.style.setProperty(`--${namespace}-c-${t}`, col);
80
+ else root.style.removeProperty(`--${namespace}-c-${t}`);
81
+ }
82
+ }
83
+ const affDevToolbarApp = defineToolbarApp({ init(canvas, eventTarget) {
84
+ let state = loadState([], "ff");
85
+ let tokens = [];
86
+ let namespace = "ff";
87
+ function mount() {
88
+ canvas.querySelector("astro-dev-toolbar-window")?.remove();
89
+ const { tokens: nextTokens, colors: defaultColors, namespace: ns } = readMeta();
90
+ tokens = nextTokens;
91
+ namespace = ns;
92
+ state = loadState(tokens, namespace);
93
+ applyDom(tokens, state, defaultColors, namespace);
94
+ const windowElement = createWindowElement(`
95
+ <style>
96
+ :host astro-dev-toolbar-window {
97
+ height: min(520px, 78vh);
98
+ overflow-y: auto;
99
+ color-scheme: dark;
100
+ --color-muted: rgba(191, 193, 201, 1);
101
+ --color-purple: rgba(224, 204, 250, 1);
102
+ }
103
+ header {
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: space-between;
107
+ gap: 12px;
108
+ }
109
+ h1 {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 8px;
113
+ font-weight: 600;
114
+ color: #fff;
115
+ margin: 0;
116
+ font-size: 22px;
117
+ }
118
+ .aff-flag-toolbar-icon {
119
+ display: inline-flex;
120
+ width: 1em;
121
+ height: 1em;
122
+ flex-shrink: 0;
123
+ align-items: center;
124
+ justify-content: center;
125
+ }
126
+ .aff-flag-toolbar-icon svg {
127
+ width: 100%;
128
+ height: 100%;
129
+ display: block;
130
+ }
131
+ .aff-intro {
132
+ margin: 0;
133
+ font-size: 14px;
134
+ line-height: 1.5rem;
135
+ color: var(--color-muted);
136
+ margin-bottom: 1rem;
137
+ }
138
+ .aff-top {
139
+ display: flex;
140
+ align-items: flex-start;
141
+ justify-content: space-between;
142
+ gap: 12px;
143
+ margin: 1rem 0;
144
+ }
145
+ .aff-top-left {
146
+ flex: 1;
147
+ min-width: 0;
148
+ }
149
+ .aff-help {
150
+ display: grid;
151
+ grid-template-columns: minmax(7.5rem, auto) 1fr;
152
+ gap: 0.45rem 0.75rem;
153
+ }
154
+ .aff-help code,
155
+ .aff-help span {
156
+ font-size: 12px;
157
+ line-height: 1.35;
158
+ }
159
+ .aff-help code {
160
+ justify-self: start;
161
+ }
162
+ .aff-actions {
163
+ display: flex;
164
+ justify-content: flex-end;
165
+ margin: 0;
166
+ flex-shrink: 0;
167
+ }
168
+ .aff-reset {
169
+ border: 1px solid #343841;
170
+ border-radius: 0.5rem;
171
+ background: #24262d;
172
+ color: #fff;
173
+ padding: 0.3rem 0.55rem;
174
+ font-size: 12px;
175
+ cursor: pointer;
176
+ }
177
+ hr {
178
+ border: 1px solid rgba(27, 30, 36, 1);
179
+ margin: 1em 0;
180
+ }
181
+ .setting-row {
182
+ display: flex;
183
+ justify-content: space-between;
184
+ align-items: center;
185
+ gap: 12px;
186
+ margin: 0 0 8px;
187
+ }
188
+ .setting-row section { flex: 1; min-width: 0; max-width: 54%; }
189
+ h3.aff-row-title {
190
+ font-size: 12px;
191
+ font-weight: 600;
192
+ color: #fff;
193
+ letter-spacing: 0.05em;
194
+ text-transform: uppercase;
195
+ margin: 0;
196
+ }
197
+ .aff-radios {
198
+ display: flex;
199
+ gap: 8px;
200
+ align-items: center;
201
+ flex-shrink: 0;
202
+ }
203
+ .aff-radios label {
204
+ display: inline-flex;
205
+ align-items: center;
206
+ gap: 4px;
207
+ font-size: 11px;
208
+ cursor: pointer;
209
+ color: var(--color-muted);
210
+ letter-spacing: 0.04em;
211
+ text-transform: uppercase;
212
+ }
213
+ .aff-radios input { accent-color: var(--color-purple); cursor: pointer; }
214
+ .aff-flag {
215
+ margin: 0 0 12px;
216
+ border: 1px solid rgba(52, 56, 65, 1);
217
+ border-radius: 10px;
218
+ padding: 0 10px 10px;
219
+ background: rgba(19, 21, 26, 0.55);
220
+ }
221
+ .aff-flag summary {
222
+ list-style: none;
223
+ cursor: pointer;
224
+ user-select: none;
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 8px;
228
+ padding: 10px 0;
229
+ font-weight: 600;
230
+ font-size: 11px;
231
+ letter-spacing: 0.06em;
232
+ text-transform: uppercase;
233
+ color: #fff;
234
+ justify-content: space-between;
235
+ }
236
+ .aff-flag summary::-webkit-details-marker { display: none; }
237
+ .aff-flag summary::after {
238
+ content: '▸';
239
+ display: inline-flex;
240
+ align-items: center;
241
+ justify-content: center;
242
+ margin-left: auto;
243
+ color: var(--color-muted);
244
+ font-size: 12px;
245
+ transform: rotate(0deg);
246
+ transition: transform 0.12s ease;
247
+ }
248
+ .aff-flag[open] summary::after {
249
+ transform: rotate(90deg);
250
+ }
251
+ .aff-flag-summary-left {
252
+ display: inline-flex;
253
+ align-items: center;
254
+ gap: 8px;
255
+ }
256
+ .aff-swatch {
257
+ width: 10px;
258
+ height: 10px;
259
+ border-radius: 9999px;
260
+ flex-shrink: 0;
261
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,0.12);
262
+ }
263
+ .aff-flag-body { padding-top: 2px; }
264
+ .aff-empty {
265
+ margin: 0;
266
+ font-size: 14px;
267
+ line-height: 1.5rem;
268
+ color: var(--color-muted);
269
+ }
270
+ code {
271
+ color: var(--color-purple);
272
+ border: 1px solid #343841;
273
+ border-radius: 0.35em;
274
+ background-color: #24262d;
275
+ padding: 0.15em 0.35em;
276
+ font-size: 0.9em;
277
+ }
278
+ input[type="color"].aff-color {
279
+ width: 2.25rem;
280
+ height: 1.75rem;
281
+ padding: 0;
282
+ border: 1px solid #343841;
283
+ border-radius: 6px;
284
+ cursor: pointer;
285
+ background: transparent;
286
+ }
287
+ </style>
288
+ <header>
289
+ <h1><span class="aff-flag-toolbar-icon">${DEV_TOOLBAR_FLAG_ICON_SVG}</span> Feature flags</h1>
290
+ <div class="aff-actions"><button id="aff-reset" class="aff-reset" type="button" title="Reset all flags to defaults">Reset all</button></div>
291
+ </header>
292
+ <div class="aff-top">
293
+ <div class="aff-top-left">
294
+ <p class="aff-intro">Per-flag preview for <code>data-${namespace}</code> regions.</p>
295
+ <div class="aff-help">
296
+ <code>Enabled</code><span>Show/hide flagged regions in preview.</span>
297
+ <code>Outline</code><span>Toggle flag outlines without changing layout.</span>
298
+ <code>Badges</code><span>Toggle element and route labels.</span>
299
+ <code>Colour</code><span>Set <code>--${namespace}-c-&lt;token&gt;</code> on <code>&lt;html&gt;</code>.</span>
300
+ </div>
301
+ </div>
302
+ </div>
303
+ <hr />
304
+ <div id="aff-body"></div>
305
+ `);
306
+ const body = windowElement.querySelector("#aff-body");
307
+ const resetBtn = windowElement.querySelector("#aff-reset");
308
+ if (body) if (tokens.length === 0) {
309
+ const p = document.createElement("p");
310
+ p.className = "aff-empty";
311
+ p.textContent = "No flags in ff.json.";
312
+ body.append(p);
313
+ } else for (const token of tokens) {
314
+ const details = document.createElement("details");
315
+ details.className = "aff-flag";
316
+ details.open = tokens.length <= 8;
317
+ const summary = document.createElement("summary");
318
+ const summaryLeft = document.createElement("span");
319
+ summaryLeft.className = "aff-flag-summary-left";
320
+ const swatch = document.createElement("span");
321
+ swatch.className = "aff-swatch";
322
+ const name = document.createElement("span");
323
+ name.textContent = token;
324
+ summaryLeft.append(swatch, name);
325
+ summary.append(summaryLeft);
326
+ const syncSwatch = () => {
327
+ const c = state.colors[token] ?? defaultColors[token] ?? "#888888";
328
+ swatch.style.background = c;
329
+ };
330
+ syncSwatch();
331
+ const inner = document.createElement("div");
332
+ inner.className = "aff-flag-body";
333
+ const row = (title, tooltip, key, onLab, offLab) => {
334
+ const label = document.createElement("label");
335
+ label.className = "setting-row";
336
+ const section = document.createElement("section");
337
+ const h = document.createElement("h3");
338
+ h.className = "aff-row-title";
339
+ h.textContent = title;
340
+ section.append(h);
341
+ const radios = document.createElement("div");
342
+ radios.className = "aff-radios";
343
+ const mk = (on, lab) => {
344
+ const wrap = document.createElement("label");
345
+ const inp = document.createElement("input");
346
+ inp.type = "radio";
347
+ inp.name = `aff-${token}-${key}`;
348
+ const cur = state[key][token] !== false;
349
+ inp.checked = on ? cur : !cur;
350
+ inp.addEventListener("change", () => {
351
+ if (!inp.checked) return;
352
+ state[key][token] = on;
353
+ applyDom(tokens, state, defaultColors, namespace);
354
+ persist(state, namespace);
355
+ });
356
+ wrap.append(inp, document.createTextNode(lab));
357
+ return wrap;
358
+ };
359
+ radios.append(mk(true, onLab), mk(false, offLab));
360
+ label.append(section, radios);
361
+ return label;
362
+ };
363
+ inner.append(row("Enabled", "When off, nodes with this flag token are hidden in preview.", "enabled", "On", "Off"), row("Outline", "Toggles dashed outline around flagged elements.", "outline", "On", "Off"), row("Badges", "Toggles element badges and top-right route pill.", "badge", "On", "Off"));
364
+ const colorRow = document.createElement("label");
365
+ colorRow.className = "setting-row";
366
+ const csection = document.createElement("section");
367
+ const ch = document.createElement("h3");
368
+ ch.className = "aff-row-title";
369
+ ch.textContent = "Colour";
370
+ ch.title = `Maps to CSS --${namespace}-c-<token> on <html>.`;
371
+ csection.append(ch);
372
+ const cinp = document.createElement("input");
373
+ cinp.type = "color";
374
+ cinp.className = "aff-color";
375
+ cinp.title = `Set --${namespace}-c-${token}`;
376
+ cinp.value = toHexColor(state.colors[token] ?? defaultColors[token], "#888888");
377
+ cinp.addEventListener("input", () => {
378
+ state.colors[token] = cinp.value;
379
+ syncSwatch();
380
+ applyDom(tokens, state, defaultColors, namespace);
381
+ persist(state, namespace);
382
+ });
383
+ colorRow.append(csection, cinp);
384
+ inner.append(colorRow);
385
+ details.append(summary, inner);
386
+ body.append(details);
387
+ }
388
+ if (resetBtn) resetBtn.addEventListener("click", () => {
389
+ try {
390
+ localStorage.removeItem(storageKey(namespace));
391
+ localStorage.removeItem("aff.dev.v1");
392
+ } catch {}
393
+ state = loadState(tokens, namespace);
394
+ applyDom(tokens, state, defaultColors, namespace);
395
+ mount();
396
+ });
397
+ canvas.append(windowElement);
398
+ }
399
+ mount();
400
+ document.addEventListener("astro:after-swap", mount);
401
+ closeOnOutsideClick(eventTarget);
402
+ synchronizePlacementOnUpdate(eventTarget, canvas);
403
+ } });
404
+ //#endregion
405
+ export { affDevToolbarApp as default };
406
+
407
+ //# sourceMappingURL=dev-toolbar-app.mjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,10 @@
1
+ //#region src/dev-toolbar-flag-icon.ts
2
+ /**
3
+ * Inline SVG for the dev toolbar app button and panel header (Heroicons-style flag, 20×20 in a 22×22 box).
4
+ * `currentColor` matches built-in Astro toolbar icons.
5
+ */
6
+ const DEV_TOOLBAR_FLAG_ICON_SVG = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 22 22\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M4.25 3.25v15.5a.75.75 0 1 1-1.5 0V3.25a.75.75 0 0 1 1.5 0Z\"/><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\"/></svg>";
7
+ //#endregion
8
+ export { DEV_TOOLBAR_FLAG_ICON_SVG as t };
9
+
10
+ //# sourceMappingURL=dev-toolbar-flag-icon-BHCQJ53N.mjs.map
@@ -0,0 +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"}
@@ -0,0 +1,103 @@
1
+ import { FeatureFlagMap, ResolveFeatureRuntimeOptions, ResolvedFeatureRuntime, longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, resolveFeatureFlagsByEnvironment, routePatternToPrefix } from "./runtime.mjs";
2
+
3
+ //#region src/badge-layout.d.ts
4
+ type ElementBadgeVerticalAnchor = "top" | "bottom";
5
+ /** Horizontal placement of the element pill: `end` = top-right (LTR), `start` = top-left, `center` = centred. */
6
+ type ElementBadgeHorizontalAlign = "start" | "center" | "end";
7
+ /** Subset of dev CSS options used for element-badge geometry (also on `DevOutlineCssOptions`). */
8
+ interface ElementBadgeLayoutOptions {
9
+ /**
10
+ * Horizontal alignment along the element’s top or bottom edge. Default `end` (top-right in LTR).
11
+ * Ignored when `elementBadgeHorizontalPercent` is set.
12
+ */
13
+ elementBadgeHorizontalAlign?: ElementBadgeHorizontalAlign;
14
+ /**
15
+ * Optional: anchor at a percentage along the element width (0–100), with the pill centred on that point (`translateX(-50%)`).
16
+ * When set, overrides `elementBadgeHorizontalAlign`.
17
+ */
18
+ elementBadgeHorizontalPercent?: number;
19
+ /**
20
+ * Vertical shift as a percentage of the **badge’s own height** (CSS transform %).
21
+ * With anchor `top`, moves the badge upward. Default `80` (~20% of the pill overlaps the element top edge).
22
+ */
23
+ elementBadgeVerticalShiftPercent?: number;
24
+ /** Anchor the badge to the top or bottom edge of the element. Default `top`. */
25
+ elementBadgeVerticalAnchor?: ElementBadgeVerticalAnchor;
26
+ }
27
+ type NormalizedElementBadgeLayout = {
28
+ mode: "align";
29
+ horizontalAlign: ElementBadgeHorizontalAlign;
30
+ verticalShiftPercent: number;
31
+ verticalAnchor: ElementBadgeVerticalAnchor;
32
+ } | {
33
+ mode: "percent";
34
+ horizontalPercent: number;
35
+ verticalShiftPercent: number;
36
+ verticalAnchor: ElementBadgeVerticalAnchor;
37
+ };
38
+ declare function normalizeElementBadgeLayout(input: ElementBadgeLayoutOptions | undefined): NormalizedElementBadgeLayout;
39
+ /**
40
+ * `left` / `right` / `top` | `bottom` / `transform` for the element `::before` badge (no trailing newline).
41
+ */
42
+ declare function elementBadgePositionBlock(layout: NormalizedElementBadgeLayout): string;
43
+ //#endregion
44
+ //#region src/dev-outline-css.d.ts
45
+ type DevOutlineHiddenStrategy = "visibility" | "display";
46
+ interface DevOutlineCssOptions extends ElementBadgeLayoutOptions {
47
+ outlineWidth?: string;
48
+ /** Fallback when no per-token colour is set. */
49
+ outlineColor?: string;
50
+ /** Per-flag-token outline / badge / route-badge colour (overrides JSON `colors`). */
51
+ outlineColorByToken?: Record<string, string>;
52
+ outlineOffset?: string;
53
+ borderRadius?: string;
54
+ /** Default pill text for the `wip` flag token in the dev toolbar. */
55
+ badgeLabelWip?: string;
56
+ /** @deprecated Use {@link badgeLabelWip}. */
57
+ badgeLabelDev?: string;
58
+ badgeLabelByToken?: Record<string, string>;
59
+ hiddenStrategy?: DevOutlineHiddenStrategy;
60
+ /**
61
+ * @deprecated Unused: badge does not use container queries.
62
+ */
63
+ badgeMinInlineSize?: string;
64
+ }
65
+ /**
66
+ * Dev-only styles: `data-ff="token"` (or space-separated tokens) gates outlines/badges.
67
+ * `html[data-ff-route="<token>"]` (dev) adds a fixed top-right route badge (label only).
68
+ * Toolbar toggles: `data-ff-enabled-*`, `data-ff-outline-*`, `data-ff-badge-*`, `--<namespace>-c-*`.
69
+ */
70
+ declare function createFeatureFlagStyles(runtime: ResolvedFeatureRuntime, css?: DevOutlineCssOptions): string;
71
+ /**
72
+ * CSS-only hiding for disabled flags. The Astro integration no longer injects this into
73
+ * production builds — static HTML is culled instead. Kept for custom tooling or tests.
74
+ */
75
+ declare function createProductionGateStyles(runtime: ResolvedFeatureRuntime): string;
76
+ //#endregion
77
+ //#region src/production-html-cull.d.ts
78
+ /**
79
+ * Production static HTML: remove nodes gated by disabled flags (same rules as dev
80
+ * `data-*` / `data-*-<token>`), strip dev-only route attributes on `<html>`, and drop
81
+ * empty `<style>` tags left after `featureFlagStyles` is empty.
82
+ */
83
+ declare function cullProductionHtml(html: string, runtime: ResolvedFeatureRuntime): string;
84
+ /**
85
+ * Walk `outDir` (e.g. Astro `dist/`) and apply {@link cullProductionHtml} to every `.html` file.
86
+ */
87
+ declare function applyProductionHtmlCullToDist(outDir: string, runtime: ResolvedFeatureRuntime): void;
88
+ //#endregion
89
+ //#region src/index.d.ts
90
+ interface AstroFeatureFlagsOptions extends ResolveFeatureRuntimeOptions {
91
+ css?: DevOutlineCssOptions;
92
+ /** When false, keeps static build output untouched (no route pruning / HTML cull). */
93
+ staticMinify?: boolean;
94
+ }
95
+ /** Prefer `prod` if present; else first non-`dev` key (sorted); else `"prod"`. */
96
+ declare function primaryNonDevEnvironmentKey(flagsByEnvironment: Record<string, FeatureFlagMap>): string;
97
+ declare function createVirtualModuleSource(runtime: ResolvedFeatureRuntime, css?: DevOutlineCssOptions, flagsByEnvironment?: Record<string, FeatureFlagMap>, devBootstrap?: string): string;
98
+ declare function getResolvedFeatures(options?: ResolveFeatureRuntimeOptions): ResolvedFeatureRuntime;
99
+ declare function featureRouteIncluded(pathname: string, runtime: ResolvedFeatureRuntime): boolean;
100
+ declare function astroFeatureFlags(options?: AstroFeatureFlagsOptions): any;
101
+ //#endregion
102
+ export { AstroFeatureFlagsOptions, type DevOutlineCssOptions, type DevOutlineHiddenStrategy, type ElementBadgeHorizontalAlign, type ElementBadgeLayoutOptions, type ElementBadgeVerticalAnchor, type FeatureFlagMap, type NormalizedElementBadgeLayout, applyProductionHtmlCullToDist, createFeatureFlagStyles, createProductionGateStyles, createVirtualModuleSource, cullProductionHtml, astroFeatureFlags as default, elementBadgePositionBlock, featureRouteIncluded, getResolvedFeatures, longestMatchingRoutePrefix, mergeFlagsWithProcessEnvOverrides, normalizeElementBadgeLayout, primaryNonDevEnvironmentKey, resolveFeatureFlagsByEnvironment, routePatternToPrefix };
103
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +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"}