featuredrop 1.2.0 → 1.3.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 (84) hide show
  1. package/README.md +171 -0
  2. package/dist/admin.cjs +212 -0
  3. package/dist/admin.cjs.map +1 -0
  4. package/dist/admin.d.cts +176 -0
  5. package/dist/admin.d.ts +176 -0
  6. package/dist/admin.js +207 -0
  7. package/dist/admin.js.map +1 -0
  8. package/dist/angular.cjs +13 -3
  9. package/dist/angular.cjs.map +1 -1
  10. package/dist/angular.d.cts +4 -0
  11. package/dist/angular.d.ts +4 -0
  12. package/dist/angular.js +13 -3
  13. package/dist/angular.js.map +1 -1
  14. package/dist/bridges.cjs +401 -0
  15. package/dist/bridges.cjs.map +1 -0
  16. package/dist/bridges.d.cts +194 -0
  17. package/dist/bridges.d.ts +194 -0
  18. package/dist/bridges.js +394 -0
  19. package/dist/bridges.js.map +1 -0
  20. package/dist/ci.cjs +328 -0
  21. package/dist/ci.cjs.map +1 -0
  22. package/dist/ci.d.cts +176 -0
  23. package/dist/ci.d.ts +176 -0
  24. package/dist/ci.js +324 -0
  25. package/dist/ci.js.map +1 -0
  26. package/dist/featuredrop.cjs +139 -18
  27. package/dist/featuredrop.cjs.map +1 -1
  28. package/dist/flags.cjs +51 -0
  29. package/dist/flags.cjs.map +1 -0
  30. package/dist/flags.d.cts +48 -0
  31. package/dist/flags.d.ts +48 -0
  32. package/dist/flags.js +47 -0
  33. package/dist/flags.js.map +1 -0
  34. package/dist/index.cjs +2583 -665
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +743 -206
  37. package/dist/index.d.ts +743 -206
  38. package/dist/index.js +2552 -666
  39. package/dist/index.js.map +1 -1
  40. package/dist/preact.cjs +710 -209
  41. package/dist/preact.cjs.map +1 -1
  42. package/dist/preact.d.cts +67 -120
  43. package/dist/preact.d.ts +67 -120
  44. package/dist/preact.js +696 -195
  45. package/dist/preact.js.map +1 -1
  46. package/dist/react.cjs +710 -209
  47. package/dist/react.cjs.map +1 -1
  48. package/dist/react.d.cts +67 -120
  49. package/dist/react.d.ts +67 -120
  50. package/dist/react.js +696 -195
  51. package/dist/react.js.map +1 -1
  52. package/dist/schema.cjs +78 -1
  53. package/dist/schema.cjs.map +1 -1
  54. package/dist/schema.d.cts +142 -0
  55. package/dist/schema.d.ts +142 -0
  56. package/dist/schema.js +78 -1
  57. package/dist/schema.js.map +1 -1
  58. package/dist/solid.cjs +13 -3
  59. package/dist/solid.cjs.map +1 -1
  60. package/dist/solid.d.cts +4 -0
  61. package/dist/solid.d.ts +4 -0
  62. package/dist/solid.js +13 -3
  63. package/dist/solid.js.map +1 -1
  64. package/dist/svelte.cjs +13 -3
  65. package/dist/svelte.cjs.map +1 -1
  66. package/dist/svelte.js +13 -3
  67. package/dist/svelte.js.map +1 -1
  68. package/dist/testing.cjs +136 -15
  69. package/dist/testing.cjs.map +1 -1
  70. package/dist/testing.d.cts +22 -0
  71. package/dist/testing.d.ts +22 -0
  72. package/dist/testing.js +136 -15
  73. package/dist/testing.js.map +1 -1
  74. package/dist/vue.cjs +13 -3
  75. package/dist/vue.cjs.map +1 -1
  76. package/dist/vue.js +13 -3
  77. package/dist/vue.js.map +1 -1
  78. package/dist/web-components.cjs +14 -4
  79. package/dist/web-components.cjs.map +1 -1
  80. package/dist/web-components.d.cts +4 -0
  81. package/dist/web-components.d.ts +4 -0
  82. package/dist/web-components.js +14 -4
  83. package/dist/web-components.js.map +1 -1
  84. package/package.json +59 -1
@@ -0,0 +1,176 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /** Entry type label — determines default icon/color in UI */
5
+ type FeatureType = "feature" | "improvement" | "fix" | "breaking";
6
+ /** Priority level for announcements */
7
+ type FeaturePriority = "critical" | "normal" | "low";
8
+ /** Call-to-action for a feature entry */
9
+ interface FeatureCTA {
10
+ /** Button/link label */
11
+ label: string;
12
+ /** URL to navigate to */
13
+ url: string;
14
+ }
15
+ /** Variant-level overrides for A/B announcement testing */
16
+ interface FeatureVariant {
17
+ /** Optional variant-specific label override */
18
+ label?: string;
19
+ /** Optional variant-specific description override */
20
+ description?: string;
21
+ /** Optional variant-specific image override */
22
+ image?: string;
23
+ /** Optional variant-specific CTA override */
24
+ cta?: FeatureCTA;
25
+ /** Optional variant-specific metadata overrides */
26
+ meta?: Record<string, unknown>;
27
+ }
28
+ /** Audience targeting rule — determines which user segments see a feature */
29
+ interface AudienceRule {
30
+ /** Plans that should see this feature (e.g. ["pro", "enterprise"]) */
31
+ plan?: string[];
32
+ /** Roles that should see this feature (e.g. ["admin", "editor"]) */
33
+ role?: string[];
34
+ /** Regions that should see this feature (e.g. ["us", "eu"]) */
35
+ region?: string[];
36
+ /** Arbitrary key-value pairs for custom matching logic */
37
+ custom?: Record<string, unknown>;
38
+ }
39
+ /** Dependency gates for progressive feature discovery */
40
+ interface FeatureDependencies {
41
+ /** Features the user must have seen before this one can surface */
42
+ seen?: string[];
43
+ /** Features the user must have clicked before this one can surface */
44
+ clicked?: string[];
45
+ /** Features the user must have dismissed before this one can surface */
46
+ dismissed?: string[];
47
+ }
48
+ /** Runtime context used by trigger evaluation */
49
+ interface TriggerContext {
50
+ /** Current app route/path */
51
+ path?: string;
52
+ /** Named events observed in this session */
53
+ events?: ReadonlySet<string>;
54
+ /** Named milestone flags reached in this session */
55
+ milestones?: ReadonlySet<string>;
56
+ /** Usage counters keyed by event/pattern name */
57
+ usage?: Record<string, number>;
58
+ /** Session elapsed time in milliseconds */
59
+ elapsedMs?: number;
60
+ /** Scroll completion percentage (0-100) */
61
+ scrollPercent?: number;
62
+ /** Optional additional trigger context */
63
+ metadata?: Record<string, unknown>;
64
+ }
65
+ type FeatureTrigger = {
66
+ type: "page";
67
+ match: string | RegExp;
68
+ } | {
69
+ type: "usage";
70
+ event: string;
71
+ minActions?: number;
72
+ } | {
73
+ type: "time";
74
+ minSeconds: number;
75
+ } | {
76
+ type: "milestone";
77
+ event: string;
78
+ } | {
79
+ type: "frustration";
80
+ pattern: string;
81
+ threshold?: number;
82
+ } | {
83
+ type: "scroll";
84
+ minPercent?: number;
85
+ } | {
86
+ type: "custom";
87
+ evaluate: (context: TriggerContext) => boolean;
88
+ };
89
+ /** A single feature entry in the manifest */
90
+ interface FeatureEntry {
91
+ /** Unique identifier for the feature */
92
+ id: string;
93
+ /** Human-readable label (e.g. "Decision Journal") */
94
+ label: string;
95
+ /** Optional longer description (supports markdown in UI components) */
96
+ description?: string;
97
+ /**
98
+ * Semantic version targeting.
99
+ * If provided as an object, requires `appVersion` to be supplied to the provider/helpers.
100
+ * - introduced: earliest app version that includes this feature
101
+ * - showNewUntil: stop showing "new" once appVersion reaches this
102
+ * - deprecatedAt: hide feature for app versions at or above this (optional safety)
103
+ * - showIn: range string, e.g. ">=2.5.0 <3.0.0"
104
+ */
105
+ version?: string | {
106
+ introduced?: string;
107
+ showNewUntil?: string;
108
+ deprecatedAt?: string;
109
+ showIn?: string;
110
+ };
111
+ /** ISO date when this feature was released */
112
+ releasedAt: string;
113
+ /** ISO date after which the "new" badge should stop showing */
114
+ showNewUntil: string;
115
+ /** Optional key to match navigation items (e.g. "/journal", "settings") */
116
+ sidebarKey?: string;
117
+ /** Optional grouping category (e.g. "ai", "billing", "core") */
118
+ category?: string;
119
+ /** Optional product scope (`"*"`, `"askverdict"`, etc.) for multi-product manifests */
120
+ product?: string;
121
+ /** Optional URL to link to (e.g. docs page, changelog entry) */
122
+ url?: string;
123
+ /** Optional feature flag key; requires a flag bridge to evaluate */
124
+ flagKey?: string;
125
+ /** Entry type — determines default icon/color in UI components */
126
+ type?: FeatureType;
127
+ /** Priority level — critical entries get special treatment in UI */
128
+ priority?: FeaturePriority;
129
+ /** Optional image/screenshot URL */
130
+ image?: string;
131
+ /** Optional call-to-action button */
132
+ cta?: FeatureCTA;
133
+ /** ISO date — entry is hidden until this date (scheduled publishing) */
134
+ publishAt?: string;
135
+ /** Optional arbitrary metadata */
136
+ meta?: Record<string, unknown>;
137
+ /** A/B variants keyed by variant name (e.g. control, treatment_a) */
138
+ variants?: Record<string, FeatureVariant>;
139
+ /** Percentage split per variant (same order as variants object keys) */
140
+ variantSplit?: number[];
141
+ /** Audience targeting — if set, only matching users see this feature */
142
+ audience?: AudienceRule;
143
+ /** Dependency requirements (progressive disclosure sequencing) */
144
+ dependsOn?: FeatureDependencies;
145
+ /** Contextual trigger rule */
146
+ trigger?: FeatureTrigger;
147
+ }
148
+
149
+ interface ManifestEditorProps {
150
+ features: readonly FeatureEntry[];
151
+ onSave: (updated: FeatureEntry[]) => Promise<void> | void;
152
+ readOnly?: boolean;
153
+ children?: ReactNode;
154
+ }
155
+ declare function ManifestEditor({ features, onSave, readOnly, children, }: ManifestEditorProps): react_jsx_runtime.JSX.Element;
156
+ interface ScheduleCalendarProps {
157
+ features: readonly FeatureEntry[];
158
+ onSchedule: (featureId: string, publishAt: string) => Promise<void> | void;
159
+ }
160
+ declare function ScheduleCalendar({ features, onSchedule }: ScheduleCalendarProps): react_jsx_runtime.JSX.Element;
161
+ interface PreviewPanelProps {
162
+ feature?: FeatureEntry | null;
163
+ components?: Array<"badge" | "changelog" | "spotlight" | "banner" | "toast">;
164
+ }
165
+ declare function PreviewPanel({ feature, components }: PreviewPanelProps): react_jsx_runtime.JSX.Element;
166
+ interface AudienceBuilderProps {
167
+ segments?: string[];
168
+ roles?: string[];
169
+ regions?: string[];
170
+ value?: AudienceRule;
171
+ onChange?: (audience: AudienceRule) => void;
172
+ onSave?: (audience: AudienceRule) => Promise<void> | void;
173
+ }
174
+ declare function AudienceBuilder({ segments, roles, regions, value, onChange, onSave, }: AudienceBuilderProps): react_jsx_runtime.JSX.Element;
175
+
176
+ export { AudienceBuilder, type AudienceBuilderProps, ManifestEditor, type ManifestEditorProps, PreviewPanel, type PreviewPanelProps, ScheduleCalendar, type ScheduleCalendarProps };
package/dist/admin.js ADDED
@@ -0,0 +1,207 @@
1
+ "use client";
2
+ import { useState, useMemo } from 'react';
3
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
+
5
+ // src/admin/index.tsx
6
+ var panelStyles = {
7
+ border: "1px solid #e5e7eb",
8
+ borderRadius: "10px",
9
+ padding: "12px",
10
+ background: "#ffffff"
11
+ };
12
+ var headingStyles = {
13
+ margin: "0 0 8px",
14
+ fontSize: "15px",
15
+ fontWeight: 700
16
+ };
17
+ function ManifestEditor({
18
+ features,
19
+ onSave,
20
+ readOnly = false,
21
+ children
22
+ }) {
23
+ const [draft, setDraft] = useState(() => JSON.stringify(features, null, 2));
24
+ const [status, setStatus] = useState("idle");
25
+ const [error, setError] = useState("");
26
+ const parsed = useMemo(() => {
27
+ try {
28
+ const next = JSON.parse(draft);
29
+ if (!Array.isArray(next)) throw new Error("Manifest must be an array");
30
+ return next;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }, [draft]);
35
+ const save = async () => {
36
+ if (readOnly || !parsed) return;
37
+ setStatus("saving");
38
+ setError("");
39
+ try {
40
+ await onSave(parsed);
41
+ setStatus("saved");
42
+ } catch (cause) {
43
+ setStatus("error");
44
+ setError(cause instanceof Error ? cause.message : "Failed to save manifest");
45
+ }
46
+ };
47
+ if (children) return /* @__PURE__ */ jsx(Fragment, { children });
48
+ return /* @__PURE__ */ jsxs("section", { "data-featuredrop-admin-manifest-editor": true, style: panelStyles, children: [
49
+ /* @__PURE__ */ jsx("p", { style: headingStyles, children: "Manifest Editor" }),
50
+ /* @__PURE__ */ jsx(
51
+ "textarea",
52
+ {
53
+ "aria-label": "Manifest JSON",
54
+ value: draft,
55
+ onChange: (event) => setDraft(event.target.value),
56
+ readOnly,
57
+ style: {
58
+ width: "100%",
59
+ minHeight: "180px",
60
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
61
+ fontSize: "12px",
62
+ lineHeight: 1.45,
63
+ border: "1px solid #d1d5db",
64
+ borderRadius: "8px",
65
+ padding: "10px"
66
+ }
67
+ }
68
+ ),
69
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", marginTop: "8px" }, children: [
70
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: save, disabled: readOnly || !parsed, children: "Save" }),
71
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", children: status }),
72
+ !parsed && /* @__PURE__ */ jsx("span", { style: { color: "#dc2626" }, children: "Invalid JSON" }),
73
+ error && /* @__PURE__ */ jsx("span", { style: { color: "#dc2626" }, children: error })
74
+ ] })
75
+ ] });
76
+ }
77
+ function ScheduleCalendar({ features, onSchedule }) {
78
+ const [values, setValues] = useState({});
79
+ return /* @__PURE__ */ jsxs("section", { "data-featuredrop-admin-schedule-calendar": true, style: panelStyles, children: [
80
+ /* @__PURE__ */ jsx("p", { style: headingStyles, children: "Schedule Calendar" }),
81
+ /* @__PURE__ */ jsx("ul", { style: { margin: 0, padding: 0, listStyle: "none", display: "grid", gap: "10px" }, children: features.map((feature) => /* @__PURE__ */ jsxs(
82
+ "li",
83
+ {
84
+ style: {
85
+ border: "1px solid #e5e7eb",
86
+ borderRadius: "8px",
87
+ padding: "10px",
88
+ display: "grid",
89
+ gap: "6px"
90
+ },
91
+ children: [
92
+ /* @__PURE__ */ jsx("strong", { children: feature.label }),
93
+ /* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: "4px" }, children: [
94
+ "Publish at",
95
+ /* @__PURE__ */ jsx(
96
+ "input",
97
+ {
98
+ type: "datetime-local",
99
+ value: values[feature.id] ?? "",
100
+ onChange: (event) => {
101
+ const value = event.target.value;
102
+ setValues((previous) => ({ ...previous, [feature.id]: value }));
103
+ }
104
+ }
105
+ )
106
+ ] }),
107
+ /* @__PURE__ */ jsx(
108
+ "button",
109
+ {
110
+ type: "button",
111
+ onClick: () => {
112
+ const value = values[feature.id];
113
+ if (!value) return;
114
+ void onSchedule(feature.id, new Date(value).toISOString());
115
+ },
116
+ children: "Schedule"
117
+ }
118
+ )
119
+ ]
120
+ },
121
+ feature.id
122
+ )) })
123
+ ] });
124
+ }
125
+ function PreviewPanel({ feature, components = ["badge", "changelog"] }) {
126
+ return /* @__PURE__ */ jsxs("section", { "data-featuredrop-admin-preview-panel": true, style: panelStyles, children: [
127
+ /* @__PURE__ */ jsx("p", { style: headingStyles, children: "Preview Panel" }),
128
+ !feature ? /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#6b7280" }, children: "Select a feature to preview." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
129
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 6px", fontWeight: 600 }, children: feature.label }),
130
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 8px", color: "#6b7280" }, children: feature.description ?? "No description" }),
131
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: components.map((component) => /* @__PURE__ */ jsx(
132
+ "span",
133
+ {
134
+ style: {
135
+ border: "1px solid #d1d5db",
136
+ borderRadius: "999px",
137
+ padding: "2px 8px",
138
+ fontSize: "12px"
139
+ },
140
+ children: component
141
+ },
142
+ component
143
+ )) })
144
+ ] })
145
+ ] });
146
+ }
147
+ function toggle(list, value) {
148
+ const items = new Set(list ?? []);
149
+ if (items.has(value)) items.delete(value);
150
+ else items.add(value);
151
+ return Array.from(items);
152
+ }
153
+ function AudienceBuilder({
154
+ segments = [],
155
+ roles = [],
156
+ regions = [],
157
+ value,
158
+ onChange,
159
+ onSave
160
+ }) {
161
+ const [audience, setAudience] = useState({
162
+ plan: value?.plan ?? [],
163
+ role: value?.role ?? [],
164
+ region: value?.region ?? []
165
+ });
166
+ const updateAudience = (next) => {
167
+ setAudience(next);
168
+ onChange?.(next);
169
+ };
170
+ const section = (title, values, selected, onToggle) => /* @__PURE__ */ jsxs("fieldset", { style: { border: "none", margin: 0, padding: 0 }, children: [
171
+ /* @__PURE__ */ jsx("legend", { style: { fontWeight: 600, marginBottom: "4px" }, children: title }),
172
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: values.map((item) => /* @__PURE__ */ jsxs("label", { style: { display: "inline-flex", alignItems: "center", gap: "6px" }, children: [
173
+ /* @__PURE__ */ jsx(
174
+ "input",
175
+ {
176
+ type: "checkbox",
177
+ checked: Boolean(selected?.includes(item)),
178
+ onChange: () => onToggle(item)
179
+ }
180
+ ),
181
+ item
182
+ ] }, item)) })
183
+ ] });
184
+ return /* @__PURE__ */ jsxs("section", { "data-featuredrop-admin-audience-builder": true, style: panelStyles, children: [
185
+ /* @__PURE__ */ jsx("p", { style: headingStyles, children: "Audience Builder" }),
186
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "10px" }, children: [
187
+ section("Plans", segments, audience.plan, (item) => updateAudience({ ...audience, plan: toggle(audience.plan, item) })),
188
+ section("Roles", roles, audience.role, (item) => updateAudience({ ...audience, role: toggle(audience.role, item) })),
189
+ section("Regions", regions, audience.region, (item) => updateAudience({ ...audience, region: toggle(audience.region, item) }))
190
+ ] }),
191
+ onSave && /* @__PURE__ */ jsx(
192
+ "button",
193
+ {
194
+ type: "button",
195
+ style: { marginTop: "10px" },
196
+ onClick: () => {
197
+ void onSave(audience);
198
+ },
199
+ children: "Save audience"
200
+ }
201
+ )
202
+ ] });
203
+ }
204
+
205
+ export { AudienceBuilder, ManifestEditor, PreviewPanel, ScheduleCalendar };
206
+ //# sourceMappingURL=admin.js.map
207
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/admin/index.tsx"],"names":[],"mappings":"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","file":"admin.js","sourcesContent":["import { useMemo, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { AudienceRule, FeatureEntry } from \"../types\";\n\nconst panelStyles: CSSProperties = {\n border: \"1px solid #e5e7eb\",\n borderRadius: \"10px\",\n padding: \"12px\",\n background: \"#ffffff\",\n};\n\nconst headingStyles: CSSProperties = {\n margin: \"0 0 8px\",\n fontSize: \"15px\",\n fontWeight: 700,\n};\n\nexport interface ManifestEditorProps {\n features: readonly FeatureEntry[];\n onSave: (updated: FeatureEntry[]) => Promise<void> | void;\n readOnly?: boolean;\n children?: ReactNode;\n}\n\nexport function ManifestEditor({\n features,\n onSave,\n readOnly = false,\n children,\n}: ManifestEditorProps) {\n const [draft, setDraft] = useState(() => JSON.stringify(features, null, 2));\n const [status, setStatus] = useState<string>(\"idle\");\n const [error, setError] = useState<string>(\"\");\n\n const parsed = useMemo(() => {\n try {\n const next = JSON.parse(draft) as unknown;\n if (!Array.isArray(next)) throw new Error(\"Manifest must be an array\");\n return next as FeatureEntry[];\n } catch {\n return null;\n }\n }, [draft]);\n\n const save = async () => {\n if (readOnly || !parsed) return;\n setStatus(\"saving\");\n setError(\"\");\n try {\n await onSave(parsed);\n setStatus(\"saved\");\n } catch (cause) {\n setStatus(\"error\");\n setError(cause instanceof Error ? cause.message : \"Failed to save manifest\");\n }\n };\n\n if (children) return <>{children}</>;\n\n return (\n <section data-featuredrop-admin-manifest-editor style={panelStyles}>\n <p style={headingStyles}>Manifest Editor</p>\n <textarea\n aria-label=\"Manifest JSON\"\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n readOnly={readOnly}\n style={{\n width: \"100%\",\n minHeight: \"180px\",\n fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\",\n fontSize: \"12px\",\n lineHeight: 1.45,\n border: \"1px solid #d1d5db\",\n borderRadius: \"8px\",\n padding: \"10px\",\n }}\n />\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"8px\", marginTop: \"8px\" }}>\n <button type=\"button\" onClick={save} disabled={readOnly || !parsed}>\n Save\n </button>\n <span aria-live=\"polite\">{status}</span>\n {!parsed && <span style={{ color: \"#dc2626\" }}>Invalid JSON</span>}\n {error && <span style={{ color: \"#dc2626\" }}>{error}</span>}\n </div>\n </section>\n );\n}\n\nexport interface ScheduleCalendarProps {\n features: readonly FeatureEntry[];\n onSchedule: (featureId: string, publishAt: string) => Promise<void> | void;\n}\n\nexport function ScheduleCalendar({ features, onSchedule }: ScheduleCalendarProps) {\n const [values, setValues] = useState<Record<string, string>>({});\n\n return (\n <section data-featuredrop-admin-schedule-calendar style={panelStyles}>\n <p style={headingStyles}>Schedule Calendar</p>\n <ul style={{ margin: 0, padding: 0, listStyle: \"none\", display: \"grid\", gap: \"10px\" }}>\n {features.map((feature) => (\n <li\n key={feature.id}\n style={{\n border: \"1px solid #e5e7eb\",\n borderRadius: \"8px\",\n padding: \"10px\",\n display: \"grid\",\n gap: \"6px\",\n }}\n >\n <strong>{feature.label}</strong>\n <label style={{ display: \"grid\", gap: \"4px\" }}>\n Publish at\n <input\n type=\"datetime-local\"\n value={values[feature.id] ?? \"\"}\n onChange={(event) => {\n const value = event.target.value;\n setValues((previous) => ({ ...previous, [feature.id]: value }));\n }}\n />\n </label>\n <button\n type=\"button\"\n onClick={() => {\n const value = values[feature.id];\n if (!value) return;\n void onSchedule(feature.id, new Date(value).toISOString());\n }}\n >\n Schedule\n </button>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\nexport interface PreviewPanelProps {\n feature?: FeatureEntry | null;\n components?: Array<\"badge\" | \"changelog\" | \"spotlight\" | \"banner\" | \"toast\">;\n}\n\nexport function PreviewPanel({ feature, components = [\"badge\", \"changelog\"] }: PreviewPanelProps) {\n return (\n <section data-featuredrop-admin-preview-panel style={panelStyles}>\n <p style={headingStyles}>Preview Panel</p>\n {!feature ? (\n <p style={{ margin: 0, color: \"#6b7280\" }}>Select a feature to preview.</p>\n ) : (\n <>\n <p style={{ margin: \"0 0 6px\", fontWeight: 600 }}>{feature.label}</p>\n <p style={{ margin: \"0 0 8px\", color: \"#6b7280\" }}>{feature.description ?? \"No description\"}</p>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"6px\" }}>\n {components.map((component) => (\n <span\n key={component}\n style={{\n border: \"1px solid #d1d5db\",\n borderRadius: \"999px\",\n padding: \"2px 8px\",\n fontSize: \"12px\",\n }}\n >\n {component}\n </span>\n ))}\n </div>\n </>\n )}\n </section>\n );\n}\n\nexport interface AudienceBuilderProps {\n segments?: string[];\n roles?: string[];\n regions?: string[];\n value?: AudienceRule;\n onChange?: (audience: AudienceRule) => void;\n onSave?: (audience: AudienceRule) => Promise<void> | void;\n}\n\nfunction toggle(list: string[] | undefined, value: string): string[] {\n const items = new Set(list ?? []);\n if (items.has(value)) items.delete(value);\n else items.add(value);\n return Array.from(items);\n}\n\nexport function AudienceBuilder({\n segments = [],\n roles = [],\n regions = [],\n value,\n onChange,\n onSave,\n}: AudienceBuilderProps) {\n const [audience, setAudience] = useState<AudienceRule>({\n plan: value?.plan ?? [],\n role: value?.role ?? [],\n region: value?.region ?? [],\n });\n\n const updateAudience = (next: AudienceRule) => {\n setAudience(next);\n onChange?.(next);\n };\n\n const section = (\n title: string,\n values: string[],\n selected: string[] | undefined,\n onToggle: (value: string) => void,\n ) => (\n <fieldset style={{ border: \"none\", margin: 0, padding: 0 }}>\n <legend style={{ fontWeight: 600, marginBottom: \"4px\" }}>{title}</legend>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"8px\" }}>\n {values.map((item) => (\n <label key={item} style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"6px\" }}>\n <input\n type=\"checkbox\"\n checked={Boolean(selected?.includes(item))}\n onChange={() => onToggle(item)}\n />\n {item}\n </label>\n ))}\n </div>\n </fieldset>\n );\n\n return (\n <section data-featuredrop-admin-audience-builder style={panelStyles}>\n <p style={headingStyles}>Audience Builder</p>\n <div style={{ display: \"grid\", gap: \"10px\" }}>\n {section(\"Plans\", segments, audience.plan, (item) =>\n updateAudience({ ...audience, plan: toggle(audience.plan, item) }))}\n {section(\"Roles\", roles, audience.role, (item) =>\n updateAudience({ ...audience, role: toggle(audience.role, item) }))}\n {section(\"Regions\", regions, audience.region, (item) =>\n updateAudience({ ...audience, region: toggle(audience.region, item) }))}\n </div>\n {onSave && (\n <button\n type=\"button\"\n style={{ marginTop: \"10px\" }}\n onClick={() => {\n void onSave(audience);\n }}\n >\n Save audience\n </button>\n )}\n </section>\n );\n}\n"]}
package/dist/angular.cjs CHANGED
@@ -119,6 +119,14 @@ function isVersionMatch(feature, appVersion) {
119
119
  if (v.showNewUntil && compareSemver(appVersion, v.showNewUntil) >= 0) return false;
120
120
  return true;
121
121
  }
122
+ function isFlagMatch(feature, flagBridge, userContext) {
123
+ if (!feature.flagKey) return true;
124
+ return false;
125
+ }
126
+ function isProductMatch(feature, product) {
127
+ if (!feature.product || feature.product === "*") return true;
128
+ return false;
129
+ }
122
130
  function isDependencyMatch(feature, dismissedIds, dependencyState) {
123
131
  const dependsOn = feature.dependsOn;
124
132
  if (!dependsOn) return true;
@@ -140,11 +148,13 @@ function isDependencyMatch(feature, dismissedIds, dependencyState) {
140
148
  }
141
149
  return true;
142
150
  }
143
- function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
151
+ function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
144
152
  if (dismissedIds.has(feature.id)) return false;
145
153
  if (!isAudienceMatch(feature, userContext, matchAudience)) return false;
146
154
  if (!isDependencyMatch(feature, dismissedIds)) return false;
147
155
  if (!isVersionMatch(feature, appVersion)) return false;
156
+ if (!isFlagMatch(feature)) return false;
157
+ if (!isProductMatch(feature)) return false;
148
158
  if (!isTriggerMatch(feature.trigger)) return false;
149
159
  const nowMs = now.getTime();
150
160
  if (feature.publishAt) {
@@ -160,7 +170,7 @@ function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(
160
170
  }
161
171
  return true;
162
172
  }
163
- function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
173
+ function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
164
174
  const watermark = storage.getWatermark();
165
175
  const dismissedIds = storage.getDismissedIds();
166
176
  return manifest.filter(
@@ -174,7 +184,7 @@ function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), use
174
184
  appVersion)
175
185
  );
176
186
  }
177
- function hasNewFeature(manifest, sidebarKey, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
187
+ function hasNewFeature(manifest, sidebarKey, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
178
188
  const watermark = storage.getWatermark();
179
189
  const dismissedIds = storage.getDismissedIds();
180
190
  return manifest.some(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/semver.ts","../src/triggers.ts","../src/core.ts","../src/angular/index.ts"],"names":["isNew"],"mappings":";;;AAWA,IAAM,YAAA,GAAe,4CAAA;AAEd,SAAS,YAAY,KAAA,EAAmC;AAC7D,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,EAAK,CAAE,MAAM,YAAY,CAAA;AAC7C,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,IACtB,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,IACtB,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,IACtB,UAAA,EAAY,KAAA,CAAM,CAAC,CAAA,GAAI,KAAA,CAAM,CAAC,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,GAAI;AAAC,GAChD;AACF;AAEO,SAAS,aAAA,CAAc,GAAW,CAAA,EAAmB;AAC1D,EAAA,MAAM,EAAA,GAAK,YAAY,CAAC,CAAA;AACxB,EAAA,MAAM,EAAA,GAAK,YAAY,CAAC,CAAA;AACxB,EAAA,IAAI,CAAC,EAAA,IAAM,CAAC,EAAA,EAAI,OAAO,CAAA;AAEvB,EAAA,KAAA,MAAW,GAAA,IAAO,CAAC,OAAA,EAAS,OAAA,EAAS,OAAO,CAAA,EAAY;AACtD,IAAA,IAAI,EAAA,CAAG,GAAG,CAAA,KAAM,EAAA,CAAG,GAAG,CAAA,EAAG,OAAO,EAAA,CAAG,GAAG,CAAA,GAAI,EAAA,CAAG,GAAG,CAAA;AAAA,EAClD;AAGA,EAAA,MAAM,OAAO,EAAA,CAAG,UAAA;AAChB,EAAA,MAAM,OAAO,EAAA,CAAG,UAAA;AAChB,EAAA,IAAI,KAAK,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,MAAA,KAAW,GAAG,OAAO,CAAA;AACnD,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA;AAC9B,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,EAAA;AAE9B,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,MAAM,CAAA;AAC7C,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,IAAA,MAAM,EAAA,GAAK,KAAK,CAAC,CAAA;AACjB,IAAA,MAAM,EAAA,GAAK,KAAK,CAAC,CAAA;AACjB,IAAA,IAAI,EAAA,KAAO,QAAW,OAAO,EAAA;AAC7B,IAAA,IAAI,EAAA,KAAO,QAAW,OAAO,CAAA;AAC7B,IAAA,MAAM,IAAA,GAAO,OAAO,EAAE,CAAA;AACtB,IAAA,MAAM,IAAA,GAAO,OAAO,EAAE,CAAA;AACtB,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,IAAI,CAAA;AACpC,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,SAAA,CAAU,IAAI,CAAA;AACpC,IAAA,IAAI,MAAA,IAAU,MAAA,IAAU,IAAA,KAAS,IAAA,SAAa,IAAA,GAAO,IAAA;AACrD,IAAA,IAAI,MAAA,KAAW,MAAA,EAAQ,OAAO,MAAA,GAAS,EAAA,GAAK,CAAA;AAC5C,IAAA,IAAI,EAAA,KAAO,EAAA,EAAI,OAAO,EAAA,GAAK,KAAK,EAAA,GAAK,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,gBAAgB,IAAA,EAA0D;AACjF,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,EAAK,CAAE,MAAM,0BAA0B,CAAA;AAC1D,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,EAAA,GAAM,KAAA,CAAM,CAAC,CAAA,IAAoB,IAAA;AACvC,EAAA,MAAM,OAAA,GAAU,MAAM,CAAC,CAAA;AACvB,EAAA,IAAI,CAAC,WAAA,CAAY,OAAO,CAAA,EAAG,OAAO,IAAA;AAClC,EAAA,OAAO,EAAE,IAAI,OAAA,EAAQ;AACvB;AAEA,SAAS,mBAAA,CAAoB,SAAiB,IAAA,EAAoD;AAChG,EAAA,MAAM,IAAA,GAAO,aAAA,CAAc,OAAA,EAAS,IAAA,CAAK,OAAO,CAAA;AAChD,EAAA,QAAQ,KAAK,EAAA;AAAI,IACf,KAAK,GAAA;AACH,MAAA,OAAO,IAAA,GAAO,CAAA;AAAA,IAChB,KAAK,IAAA;AACH,MAAA,OAAO,IAAA,IAAQ,CAAA;AAAA,IACjB,KAAK,GAAA;AACH,MAAA,OAAO,IAAA,GAAO,CAAA;AAAA,IAChB,KAAK,IAAA;AACH,MAAA,OAAO,IAAA,IAAQ,CAAA;AAAA,IACjB,KAAK,GAAA;AACH,MAAA,OAAO,IAAA,KAAS,CAAA;AAAA,IAClB;AACE,MAAA,OAAO,KAAA;AAAA;AAEb;AAGO,SAAS,cAAA,CAAe,SAAiB,KAAA,EAAwB;AACtE,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA,CAAM,KAAK,CAAA,CAAE,OAAO,OAAO,CAAA;AAC/C,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAC/B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,IAAA,GAAO,gBAAgB,IAAI,CAAA;AACjC,IAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,IAAA,IAAI,CAAC,mBAAA,CAAoB,OAAA,EAAS,IAAI,GAAG,OAAO,KAAA;AAAA,EAClD;AACA,EAAA,OAAO,IAAA;AACT;AC/EO,SAAS,cAAA,CAAe,SAAqC,OAAA,EAAmC;AACrG,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAc,OAAO,KAAA;AAsCvB;;;AClCO,SAAS,eAAA,CACd,UACA,WAAA,EACS;AACT,EAAA,IAAI,QAAA,CAAS,IAAA,IAAQ,QAAA,CAAS,IAAA,CAAK,SAAS,CAAA,EAAG;AAC7C,IAAA,IAAI,CAAC,YAAY,IAAA,IAAQ,CAAC,SAAS,IAAA,CAAK,QAAA,CAAS,WAAA,CAAY,IAAI,CAAA,EAAG;AAClE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,IAAI,QAAA,CAAS,IAAA,IAAQ,QAAA,CAAS,IAAA,CAAK,SAAS,CAAA,EAAG;AAC7C,IAAA,IAAI,CAAC,YAAY,IAAA,IAAQ,CAAC,SAAS,IAAA,CAAK,QAAA,CAAS,WAAA,CAAY,IAAI,CAAA,EAAG;AAClE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,IAAI,QAAA,CAAS,MAAA,IAAU,QAAA,CAAS,MAAA,CAAO,SAAS,CAAA,EAAG;AACjD,IAAA,IAAI,CAAC,YAAY,MAAA,IAAU,CAAC,SAAS,MAAA,CAAO,QAAA,CAAS,WAAA,CAAY,MAAM,CAAA,EAAG;AACxE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAUA,SAAS,eAAA,CACP,OAAA,EACA,WAAA,EACA,OAAA,EACS;AAET,EAAA,IAAI,CAAC,OAAA,CAAQ,QAAA,EAAU,OAAO,IAAA;AAG9B,EAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,MAAA,KAAW,OAAA,CAAQ,QAAA;AAC/C,EAAA,MAAM,WACH,IAAA,IAAQ,IAAA,CAAK,SAAS,CAAA,IACtB,IAAA,IAAQ,KAAK,MAAA,GAAS,CAAA,IACtB,MAAA,IAAU,MAAA,CAAO,SAAS,CAAA,IAC1B,MAAA,IAAU,OAAO,IAAA,CAAK,MAAM,EAAE,MAAA,GAAS,CAAA;AAC1C,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AAGtB,EAAA,IAAI,CAAC,aAAa,OAAO,KAAA;AAGzB,EAAA,IAAI,OAAA,EAAS,OAAO,OAAA,CAAQ,OAAA,CAAQ,UAAU,WAAW,CAAA;AACzD,EAAA,OAAO,eAAA,CAAgB,OAAA,CAAQ,QAAA,EAAU,WAAW,CAAA;AACtD;AAEA,SAAS,cAAA,CAAe,SAAuB,UAAA,EAA8B;AAC3E,EAAA,MAAM,IAAI,OAAA,CAAQ,OAAA;AAClB,EAAA,IAAI,CAAC,CAAA,IAAK,OAAO,CAAA,KAAM,UAAU,OAAO,IAAA;AACxC,EAAA,IAAI,CAAC,YAAY,OAAO,KAAA;AACxB,EAAA,IAAI,CAAC,CAAA,CAAE,UAAA,IAAc,CAAC,CAAA,CAAE,YAAA,IAAgB,CAAC,CAAA,CAAE,YAAA,IAAgB,CAAC,CAAA,CAAE,MAAA,EAAQ,OAAO,IAAA;AAG7E,EAAA,IAAI,CAAA,CAAE,UAAU,CAAC,cAAA,CAAe,YAAY,CAAA,CAAE,MAAM,GAAG,OAAO,KAAA;AAE9D,EAAA,IAAI,CAAA,CAAE,cAAc,aAAA,CAAc,UAAA,EAAY,EAAE,UAAU,CAAA,GAAI,GAAG,OAAO,KAAA;AACxE,EAAA,IAAI,CAAA,CAAE,gBAAgB,aAAA,CAAc,UAAA,EAAY,EAAE,YAAY,CAAA,IAAK,GAAG,OAAO,KAAA;AAG7E,EAAA,IAAI,CAAA,CAAE,gBAAgB,aAAA,CAAc,UAAA,EAAY,EAAE,YAAY,CAAA,IAAK,GAAG,OAAO,KAAA;AAE7E,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,iBAAA,CACP,OAAA,EACA,YAAA,EACA,eAAA,EACS;AACT,EAAA,MAAM,YAAY,OAAA,CAAQ,SAAA;AAC1B,EAAA,IAAI,CAAC,WAAW,OAAO,IAAA;AAIvB,EAAA,MAAM,sBAAA,GAA0D,YAAA;AAEhE,EAAA,IAAI,SAAA,CAAU,IAAA,IAAQ,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA,EAAG;AAC/C,IAAA,KAAA,MAAW,EAAA,IAAM,UAAU,IAAA,EAAM;AAE/B,MAAA,IAAa,CAAC,uBAAuB,GAAA,CAAI,EAAE,GAAG,OAAO,KAAA;AAAA,IACvD;AAAA,EACF;AAEA,EAAA,IAAI,SAAA,CAAU,OAAA,IAAW,SAAA,CAAU,OAAA,CAAQ,SAAS,CAAA,EAAG;AACrD,IAAA,KAAA,MAAW,EAAA,IAAM,UAAU,OAAA,EAAS;AAClC,MAAqC,OAAO,KAAA;AAAA,IAC9C;AAAA,EACF;AAEA,EAAA,IAAI,SAAA,CAAU,SAAA,IAAa,SAAA,CAAU,SAAA,CAAU,SAAS,CAAA,EAAG;AACzD,IAAA,KAAA,MAAW,EAAA,IAAM,UAAU,SAAA,EAAW;AACpC,MAAA,IAAI,CAAC,sBAAA,CAAuB,GAAA,CAAI,EAAE,GAAG,OAAO,KAAA;AAAA,IAC9C;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAYO,SAAS,KAAA,CACd,OAAA,EACA,SAAA,EACA,YAAA,EACA,GAAA,mBAAY,IAAI,IAAA,EAAK,EACrB,WAAA,EACA,aAAA,EACA,UAAA,EACA,eAAA,EACA,cAAA,EACS;AAET,EAAA,IAAI,YAAA,CAAa,GAAA,CAAI,OAAA,CAAQ,EAAE,GAAG,OAAO,KAAA;AAGzC,EAAA,IAAI,CAAC,eAAA,CAAgB,OAAA,EAAS,WAAA,EAAa,aAAa,GAAG,OAAO,KAAA;AAGlE,EAAA,IAAI,CAAC,iBAAA,CAAkB,OAAA,EAAS,YAA6B,GAAG,OAAO,KAAA;AAGvE,EAAA,IAAI,CAAC,cAAA,CAAe,OAAA,EAAS,UAAU,GAAG,OAAO,KAAA;AAGjD,EAAA,IAAI,CAAC,cAAA,CAAe,OAAA,CAAQ,OAAuB,GAAG,OAAO,KAAA;AAE7D,EAAA,MAAM,KAAA,GAAQ,IAAI,OAAA,EAAQ;AAG1B,EAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,IAAA,MAAM,YAAY,IAAI,IAAA,CAAK,OAAA,CAAQ,SAAS,EAAE,OAAA,EAAQ;AACtD,IAAA,IAAI,KAAA,GAAQ,WAAW,OAAO,KAAA;AAAA,EAChC;AAEA,EAAA,MAAM,cAAc,IAAI,IAAA,CAAK,OAAA,CAAQ,YAAY,EAAE,OAAA,EAAQ;AAG3D,EAAA,IAAI,KAAA,IAAS,aAAa,OAAO,KAAA;AAGjC,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,MAAM,WAAA,GAAc,IAAI,IAAA,CAAK,SAAS,EAAE,OAAA,EAAQ;AAChD,IAAA,MAAM,aAAa,IAAI,IAAA,CAAK,OAAA,CAAQ,UAAU,EAAE,OAAA,EAAQ;AACxD,IAAA,IAAI,UAAA,IAAc,aAAa,OAAO,KAAA;AAAA,EACxC;AAEA,EAAA,OAAO,IAAA;AACT;AAKO,SAAS,cAAA,CACd,QAAA,EACA,OAAA,EACA,GAAA,mBAAY,IAAI,IAAA,EAAK,EACrB,WAAA,EACA,aAAA,EACA,UAAA,EACA,eAAA,EACA,cAAA,EACgB;AAChB,EAAA,MAAM,SAAA,GAAY,QAAQ,YAAA,EAAa;AACvC,EAAA,MAAM,YAAA,GAAe,QAAQ,eAAA,EAAgB;AAC7C,EAAA,OAAO,QAAA,CAAS,MAAA;AAAA,IAAO,CAAC,CAAA,KACtB,KAAA;AAAA,MACE,CAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,GAAA;AAAA,MACA,WAAA;AAAA,MACA,aAAA;AAAA,MACA,UAGF;AAAA,GACF;AACF;AA8BO,SAAS,aAAA,CACd,QAAA,EACA,UAAA,EACA,OAAA,EACA,GAAA,mBAAY,IAAI,IAAA,EAAK,EACrB,WAAA,EACA,aAAA,EACA,UAAA,EACA,eAAA,EACA,cAAA,EACS;AACT,EAAA,MAAM,SAAA,GAAY,QAAQ,YAAA,EAAa;AACvC,EAAA,MAAM,YAAA,GAAe,QAAQ,eAAA,EAAgB;AAC7C,EAAA,OAAO,QAAA,CAAS,IAAA;AAAA,IACd,CAAC,CAAA,KACC,CAAA,CAAE,UAAA,KAAe,UAAA,IACjB,KAAA;AAAA,MACE,CAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,GAAA;AAAA,MACA,WAAA;AAAA,MACA,aAAA;AAAA,MACA,UAGF;AAAA,GACJ;AACF;;;AC1OA,SAAS,eAAe,QAAA,EAA0C;AAChE,EAAA,MAAM,gBAAgB,EAAE,QAAA,EAAU,GAAG,MAAA,EAAQ,CAAA,EAAG,KAAK,CAAA,EAAE;AACvD,EAAA,OAAO,CAAC,GAAG,QAAQ,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAClC,IAAA,MAAM,EAAA,GAAK,aAAA,CAAc,CAAA,CAAE,QAAA,IAAY,QAAQ,CAAA;AAC/C,IAAA,MAAM,EAAA,GAAK,aAAA,CAAc,CAAA,CAAE,QAAA,IAAY,QAAQ,CAAA;AAC/C,IAAA,IAAI,EAAA,KAAO,EAAA,EAAI,OAAO,EAAA,GAAK,EAAA;AAC3B,IAAA,OAAO,IAAI,IAAA,CAAK,CAAA,CAAE,UAAU,CAAA,CAAE,OAAA,EAAQ,GAAI,IAAI,IAAA,CAAK,CAAA,CAAE,UAAU,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC3E,CAAC,CAAA;AACH;AAEO,SAAS,yBACd,OAAA,EAC2B;AAC3B,EAAA,MAAM,kBAAkB,MACtB,cAAA;AAAA,IACE,OAAA,CAAQ,QAAA;AAAA,IACR,OAAA,CAAQ,OAAA;AAAA,wBACJ,IAAA,EAAK;AAAA,IACT,OAAA,CAAQ,WAAA;AAAA,IACR,OAAA,CAAQ,aAAA;AAAA,IACR,OAAA,CAAQ;AAAA,GACV;AAEF,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,YAAA,CAAa,eAAA,EAAiB,CAAA;AAE1D,EAAA,MAAM,UAAU,MAAY;AAC1B,IAAA,WAAA,CAAY,GAAA,CAAI,iBAAiB,CAAA;AAAA,EACnC,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,EAAA,KAAqB;AACpC,IAAA,MAAM,OAAA,GAAU,aAAY,CAAE,IAAA,CAAK,CAAC,IAAA,KAAS,IAAA,CAAK,OAAO,EAAE,CAAA;AAC3D,IAAA,OAAA,CAAQ,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAC1B,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,SAAA,EAAW,qBAAqB,OAAO,CAAA;AAAA,IACjD;AACA,IAAA,OAAA,EAAQ;AAAA,EACV,CAAA;AAEA,EAAA,MAAM,aAAa,YAA2B;AAC5C,IAAA,MAAM,OAAA,CAAQ,OAAA,CAAQ,UAAA,iBAAW,IAAI,MAAM,CAAA;AAC3C,IAAA,OAAA,CAAQ,WAAW,cAAA,IAAiB;AACpC,IAAA,OAAA,EAAQ;AAAA,EACV,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,MAAc,WAAA,EAAY,CAAE,MAAA;AAE7C,EAAA,MAAMA,MAAAA,GAAQ,CAAC,UAAA,KACb,aAAA;AAAA,IACE,OAAA,CAAQ,QAAA;AAAA,IACR,UAAA;AAAA,IACA,OAAA,CAAQ,OAAA;AAAA,wBACJ,IAAA,EAAK;AAAA,IACT,OAAA,CAAQ,WAAA;AAAA,IACR,OAAA,CAAQ,aAAA;AAAA,IACR,OAAA,CAAQ;AAAA,GACV;AAEF,EAAA,MAAM,UAAA,GAAa,CAAC,UAAA,KAClB,WAAA,EAAY,CAAE,KAAK,CAAC,OAAA,KAAY,OAAA,CAAQ,UAAA,KAAe,UAAU,CAAA;AAEnE,EAAA,OAAO;AAAA,IACL,UAAU,OAAA,CAAQ,QAAA;AAAA,IAClB,WAAA;AAAA,IACA,QAAA;AAAA,IACA,iBAAA,EAAmB,MAAM,cAAA,CAAe,WAAA,EAAa,CAAA;AAAA,IACrD,OAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA,EAAAA,MAAAA;AAAA,IACA;AAAA,GACF;AACF;AAEO,IAAM,qBAAN,MAAyB;AAAA,EACrB,QAAA;AAAA,EACA,WAAA;AAAA,EAEQ,OAAA;AAAA,EAEjB,YAAY,OAAA,EAAoC;AAC9C,IAAA,IAAA,CAAK,OAAA,GAAU,yBAAyB,OAAO,CAAA;AAC/C,IAAA,IAAA,CAAK,QAAA,GAAW,KAAK,OAAA,CAAQ,QAAA;AAC7B,IAAA,IAAA,CAAK,WAAA,GAAc,KAAK,OAAA,CAAQ,WAAA;AAAA,EAClC;AAAA,EAEA,QAAA,GAAmB;AACjB,IAAA,OAAO,IAAA,CAAK,QAAQ,QAAA,EAAS;AAAA,EAC/B;AAAA,EAEA,iBAAA,GAAoC;AAClC,IAAA,OAAO,IAAA,CAAK,QAAQ,iBAAA,EAAkB;AAAA,EACxC;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,QAAQ,OAAA,EAAQ;AAAA,EACvB;AAAA,EAEA,QAAQ,EAAA,EAAkB;AACxB,IAAA,IAAA,CAAK,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAAA,EACzB;AAAA,EAEA,UAAA,GAA4B;AAC1B,IAAA,OAAO,IAAA,CAAK,QAAQ,UAAA,EAAW;AAAA,EACjC;AAAA,EAEA,MAAM,UAAA,EAA6B;AACjC,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,UAAU,CAAA;AAAA,EACtC;AAAA,EAEA,WAAW,UAAA,EAA8C;AACvD,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,UAAA,CAAW,UAAU,CAAA;AAAA,EAC3C;AACF","file":"angular.cjs","sourcesContent":["// Minimal semver comparison utilities (no build metadata sorting needed)\n\nexport type Comparator = \">=\" | \"<=\" | \">\" | \"<\" | \"=\";\n\nexport interface SemverParts {\n major: number;\n minor: number;\n patch: number;\n prerelease: string[];\n}\n\nconst SEMVER_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/;\n\nexport function parseSemver(input: string): SemverParts | null {\n const match = input.trim().match(SEMVER_REGEX);\n if (!match) return null;\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n prerelease: match[4] ? match[4].split(\".\") : [],\n };\n}\n\nexport function compareSemver(a: string, b: string): number {\n const pa = parseSemver(a);\n const pb = parseSemver(b);\n if (!pa || !pb) return 0;\n\n for (const key of [\"major\", \"minor\", \"patch\"] as const) {\n if (pa[key] !== pb[key]) return pa[key] - pb[key];\n }\n\n // Handle prerelease: absence > presence, otherwise lexicographic\n const aPre = pa.prerelease;\n const bPre = pb.prerelease;\n if (aPre.length === 0 && bPre.length === 0) return 0;\n if (aPre.length === 0) return 1;\n if (bPre.length === 0) return -1;\n\n const len = Math.max(aPre.length, bPre.length);\n for (let i = 0; i < len; i++) {\n const ai = aPre[i];\n const bi = bPre[i];\n if (ai === undefined) return -1;\n if (bi === undefined) return 1;\n const aNum = Number(ai);\n const bNum = Number(bi);\n const aIsNum = Number.isInteger(aNum);\n const bIsNum = Number.isInteger(bNum);\n if (aIsNum && bIsNum && aNum !== bNum) return aNum - bNum;\n if (aIsNum !== bIsNum) return aIsNum ? -1 : 1;\n if (ai !== bi) return ai < bi ? -1 : 1;\n }\n return 0;\n}\n\nfunction parseComparator(comp: string): { op: Comparator; version: string } | null {\n const match = comp.trim().match(/^(>=|<=|>|<|=)?\\\\s*(.+)$/);\n if (!match) return null;\n const op = (match[1] as Comparator) || \">=\";\n const version = match[2];\n if (!parseSemver(version)) return null;\n return { op, version };\n}\n\nfunction satisfiesComparator(version: string, comp: { op: Comparator; version: string }): boolean {\n const diff = compareSemver(version, comp.version);\n switch (comp.op) {\n case \">\":\n return diff > 0;\n case \">=\":\n return diff >= 0;\n case \"<\":\n return diff < 0;\n case \"<=\":\n return diff <= 0;\n case \"=\":\n return diff === 0;\n default:\n return false;\n }\n}\n\n// Space-separated comparator list (AND semantics), e.g. \">=2.5.0 <3.0.0\"\nexport function satisfiesRange(version: string, range: string): boolean {\n const parts = range.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return true;\n for (const part of parts) {\n const comp = parseComparator(part);\n if (!comp) return false;\n if (!satisfiesComparator(version, comp)) return false;\n }\n return true;\n}\n","import type { FeatureEntry, FeatureTrigger, TriggerContext } from \"./types\";\n\nfunction wildcardToRegExp(value: string): RegExp {\n const escaped = value.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = `^${escaped.replace(/\\*/g, \".*\")}$`;\n return new RegExp(pattern);\n}\n\nfunction matchPath(path: string, pattern: string | RegExp): boolean {\n if (pattern instanceof RegExp) return pattern.test(path);\n if (!pattern) return false;\n if (pattern.includes(\"*\")) return wildcardToRegExp(pattern).test(path);\n return path === pattern || path.startsWith(pattern);\n}\n\nexport function isTriggerMatch(trigger: FeatureTrigger | undefined, context?: TriggerContext): boolean {\n if (!trigger) return true;\n if (!context) return false;\n\n if (trigger.type === \"page\") {\n const path = context.path;\n if (!path) return false;\n return matchPath(path, trigger.match);\n }\n\n if (trigger.type === \"usage\") {\n const usage = context.usage ?? {};\n const count = usage[trigger.event] ?? 0;\n return count >= (trigger.minActions ?? 1);\n }\n\n if (trigger.type === \"time\") {\n const elapsedMs = context.elapsedMs ?? 0;\n return elapsedMs >= trigger.minSeconds * 1000;\n }\n\n if (trigger.type === \"milestone\") {\n return context.milestones?.has(trigger.event) ?? false;\n }\n\n if (trigger.type === \"frustration\") {\n const usage = context.usage ?? {};\n const count = usage[trigger.pattern] ?? 0;\n return count >= (trigger.threshold ?? 1);\n }\n\n if (trigger.type === \"scroll\") {\n return (context.scrollPercent ?? 0) >= (trigger.minPercent ?? 50);\n }\n\n try {\n return trigger.evaluate(context);\n } catch {\n return false;\n }\n}\n\nexport class TriggerEngine {\n private context: TriggerContext;\n\n constructor(initial?: TriggerContext) {\n this.context = {\n path: initial?.path,\n events: new Set(initial?.events ?? []),\n milestones: new Set(initial?.milestones ?? []),\n usage: { ...(initial?.usage ?? {}) },\n elapsedMs: initial?.elapsedMs ?? 0,\n scrollPercent: initial?.scrollPercent ?? 0,\n metadata: { ...(initial?.metadata ?? {}) },\n };\n }\n\n setPath(path: string): void {\n this.context.path = path;\n }\n\n trackEvent(event: string): void {\n const next = new Set(this.context.events ?? new Set<string>());\n next.add(event);\n this.context.events = next;\n }\n\n trackUsage(event: string, delta = 1): void {\n const usage = { ...(this.context.usage ?? {}) };\n usage[event] = (usage[event] ?? 0) + Math.max(1, delta);\n this.context.usage = usage;\n }\n\n trackMilestone(event: string): void {\n const next = new Set(this.context.milestones ?? new Set<string>());\n next.add(event);\n this.context.milestones = next;\n }\n\n setElapsedMs(elapsedMs: number): void {\n this.context.elapsedMs = Math.max(0, elapsedMs);\n }\n\n setScrollPercent(scrollPercent: number): void {\n const clamped = Math.max(0, Math.min(100, scrollPercent));\n this.context.scrollPercent = clamped;\n }\n\n setMetadata(next: Record<string, unknown>): void {\n this.context.metadata = { ...next };\n }\n\n getContext(): TriggerContext {\n return {\n path: this.context.path,\n events: new Set(this.context.events ?? []),\n milestones: new Set(this.context.milestones ?? []),\n usage: { ...(this.context.usage ?? {}) },\n elapsedMs: this.context.elapsedMs,\n scrollPercent: this.context.scrollPercent,\n metadata: { ...(this.context.metadata ?? {}) },\n };\n }\n\n evaluate(trigger: FeatureTrigger | undefined): boolean {\n return isTriggerMatch(trigger, this.context);\n }\n\n evaluateFeature(feature: Pick<FeatureEntry, \"trigger\">): boolean {\n return this.evaluate(feature.trigger);\n }\n}\n","import type {\n AudienceMatchFn,\n AudienceRule,\n FeatureEntry,\n FeatureManifest,\n StorageAdapter,\n UserContext,\n FeatureDependencyState,\n TriggerContext,\n} from \"./types\";\nimport { compareSemver, satisfiesRange } from \"./semver\";\nimport { isTriggerMatch } from \"./triggers\";\n\n/**\n * Default audience matching logic.\n *\n * For each specified field (plan, role, region), checks if the user's\n * value is included in the allowed list. Fields use AND logic between them,\n * OR logic within each field's array. The `custom` field is ignored by\n * the default matcher — use a custom `AudienceMatchFn` for that.\n */\nexport function matchesAudience(\n audience: AudienceRule,\n userContext: UserContext,\n): boolean {\n if (audience.plan && audience.plan.length > 0) {\n if (!userContext.plan || !audience.plan.includes(userContext.plan)) {\n return false;\n }\n }\n if (audience.role && audience.role.length > 0) {\n if (!userContext.role || !audience.role.includes(userContext.role)) {\n return false;\n }\n }\n if (audience.region && audience.region.length > 0) {\n if (!userContext.region || !audience.region.includes(userContext.region)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Check if a feature's audience allows the given user context.\n *\n * - No `audience` field → visible to all\n * - Empty `audience` ({}) → visible to all\n * - `audience` specified but no `userContext` → hidden (safe default)\n * - Otherwise, delegate to `matchFn` (or default `matchesAudience`)\n */\nfunction isAudienceMatch(\n feature: FeatureEntry,\n userContext?: UserContext,\n matchFn?: AudienceMatchFn,\n): boolean {\n // No audience restriction → show to everyone\n if (!feature.audience) return true;\n\n // Check if audience is empty (no fields with values)\n const { plan, role, region, custom } = feature.audience;\n const hasRules =\n (plan && plan.length > 0) ||\n (role && role.length > 0) ||\n (region && region.length > 0) ||\n (custom && Object.keys(custom).length > 0);\n if (!hasRules) return true;\n\n // Audience specified but no user context → hidden (safe default)\n if (!userContext) return false;\n\n // Use custom matcher if provided, otherwise default\n if (matchFn) return matchFn(feature.audience, userContext);\n return matchesAudience(feature.audience, userContext);\n}\n\nfunction isVersionMatch(feature: FeatureEntry, appVersion?: string): boolean {\n const v = feature.version;\n if (!v || typeof v === \"string\") return true; // string = display only\n if (!appVersion) return false; // Safe default when constraints exist\n if (!v.introduced && !v.showNewUntil && !v.deprecatedAt && !v.showIn) return true;\n\n // Range check\n if (v.showIn && !satisfiesRange(appVersion, v.showIn)) return false;\n\n if (v.introduced && compareSemver(appVersion, v.introduced) < 0) return false;\n if (v.deprecatedAt && compareSemver(appVersion, v.deprecatedAt) >= 0) return false;\n\n // showNewUntil gates \"new\" state only\n if (v.showNewUntil && compareSemver(appVersion, v.showNewUntil) >= 0) return false;\n\n return true;\n}\n\nfunction isDependencyMatch(\n feature: FeatureEntry,\n dismissedIds: ReadonlySet<string>,\n dependencyState?: FeatureDependencyState,\n): boolean {\n const dependsOn = feature.dependsOn;\n if (!dependsOn) return true;\n\n const seenIds = dependencyState?.seenIds;\n const clickedIds = dependencyState?.clickedIds;\n const dismissedDependencyIds = dependencyState?.dismissedIds ?? dismissedIds;\n\n if (dependsOn.seen && dependsOn.seen.length > 0) {\n for (const id of dependsOn.seen) {\n const seen = seenIds?.has(id) ?? false;\n if (!seen && !dismissedDependencyIds.has(id)) return false;\n }\n }\n\n if (dependsOn.clicked && dependsOn.clicked.length > 0) {\n for (const id of dependsOn.clicked) {\n if (!(clickedIds?.has(id) ?? false)) return false;\n }\n }\n\n if (dependsOn.dismissed && dependsOn.dismissed.length > 0) {\n for (const id of dependsOn.dismissed) {\n if (!dismissedDependencyIds.has(id)) return false;\n }\n }\n\n return true;\n}\n\n/**\n * Check if a single feature should show as \"new\".\n *\n * A feature is \"new\" when ALL of these are true:\n * 1. Current time is before `showNewUntil`\n * 2. Feature was released after the watermark (or no watermark exists)\n * 3. Feature has not been individually dismissed\n * 4. If `publishAt` is set, current time must be after it (scheduled publishing)\n * 5. If `audience` is set, user must match the targeting rules\n */\nexport function isNew(\n feature: FeatureEntry,\n watermark: string | null,\n dismissedIds: ReadonlySet<string>,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n): boolean {\n // Already dismissed by the user on this device\n if (dismissedIds.has(feature.id)) return false;\n\n // Audience targeting — check before time-based checks\n if (!isAudienceMatch(feature, userContext, matchAudience)) return false;\n\n // Dependency targeting — defer features until prerequisites are satisfied\n if (!isDependencyMatch(feature, dismissedIds, dependencyState)) return false;\n\n // Version targeting — requires appVersion when constraints exist\n if (!isVersionMatch(feature, appVersion)) return false;\n\n // Contextual trigger rules — show only when trigger condition is satisfied.\n if (!isTriggerMatch(feature.trigger, triggerContext)) return false;\n\n const nowMs = now.getTime();\n\n // Scheduled publishing — hidden until publishAt\n if (feature.publishAt) {\n const publishMs = new Date(feature.publishAt).getTime();\n if (nowMs < publishMs) return false;\n }\n\n const showUntilMs = new Date(feature.showNewUntil).getTime();\n\n // Past the display window\n if (nowMs >= showUntilMs) return false;\n\n // If there's a watermark, feature must have been released after it\n if (watermark) {\n const watermarkMs = new Date(watermark).getTime();\n const releasedMs = new Date(feature.releasedAt).getTime();\n if (releasedMs <= watermarkMs) return false;\n }\n\n return true;\n}\n\n/**\n * Get all features that are currently \"new\" for this user.\n */\nexport function getNewFeatures(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n): FeatureEntry[] {\n const watermark = storage.getWatermark();\n const dismissedIds = storage.getDismissedIds();\n return manifest.filter((f) =>\n isNew(\n f,\n watermark,\n dismissedIds,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n ),\n );\n}\n\n/**\n * Get the count of new features.\n */\nexport function getNewFeatureCount(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n): number {\n return getNewFeatures(\n manifest,\n storage,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n ).length;\n}\n\n/**\n * Check if a specific sidebar key has a new feature.\n */\nexport function hasNewFeature(\n manifest: FeatureManifest,\n sidebarKey: string,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n): boolean {\n const watermark = storage.getWatermark();\n const dismissedIds = storage.getDismissedIds();\n return manifest.some(\n (f) =>\n f.sidebarKey === sidebarKey &&\n isNew(\n f,\n watermark,\n dismissedIds,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n ),\n );\n}\n\n/**\n * Get all features sorted by priority (critical first) then by release date (newest first).\n */\nexport function getNewFeaturesSorted(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n): FeatureEntry[] {\n const priorityOrder = { critical: 0, normal: 1, low: 2 };\n return getNewFeatures(\n manifest,\n storage,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n ).sort(\n (a, b) => {\n const pa = priorityOrder[a.priority ?? \"normal\"];\n const pb = priorityOrder[b.priority ?? \"normal\"];\n if (pa !== pb) return pa - pb;\n return (\n new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime()\n );\n },\n );\n}\n","import { getNewFeatures, hasNewFeature } from \"../core\";\nimport type {\n AnalyticsCallbacks,\n AudienceMatchFn,\n FeatureEntry,\n FeatureManifest,\n StorageAdapter,\n UserContext,\n} from \"../types\";\n\nexport interface SignalLike<T> {\n (): T;\n set: (value: T) => void;\n}\n\nexport type CreateSignalLikeFn = <T>(initial: T) => SignalLike<T>;\n\nexport interface AngularFeatureDropOptions {\n manifest: FeatureManifest;\n storage: StorageAdapter;\n createSignal: CreateSignalLikeFn;\n analytics?: AnalyticsCallbacks;\n userContext?: UserContext;\n matchAudience?: AudienceMatchFn;\n appVersion?: string;\n}\n\nexport interface AngularFeatureDropService {\n manifest: FeatureManifest;\n newFeatures: SignalLike<FeatureEntry[]>;\n newCount: () => number;\n newFeaturesSorted: () => FeatureEntry[];\n refresh: () => void;\n dismiss: (id: string) => void;\n dismissAll: () => Promise<void>;\n isNew: (sidebarKey: string) => boolean;\n getFeature: (sidebarKey: string) => FeatureEntry | undefined;\n}\n\nfunction sortByPriority(features: FeatureEntry[]): FeatureEntry[] {\n const priorityOrder = { critical: 0, normal: 1, low: 2 };\n return [...features].sort((a, b) => {\n const pa = priorityOrder[a.priority ?? \"normal\"];\n const pb = priorityOrder[b.priority ?? \"normal\"];\n if (pa !== pb) return pa - pb;\n return new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime();\n });\n}\n\nexport function createFeatureDropService(\n options: AngularFeatureDropOptions,\n): AngularFeatureDropService {\n const computeFeatures = (): FeatureEntry[] =>\n getNewFeatures(\n options.manifest,\n options.storage,\n new Date(),\n options.userContext,\n options.matchAudience,\n options.appVersion,\n );\n\n const newFeatures = options.createSignal(computeFeatures());\n\n const refresh = (): void => {\n newFeatures.set(computeFeatures());\n };\n\n const dismiss = (id: string): void => {\n const feature = newFeatures().find((item) => item.id === id);\n options.storage.dismiss(id);\n if (feature) {\n options.analytics?.onFeatureDismissed?.(feature);\n }\n refresh();\n };\n\n const dismissAll = async (): Promise<void> => {\n await options.storage.dismissAll(new Date());\n options.analytics?.onAllDismissed?.();\n refresh();\n };\n\n const newCount = (): number => newFeatures().length;\n\n const isNew = (sidebarKey: string): boolean =>\n hasNewFeature(\n options.manifest,\n sidebarKey,\n options.storage,\n new Date(),\n options.userContext,\n options.matchAudience,\n options.appVersion,\n );\n\n const getFeature = (sidebarKey: string): FeatureEntry | undefined =>\n newFeatures().find((feature) => feature.sidebarKey === sidebarKey);\n\n return {\n manifest: options.manifest,\n newFeatures,\n newCount,\n newFeaturesSorted: () => sortByPriority(newFeatures()),\n refresh,\n dismiss,\n dismissAll,\n isNew,\n getFeature,\n };\n}\n\nexport class FeatureDropService {\n readonly manifest: FeatureManifest;\n readonly newFeatures: SignalLike<FeatureEntry[]>;\n\n private readonly service: AngularFeatureDropService;\n\n constructor(options: AngularFeatureDropOptions) {\n this.service = createFeatureDropService(options);\n this.manifest = this.service.manifest;\n this.newFeatures = this.service.newFeatures;\n }\n\n newCount(): number {\n return this.service.newCount();\n }\n\n newFeaturesSorted(): FeatureEntry[] {\n return this.service.newFeaturesSorted();\n }\n\n refresh(): void {\n this.service.refresh();\n }\n\n dismiss(id: string): void {\n this.service.dismiss(id);\n }\n\n dismissAll(): Promise<void> {\n return this.service.dismissAll();\n }\n\n isNew(sidebarKey: string): boolean {\n return this.service.isNew(sidebarKey);\n }\n\n getFeature(sidebarKey: string): FeatureEntry | undefined {\n return this.service.getFeature(sidebarKey);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/semver.ts","../src/triggers.ts","../src/core.ts","../src/angular/index.ts"],"names":["isNew"],"mappings":"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","file":"angular.cjs","sourcesContent":["// Minimal semver comparison utilities (no build metadata sorting needed)\n\nexport type Comparator = \">=\" | \"<=\" | \">\" | \"<\" | \"=\";\n\nexport interface SemverParts {\n major: number;\n minor: number;\n patch: number;\n prerelease: string[];\n}\n\nconst SEMVER_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/;\n\nexport function parseSemver(input: string): SemverParts | null {\n const match = input.trim().match(SEMVER_REGEX);\n if (!match) return null;\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n prerelease: match[4] ? match[4].split(\".\") : [],\n };\n}\n\nexport function compareSemver(a: string, b: string): number {\n const pa = parseSemver(a);\n const pb = parseSemver(b);\n if (!pa || !pb) return 0;\n\n for (const key of [\"major\", \"minor\", \"patch\"] as const) {\n if (pa[key] !== pb[key]) return pa[key] - pb[key];\n }\n\n // Handle prerelease: absence > presence, otherwise lexicographic\n const aPre = pa.prerelease;\n const bPre = pb.prerelease;\n if (aPre.length === 0 && bPre.length === 0) return 0;\n if (aPre.length === 0) return 1;\n if (bPre.length === 0) return -1;\n\n const len = Math.max(aPre.length, bPre.length);\n for (let i = 0; i < len; i++) {\n const ai = aPre[i];\n const bi = bPre[i];\n if (ai === undefined) return -1;\n if (bi === undefined) return 1;\n const aNum = Number(ai);\n const bNum = Number(bi);\n const aIsNum = Number.isInteger(aNum);\n const bIsNum = Number.isInteger(bNum);\n if (aIsNum && bIsNum && aNum !== bNum) return aNum - bNum;\n if (aIsNum !== bIsNum) return aIsNum ? -1 : 1;\n if (ai !== bi) return ai < bi ? -1 : 1;\n }\n return 0;\n}\n\nfunction parseComparator(comp: string): { op: Comparator; version: string } | null {\n const match = comp.trim().match(/^(>=|<=|>|<|=)?\\\\s*(.+)$/);\n if (!match) return null;\n const op = (match[1] as Comparator) || \">=\";\n const version = match[2];\n if (!parseSemver(version)) return null;\n return { op, version };\n}\n\nfunction satisfiesComparator(version: string, comp: { op: Comparator; version: string }): boolean {\n const diff = compareSemver(version, comp.version);\n switch (comp.op) {\n case \">\":\n return diff > 0;\n case \">=\":\n return diff >= 0;\n case \"<\":\n return diff < 0;\n case \"<=\":\n return diff <= 0;\n case \"=\":\n return diff === 0;\n default:\n return false;\n }\n}\n\n// Space-separated comparator list (AND semantics), e.g. \">=2.5.0 <3.0.0\"\nexport function satisfiesRange(version: string, range: string): boolean {\n const parts = range.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return true;\n for (const part of parts) {\n const comp = parseComparator(part);\n if (!comp) return false;\n if (!satisfiesComparator(version, comp)) return false;\n }\n return true;\n}\n","import type { FeatureEntry, FeatureTrigger, TriggerContext } from \"./types\";\n\nfunction wildcardToRegExp(value: string): RegExp {\n const escaped = value.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const pattern = `^${escaped.replace(/\\*/g, \".*\")}$`;\n return new RegExp(pattern);\n}\n\nfunction matchPath(path: string, pattern: string | RegExp): boolean {\n if (pattern instanceof RegExp) return pattern.test(path);\n if (!pattern) return false;\n if (pattern.includes(\"*\")) return wildcardToRegExp(pattern).test(path);\n return path === pattern || path.startsWith(pattern);\n}\n\nexport function isTriggerMatch(trigger: FeatureTrigger | undefined, context?: TriggerContext): boolean {\n if (!trigger) return true;\n if (!context) return false;\n\n if (trigger.type === \"page\") {\n const path = context.path;\n if (!path) return false;\n return matchPath(path, trigger.match);\n }\n\n if (trigger.type === \"usage\") {\n const usage = context.usage ?? {};\n const count = usage[trigger.event] ?? 0;\n return count >= (trigger.minActions ?? 1);\n }\n\n if (trigger.type === \"time\") {\n const elapsedMs = context.elapsedMs ?? 0;\n return elapsedMs >= trigger.minSeconds * 1000;\n }\n\n if (trigger.type === \"milestone\") {\n return context.milestones?.has(trigger.event) ?? false;\n }\n\n if (trigger.type === \"frustration\") {\n const usage = context.usage ?? {};\n const count = usage[trigger.pattern] ?? 0;\n return count >= (trigger.threshold ?? 1);\n }\n\n if (trigger.type === \"scroll\") {\n return (context.scrollPercent ?? 0) >= (trigger.minPercent ?? 50);\n }\n\n try {\n return trigger.evaluate(context);\n } catch {\n return false;\n }\n}\n\nexport class TriggerEngine {\n private context: TriggerContext;\n\n constructor(initial?: TriggerContext) {\n this.context = {\n path: initial?.path,\n events: new Set(initial?.events ?? []),\n milestones: new Set(initial?.milestones ?? []),\n usage: { ...(initial?.usage ?? {}) },\n elapsedMs: initial?.elapsedMs ?? 0,\n scrollPercent: initial?.scrollPercent ?? 0,\n metadata: { ...(initial?.metadata ?? {}) },\n };\n }\n\n setPath(path: string): void {\n this.context.path = path;\n }\n\n trackEvent(event: string): void {\n const next = new Set(this.context.events ?? new Set<string>());\n next.add(event);\n this.context.events = next;\n }\n\n trackUsage(event: string, delta = 1): void {\n const usage = { ...(this.context.usage ?? {}) };\n usage[event] = (usage[event] ?? 0) + Math.max(1, delta);\n this.context.usage = usage;\n }\n\n trackMilestone(event: string): void {\n const next = new Set(this.context.milestones ?? new Set<string>());\n next.add(event);\n this.context.milestones = next;\n }\n\n setElapsedMs(elapsedMs: number): void {\n this.context.elapsedMs = Math.max(0, elapsedMs);\n }\n\n setScrollPercent(scrollPercent: number): void {\n const clamped = Math.max(0, Math.min(100, scrollPercent));\n this.context.scrollPercent = clamped;\n }\n\n setMetadata(next: Record<string, unknown>): void {\n this.context.metadata = { ...next };\n }\n\n getContext(): TriggerContext {\n return {\n path: this.context.path,\n events: new Set(this.context.events ?? []),\n milestones: new Set(this.context.milestones ?? []),\n usage: { ...(this.context.usage ?? {}) },\n elapsedMs: this.context.elapsedMs,\n scrollPercent: this.context.scrollPercent,\n metadata: { ...(this.context.metadata ?? {}) },\n };\n }\n\n evaluate(trigger: FeatureTrigger | undefined): boolean {\n return isTriggerMatch(trigger, this.context);\n }\n\n evaluateFeature(feature: Pick<FeatureEntry, \"trigger\">): boolean {\n return this.evaluate(feature.trigger);\n }\n}\n","import type {\n AudienceMatchFn,\n AudienceRule,\n FeatureEntry,\n FeatureManifest,\n StorageAdapter,\n UserContext,\n FeatureDependencyState,\n FeatureFlagBridge,\n TriggerContext,\n} from \"./types\";\nimport { compareSemver, satisfiesRange } from \"./semver\";\nimport { isTriggerMatch } from \"./triggers\";\n\n/**\n * Default audience matching logic.\n *\n * For each specified field (plan, role, region), checks if the user's\n * value is included in the allowed list. Fields use AND logic between them,\n * OR logic within each field's array. The `custom` field is ignored by\n * the default matcher — use a custom `AudienceMatchFn` for that.\n */\nexport function matchesAudience(\n audience: AudienceRule,\n userContext: UserContext,\n): boolean {\n if (audience.plan && audience.plan.length > 0) {\n if (!userContext.plan || !audience.plan.includes(userContext.plan)) {\n return false;\n }\n }\n if (audience.role && audience.role.length > 0) {\n if (!userContext.role || !audience.role.includes(userContext.role)) {\n return false;\n }\n }\n if (audience.region && audience.region.length > 0) {\n if (!userContext.region || !audience.region.includes(userContext.region)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Check if a feature's audience allows the given user context.\n *\n * - No `audience` field → visible to all\n * - Empty `audience` ({}) → visible to all\n * - `audience` specified but no `userContext` → hidden (safe default)\n * - Otherwise, delegate to `matchFn` (or default `matchesAudience`)\n */\nfunction isAudienceMatch(\n feature: FeatureEntry,\n userContext?: UserContext,\n matchFn?: AudienceMatchFn,\n): boolean {\n // No audience restriction → show to everyone\n if (!feature.audience) return true;\n\n // Check if audience is empty (no fields with values)\n const { plan, role, region, custom } = feature.audience;\n const hasRules =\n (plan && plan.length > 0) ||\n (role && role.length > 0) ||\n (region && region.length > 0) ||\n (custom && Object.keys(custom).length > 0);\n if (!hasRules) return true;\n\n // Audience specified but no user context → hidden (safe default)\n if (!userContext) return false;\n\n // Use custom matcher if provided, otherwise default\n if (matchFn) return matchFn(feature.audience, userContext);\n return matchesAudience(feature.audience, userContext);\n}\n\nfunction isVersionMatch(feature: FeatureEntry, appVersion?: string): boolean {\n const v = feature.version;\n if (!v || typeof v === \"string\") return true; // string = display only\n if (!appVersion) return false; // Safe default when constraints exist\n if (!v.introduced && !v.showNewUntil && !v.deprecatedAt && !v.showIn) return true;\n\n // Range check\n if (v.showIn && !satisfiesRange(appVersion, v.showIn)) return false;\n\n if (v.introduced && compareSemver(appVersion, v.introduced) < 0) return false;\n if (v.deprecatedAt && compareSemver(appVersion, v.deprecatedAt) >= 0) return false;\n\n // showNewUntil gates \"new\" state only\n if (v.showNewUntil && compareSemver(appVersion, v.showNewUntil) >= 0) return false;\n\n return true;\n}\n\nfunction isFlagMatch(\n feature: FeatureEntry,\n flagBridge?: FeatureFlagBridge,\n userContext?: UserContext,\n): boolean {\n if (!feature.flagKey) return true;\n if (!flagBridge) return false;\n try {\n return flagBridge.isEnabled(feature.flagKey, userContext);\n } catch {\n return false;\n }\n}\n\nfunction isProductMatch(feature: FeatureEntry, product?: string): boolean {\n if (!feature.product || feature.product === \"*\") return true;\n if (!product) return false;\n return feature.product === product;\n}\n\nfunction isDependencyMatch(\n feature: FeatureEntry,\n dismissedIds: ReadonlySet<string>,\n dependencyState?: FeatureDependencyState,\n): boolean {\n const dependsOn = feature.dependsOn;\n if (!dependsOn) return true;\n\n const seenIds = dependencyState?.seenIds;\n const clickedIds = dependencyState?.clickedIds;\n const dismissedDependencyIds = dependencyState?.dismissedIds ?? dismissedIds;\n\n if (dependsOn.seen && dependsOn.seen.length > 0) {\n for (const id of dependsOn.seen) {\n const seen = seenIds?.has(id) ?? false;\n if (!seen && !dismissedDependencyIds.has(id)) return false;\n }\n }\n\n if (dependsOn.clicked && dependsOn.clicked.length > 0) {\n for (const id of dependsOn.clicked) {\n if (!(clickedIds?.has(id) ?? false)) return false;\n }\n }\n\n if (dependsOn.dismissed && dependsOn.dismissed.length > 0) {\n for (const id of dependsOn.dismissed) {\n if (!dismissedDependencyIds.has(id)) return false;\n }\n }\n\n return true;\n}\n\n/**\n * Check if a single feature should show as \"new\".\n *\n * A feature is \"new\" when ALL of these are true:\n * 1. Current time is before `showNewUntil`\n * 2. Feature was released after the watermark (or no watermark exists)\n * 3. Feature has not been individually dismissed\n * 4. If `publishAt` is set, current time must be after it (scheduled publishing)\n * 5. If `audience` is set, user must match the targeting rules\n * 6. If `flagKey` is set, the flag bridge must resolve it as enabled\n * 7. If `product` is set, it must match the current product scope\n */\nexport function isNew(\n feature: FeatureEntry,\n watermark: string | null,\n dismissedIds: ReadonlySet<string>,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): boolean {\n // Already dismissed by the user on this device\n if (dismissedIds.has(feature.id)) return false;\n\n // Audience targeting — check before time-based checks\n if (!isAudienceMatch(feature, userContext, matchAudience)) return false;\n\n // Dependency targeting — defer features until prerequisites are satisfied\n if (!isDependencyMatch(feature, dismissedIds, dependencyState)) return false;\n\n // Version targeting — requires appVersion when constraints exist\n if (!isVersionMatch(feature, appVersion)) return false;\n\n // Feature flag targeting — hide flagged entries unless enabled\n if (!isFlagMatch(feature, flagBridge, userContext)) return false;\n\n // Multi-product targeting — hide entries for other product scopes\n if (!isProductMatch(feature, product)) return false;\n\n // Contextual trigger rules — show only when trigger condition is satisfied.\n if (!isTriggerMatch(feature.trigger, triggerContext)) return false;\n\n const nowMs = now.getTime();\n\n // Scheduled publishing — hidden until publishAt\n if (feature.publishAt) {\n const publishMs = new Date(feature.publishAt).getTime();\n if (nowMs < publishMs) return false;\n }\n\n const showUntilMs = new Date(feature.showNewUntil).getTime();\n\n // Past the display window\n if (nowMs >= showUntilMs) return false;\n\n // If there's a watermark, feature must have been released after it\n if (watermark) {\n const watermarkMs = new Date(watermark).getTime();\n const releasedMs = new Date(feature.releasedAt).getTime();\n if (releasedMs <= watermarkMs) return false;\n }\n\n return true;\n}\n\n/**\n * Get all features that are currently \"new\" for this user.\n */\nexport function getNewFeatures(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): FeatureEntry[] {\n const watermark = storage.getWatermark();\n const dismissedIds = storage.getDismissedIds();\n return manifest.filter((f) =>\n isNew(\n f,\n watermark,\n dismissedIds,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ),\n );\n}\n\n/**\n * Get the count of new features.\n */\nexport function getNewFeatureCount(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): number {\n return getNewFeatures(\n manifest,\n storage,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ).length;\n}\n\n/**\n * Check if a specific sidebar key has a new feature.\n */\nexport function hasNewFeature(\n manifest: FeatureManifest,\n sidebarKey: string,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): boolean {\n const watermark = storage.getWatermark();\n const dismissedIds = storage.getDismissedIds();\n return manifest.some(\n (f) =>\n f.sidebarKey === sidebarKey &&\n isNew(\n f,\n watermark,\n dismissedIds,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ),\n );\n}\n\n/**\n * Get all features sorted by priority (critical first) then by release date (newest first).\n */\nexport function getNewFeaturesSorted(\n manifest: FeatureManifest,\n storage: StorageAdapter,\n now: Date = new Date(),\n userContext?: UserContext,\n matchAudience?: AudienceMatchFn,\n appVersion?: string,\n dependencyState?: FeatureDependencyState,\n triggerContext?: TriggerContext,\n flagBridge?: FeatureFlagBridge,\n product?: string,\n): FeatureEntry[] {\n const priorityOrder = { critical: 0, normal: 1, low: 2 };\n return getNewFeatures(\n manifest,\n storage,\n now,\n userContext,\n matchAudience,\n appVersion,\n dependencyState,\n triggerContext,\n flagBridge,\n product,\n ).sort(\n (a, b) => {\n const pa = priorityOrder[a.priority ?? \"normal\"];\n const pb = priorityOrder[b.priority ?? \"normal\"];\n if (pa !== pb) return pa - pb;\n return (\n new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime()\n );\n },\n );\n}\n","import { getNewFeatures, hasNewFeature } from \"../core\";\nimport type {\n AnalyticsCallbacks,\n AudienceMatchFn,\n FeatureEntry,\n FeatureManifest,\n StorageAdapter,\n UserContext,\n} from \"../types\";\n\nexport interface SignalLike<T> {\n (): T;\n set: (value: T) => void;\n}\n\nexport type CreateSignalLikeFn = <T>(initial: T) => SignalLike<T>;\n\nexport interface AngularFeatureDropOptions {\n manifest: FeatureManifest;\n storage: StorageAdapter;\n createSignal: CreateSignalLikeFn;\n analytics?: AnalyticsCallbacks;\n userContext?: UserContext;\n matchAudience?: AudienceMatchFn;\n appVersion?: string;\n}\n\nexport interface AngularFeatureDropService {\n manifest: FeatureManifest;\n newFeatures: SignalLike<FeatureEntry[]>;\n newCount: () => number;\n newFeaturesSorted: () => FeatureEntry[];\n refresh: () => void;\n dismiss: (id: string) => void;\n dismissAll: () => Promise<void>;\n isNew: (sidebarKey: string) => boolean;\n getFeature: (sidebarKey: string) => FeatureEntry | undefined;\n}\n\nfunction sortByPriority(features: FeatureEntry[]): FeatureEntry[] {\n const priorityOrder = { critical: 0, normal: 1, low: 2 };\n return [...features].sort((a, b) => {\n const pa = priorityOrder[a.priority ?? \"normal\"];\n const pb = priorityOrder[b.priority ?? \"normal\"];\n if (pa !== pb) return pa - pb;\n return new Date(b.releasedAt).getTime() - new Date(a.releasedAt).getTime();\n });\n}\n\nexport function createFeatureDropService(\n options: AngularFeatureDropOptions,\n): AngularFeatureDropService {\n const computeFeatures = (): FeatureEntry[] =>\n getNewFeatures(\n options.manifest,\n options.storage,\n new Date(),\n options.userContext,\n options.matchAudience,\n options.appVersion,\n );\n\n const newFeatures = options.createSignal(computeFeatures());\n\n const refresh = (): void => {\n newFeatures.set(computeFeatures());\n };\n\n const dismiss = (id: string): void => {\n const feature = newFeatures().find((item) => item.id === id);\n options.storage.dismiss(id);\n if (feature) {\n options.analytics?.onFeatureDismissed?.(feature);\n }\n refresh();\n };\n\n const dismissAll = async (): Promise<void> => {\n await options.storage.dismissAll(new Date());\n options.analytics?.onAllDismissed?.();\n refresh();\n };\n\n const newCount = (): number => newFeatures().length;\n\n const isNew = (sidebarKey: string): boolean =>\n hasNewFeature(\n options.manifest,\n sidebarKey,\n options.storage,\n new Date(),\n options.userContext,\n options.matchAudience,\n options.appVersion,\n );\n\n const getFeature = (sidebarKey: string): FeatureEntry | undefined =>\n newFeatures().find((feature) => feature.sidebarKey === sidebarKey);\n\n return {\n manifest: options.manifest,\n newFeatures,\n newCount,\n newFeaturesSorted: () => sortByPriority(newFeatures()),\n refresh,\n dismiss,\n dismissAll,\n isNew,\n getFeature,\n };\n}\n\nexport class FeatureDropService {\n readonly manifest: FeatureManifest;\n readonly newFeatures: SignalLike<FeatureEntry[]>;\n\n private readonly service: AngularFeatureDropService;\n\n constructor(options: AngularFeatureDropOptions) {\n this.service = createFeatureDropService(options);\n this.manifest = this.service.manifest;\n this.newFeatures = this.service.newFeatures;\n }\n\n newCount(): number {\n return this.service.newCount();\n }\n\n newFeaturesSorted(): FeatureEntry[] {\n return this.service.newFeaturesSorted();\n }\n\n refresh(): void {\n this.service.refresh();\n }\n\n dismiss(id: string): void {\n this.service.dismiss(id);\n }\n\n dismissAll(): Promise<void> {\n return this.service.dismissAll();\n }\n\n isNew(sidebarKey: string): boolean {\n return this.service.isNew(sidebarKey);\n }\n\n getFeature(sidebarKey: string): FeatureEntry | undefined {\n return this.service.getFeature(sidebarKey);\n }\n}\n"]}
@@ -126,8 +126,12 @@ interface FeatureEntry {
126
126
  sidebarKey?: string;
127
127
  /** Optional grouping category (e.g. "ai", "billing", "core") */
128
128
  category?: string;
129
+ /** Optional product scope (`"*"`, `"askverdict"`, etc.) for multi-product manifests */
130
+ product?: string;
129
131
  /** Optional URL to link to (e.g. docs page, changelog entry) */
130
132
  url?: string;
133
+ /** Optional feature flag key; requires a flag bridge to evaluate */
134
+ flagKey?: string;
131
135
  /** Entry type — determines default icon/color in UI components */
132
136
  type?: FeatureType;
133
137
  /** Priority level — critical entries get special treatment in UI */