@basestack/flags-wc 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Feature Flags Web Components
2
+
3
+ StencilJS-powered web components for feature-flag previews, bundled with **tsdown** and shipped as **ESM-only** via Bun.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ bun install
9
+ # scripts
10
+ bun run dev # watch build
11
+ bun run build # stencil build + tsdown bundle
12
+ bun run lint # biome
13
+ bun run test # vitest
14
+ ```
15
+
16
+ ## Using the modal
17
+
18
+ ```ts
19
+ import { registerFeatureFlagComponents } from '@example/feature-flags-wc';
20
+
21
+ await registerFeatureFlagComponents();
22
+ ```
23
+
24
+ ```html
25
+ <feature-flag-preview-modal
26
+ open
27
+ api-endpoint="https://flags.basestack.co/api/v1/flags/preview"
28
+ project-key="YOUR_PROJECT_KEY"
29
+ storage-key="bs-flags-preview-state"
30
+ heading="Feature preview dialog"
31
+ subtitle="Select and enable previews tailored to your workflow."
32
+ selection-prompt="Select a preview to inspect"
33
+ selection-placeholder="Choose a feature on the left to preview its details."
34
+ enable-label="Enable"
35
+ enabled-label="Enabled"
36
+ loading-label="Loading feature previews…"
37
+ empty-label="No feature flags are available right now."
38
+ ></feature-flag-preview-modal>
39
+ ```
40
+
41
+ - When `api-endpoint` is omitted, the component uses `https://flags.basestack.co/api/v1/flags/preview`.
42
+ - Provide either `project-key` (header `x-project-key`) or `environment-key` (header `x-environment-key`) for the Basestack API. If neither is provided, built-in mock flags are shown.
43
+ - User choices are persisted to `localStorage` under `storage-key`.
44
+ - All user-facing labels (heading, subtitle, prompts, button text, badges, empty/loading states) are optional attributes so you can localize or reword them per embed.
45
+ - The SDK helpers exported from `src/sdk` give you headless access to fetching and storage logic.
46
+
47
+ ### Feedback modal
48
+
49
+ ```html
50
+ <feature-flag-feedback-modal
51
+ open
52
+ flag-key="command-palette"
53
+ feature-name="Command palette"
54
+ project-key="YOUR_PROJECT_KEY"
55
+ environment-key="YOUR_ENVIRONMENT_KEY"
56
+ heading="Feedback"
57
+ mood-prompt="How did this feature make you feel?"
58
+ rating-prompt="How would you rate the quality of this feature?"
59
+ feedback-label="Your feedback"
60
+ feedback-placeholder="Anything you'd like to add? Your input is valuable to us"
61
+ submit-label="Send Feedback"
62
+ ></feature-flag-feedback-modal>
63
+ ```
64
+
65
+ - Feedback is sent to `https://flags-api.basestack.co/v1/flags/feedback/:flagKey` with the appropriate `x-project-key` or `x-environment-key` header.
66
+
67
+ ## Build outputs
68
+
69
+ - `stencil build` emits the web components as ESM-only custom elements in `dist/components`.
70
+ - `tsdown build` bundles the SDK entry to `dist/sdk` with type declarations for ESM consumption.
@@ -0,0 +1,11 @@
1
+ import type { Components, JSX } from "../types/components";
2
+
3
+ interface FeatureFlagFeedbackModal extends Components.FeatureFlagFeedbackModal, HTMLElement {}
4
+ export const FeatureFlagFeedbackModal: {
5
+ prototype: FeatureFlagFeedbackModal;
6
+ new (): FeatureFlagFeedbackModal;
7
+ };
8
+ /**
9
+ * Used to define this component and all nested components recursively.
10
+ */
11
+ export const defineCustomElement: () => void;
@@ -0,0 +1 @@
1
+ import{proxyCustomElement as e,HTMLElement as t,createEvent as a,h as s,Host as r,transformTag as i}from"@stencil/core/internal/client";const o=e(class extends t{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.feedbackSent=a(this,"feedbackSent",7),this.closed=a(this,"closed",7),this.theme="light",this.apiEndpoint="https://flags-api.basestack.co/v1/flags/feedback",this.open=!1,this.heading="Feedback",this.moodPrompt="How are you feeling after using this feature?",this.ratingPrompt="How satisfied are you with this feature?",this.feedbackLabel="Share your feedback",this.feedbackPlaceholder="Anything else you’d like to add?",this.submitLabel="Submit feedback",this.privacyPolicyUrl="https://basestack.co/legal/privacy",this.privacyPolicyLabel="To keep your data safe, avoid sharing personal or sensitive details.",this.privacyPolicyLinkLabel="Learn more",this.closeLabel="Close",this.mood="HAPPY",this.rating=4,this.description="",this.isSubmitting=!1,this.closeModal=()=>{this.isSubmitting||(this.open=!1,this.closed.emit())}}handleKeydown(e){"Escape"===e.key&&this.open&&this.closeModal()}setMood(e){this.mood=e}setRating(e){this.rating=e}async submitFeedback(e){if(e.preventDefault(),!this.flagKey)return void(this.error="Missing flag key for feedback submission.");if(!this.projectKey&&!this.environmentKey)return void(this.error="Provide either project-key or environment-key.");this.isSubmitting=!0,this.error=void 0;const t={flagKey:this.flagKey,mood:this.mood,rating:this.rating,description:this.description.trim()},a=""+this.apiEndpoint.replace(/\/$/,"");try{const e=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json",...this.projectKey?{"x-project-key":this.projectKey}:{},...this.environmentKey?{"x-environment-key":this.environmentKey}:{}},body:JSON.stringify(t)});if(!e.ok)throw Error(`Failed to send feedback (${e.status})`);this.feedbackSent.emit(t),this.description="",this.rating=4,this.mood="HAPPY",this.open=!1}catch(e){this.error=e instanceof Error?e.message:"Unable to send feedback"}finally{this.isSubmitting=!1}}renderMoodSelector(){return s("div",{class:"section"},s("p",{class:"section-title"},this.moodPrompt),s("div",{class:"options-container"},[{value:"VERY_SAD",label:"Dissatisfied",icon:"😭"},{value:"SAD",label:"Sad",icon:"😟"},{value:"NEUTRAL",label:"Neutral",icon:"😐"},{value:"HAPPY",label:"Happy",icon:"😊"},{value:"VERY_HAPPY",label:"Satisfied",icon:"🤩"}].map((e=>s("button",{type:"button",class:{"option option-mood":!0,"option-active":this.mood===e.value},onClick:()=>this.setMood(e.value),"aria-pressed":this.mood===e.value},s("span",{class:"option-icon","aria-hidden":"true"},e.icon),s("span",{class:"sr-only"},e.label))))))}renderRatingSelector(){return s("div",{class:"section"},s("p",{class:"section-title"},this.ratingPrompt),s("div",{class:"options-container"},[1,2,3,4,5].map((e=>s("button",{type:"button",class:{"option option-rating":!0,"option-active":this.rating===e},onClick:()=>this.setRating(e),"aria-pressed":this.rating===e},e)))))}render(){return s(r,{key:"0addc17f1548dc629774d177a7128843509064d0"},s("div",{key:"4070fd246c13f2cd01fc04f5cdc9d969edf44fe4",class:{modal:!0,open:this.open},role:"dialog","aria-modal":"true","aria-hidden":!this.open,"aria-label":this.heading,onClick:e=>e.stopPropagation()},s("header",{key:"45e101d37c79ddfb8b9c8dc1cb195bcd1abe6c40",class:"modal-header"},s("p",{key:"af55ddd65641fa2218b7a56c2d544493f5746457",class:"modal-header-label"},this.featureName??this.flagKey),s("h2",{key:"d32d5fa88bbd667327ab2403aa9d6d23f27bdc3b",class:"modal-header-title"},this.heading),s("button",{key:"4a36b5a79d4eeb4d6338244b2aa95d7510a4faa3",class:"modal-close-button",type:"button",onClick:this.closeModal,"aria-label":this.closeLabel},s("svg",{key:"41fbd4f1b83b8694b416e21c1b9365bcf09d64fe",xmlns:"http://www.w3.org/2000/svg",height:"24px",viewBox:"0 -960 960 960",width:"24px",fill:"inherit","aria-hidden":"true"},s("path",{key:"0410a21ba1a56f361bf6fdb47ade920693aa3ca5",d:"m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"})))),s("form",{key:"a0423604bc363d5dbd1b422e9de7b38f4547fdf3",class:"content-container",onSubmit:e=>this.submitFeedback(e)},s("div",{key:"dad03557b724e36189e22b51a3bdf963c58ad012",class:"content-wrapper"},this.renderMoodSelector(),this.renderRatingSelector(),s("div",{key:"620b7b1874f0f76102881a349453945455f6cff6",class:"section"},s("label",{key:"5f54d1005bff27d7dc1c54d39ecac62df1e2dab6",class:"section-title",htmlFor:"feedback-text"},this.feedbackLabel),s("textarea",{key:"c14e06ef8000654ffa6ef31f12fc954362d41af3",class:"feedback-input",id:"feedback-text",name:"feedback",value:this.description,placeholder:this.feedbackPlaceholder,onInput:e=>{this.description=e.target.value}}),this.error?s("p",{class:"label-text text-danger"},this.error):null,s("p",{key:"9fe7b463d2787f8c2a64da101aa9ee2fea3daa14",class:"label-text"},this.privacyPolicyLabel," ",s("a",{key:"db22738b7a803be32b46501f9f98746319264ec7",href:this.privacyPolicyUrl,class:"text-link"},this.privacyPolicyLinkLabel),"."))),s("div",{key:"06e18662386f40c0a094a345533da3326adb915a",class:"content-footer"},s("button",{key:"6ea1e0a2972d0d4d603908fb70b4baea2004faea",class:"submit-button",type:"submit",disabled:this.isSubmitting},this.isSubmitting?"Sending…":this.submitLabel)))),s("button",{key:"582935da1e6d52e5bd9c209c135494bc873b8bc0",type:"button",class:{overlay:!0,open:this.open},"aria-label":this.closeLabel,onClick:this.closeModal,onKeyDown:e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),this.closeModal())}}))}static get style(){return'*{box-sizing:border-box;margin:0}:host{--bs-font-family:"Roboto", system-ui, sans-serif;--bs-font-xxLarge:24px;--bs-font-xLarge:20px;--bs-font-large:18px;--bs-font-medium:16px;--bs-font-small:14px;--bs-font-xSmall:12px;--bs-spacing-s1:0.25rem;--bs-spacing-s2:0.5rem;--bs-spacing-s3:0.75rem;--bs-spacing-s4:1rem;--bs-spacing-s5:1.25rem;--bs-spacing-s6:1.875rem;--bs-spacing-s7:2.5rem;--bs-spacing-s8:3.125rem;--bs-white:#ffffff;--bs-black:#000000;--bs-gray-50:#f6f6f6;--bs-gray-100:#eeeeee;--bs-gray-200:#e2e2e2;--bs-gray-300:#cbcbcb;--bs-gray-400:#afafaf;--bs-gray-500:#757575;--bs-gray-600:#545454;--bs-gray-700:#333333;--bs-gray-800:#1f1f1f;--bs-gray-900:#141414;--bs-blue-50:#eff3fe;--bs-blue-100:#d4e2fc;--bs-blue-300:#5b91f5;--bs-blue-400:#276ef1;--bs-blue-500:#1e54b7;--bs-green-50:#e6f2ed;--bs-green-100:#addec9;--bs-green-500:#03703c;--bs-green-600:#03582f;--bs-red-300:#e85c4a;--bs-red-400:#e11900;--bs-primary:var(--bs-blue-400);--bs-danger:var(--bs-red-400);--bs-bg-primary:var(--bs-white);--bs-bg-secondary:var(--bs-gray-50);--bs-text-primary:var(--bs-black);--bs-text-secondary:var(--bs-gray-500);--bs-text-white:var(--bs-white);--bs-primary-button-bg:var(--bs-primary);--bs-primary-button-text:var(--bs-white);--bs-outline-button-text:var(--bs-text-primary);--bs-outline-button-border:var(--bs-gray-200);--bs-neutral-button-bg:"transparent";--bs-neutral-button-text:var(--bs-black);--bs-neutral-button-bg-hover:var(--bs-gray-100);--bs-neutral-button-bg-active:var(--bs-gray-50);--bs-default-tag-bg:var(--bs-gray-200);--bs-default-tag-text:var(--bs-text-primary);--bs-success-tag-bg:var(--bs-green-50);--bs-success-tag-text:var(--bs-green-500);--bs-option-bg-selected:var(--bs-blue-50);--bs-input-border-focus:var(--bs-black);--bs-border-light:var(--bs-gray-50);--bs-border-dark:var(--bs-gray-100);--bs-shadow-sm:rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;--bs-shadow-md:rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;font-family:var(--bs-font-family), serif;font-size:var(--bs-font-small);color:var(--bs-text-primary);line-height:1.4}:host([theme="dark"]){--bs-primary:var(--bs-blue-300);--bs-danger:var(--bs-red-300);--bs-bg-primary:var(--bs-gray-800);--bs-bg-secondary:var(--bs-gray-700);--bs-text-primary:var(--bs-gray-200);--bs-text-secondary:var(--bs-gray-400);--bs-text-white:var(--bs-white);--bs-primary-button-bg:var(--bs-blue-400);--bs-primary-button-text:var(--bs-white);--bs-outline-button-text:var(--bs-gray-300);--bs-outline-button-border:var(--bs-gray-600);--bs-neutral-button-bg:"transparent";--bs-neutral-button-text:var(--bs-gray-300);--bs-neutral-button-bg-hover:var(--bs-gray-600);--bs-neutral-button-bg-active:var(--bs-gray-700);--bs-default-tag-bg:var(--bs-gray-600);--bs-default-tag-text:var(--bs-text-primary);--bs-success-tag-bg:var(--bs-green-100);--bs-success-tag-text:var(--bs-green-600);--bs-option-bg-selected:rgba(91, 145, 245, 0.2);--bs-input-border-focus:var(--bs-gray-500);--bs-border-light:var(--bs-gray-600);--bs-border-dark:var(--bs-gray-700)}.overlay{position:fixed;inset:0;background:rgba(0, 0, 0, 0.35);opacity:0;pointer-events:none;transition:opacity 0.2s ease;border:none;padding:0;z-index:999}.overlay.open{opacity:1;pointer-events:auto}.modal{z-index:1000;position:fixed;top:50%;left:50%;transform:translate(-50%, -48%) scale(0.98);opacity:0;pointer-events:none;border-radius:8px;box-shadow:0 30px 70px rgba(0, 0, 0, 0.25);width:min(420px, 94vw);transition:opacity 0.2s ease, transform 0.22s ease;overflow:hidden;background-color:var(--bs-bg-primary)}.modal.open{opacity:1;pointer-events:auto;transform:translate(-50%, -50%) scale(1)}.modal-large{width:min(980px, 92vw)}.modal-header{position:relative;display:flex;flex-direction:column;padding:var(--bs-spacing-s5);border-bottom:1px solid var(--bs-border-dark);gap:var(--bs-spacing-s2)}.modal-header-label{margin:0;color:var(--bs-text-secondary);font-size:var(--bs-font-small);line-height:1.2}.modal-header-title{margin:0;font-weight:700;font-size:var(--bs-font-large);color:var(--bs-text-primary);line-height:1.2}.modal-close-button{position:absolute;height:32px;width:32px;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;top:var(--bs-spacing-s3);right:var(--bs-spacing-s3);border:none;background-color:var(--bs-neutral-button-bg);fill:var(--bs-neutral-button-text);cursor:pointer;border-radius:50%}.modal-close-button:not(:active):hover{background-color:var(--bs-neutral-button-bg-hover)}.modal-close-button:active{background-color:var(--bs-neutral-button-bg-active)}.content-container{display:flex;flex-direction:column;max-height:calc(90vh - 100px);overflow:auto}.content-wrapper{padding:var(--bs-spacing-s5) var(--bs-spacing-s5) 0 var(--bs-spacing-s5);display:flex;flex-direction:column;gap:var(--bs-spacing-s5)}.content-footer{position:sticky;bottom:0;left:0;z-index:10;background-color:var(--bs-bg-primary);padding:var(--bs-spacing-s5);display:flex;flex-direction:column;gap:var(--bs-spacing-s5)}.section{display:flex;flex-direction:column;gap:var(--bs-spacing-s2)}.section-title{margin:0;font-weight:500;font-size:var(--bs-font-small);color:var(--bs-text-primary)}.options-container{display:flex;gap:var(--bs-spacing-s3);flex-wrap:wrap}.option{position:relative;cursor:pointer;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;height:48px;width:48px;border-radius:8px;font-family:var(--bs-font-family), serif;background-color:var(--bs-bg-secondary);border:2px solid var(--bs-bg-secondary);font-weight:400;padding:0;margin:0}.option-icon{display:inline-flex}.option-mood{font-size:var(--bs-font-xLarge)}.option-rating{color:var(--bs-text-primary);font-size:var(--bs-font-small)}.option-active{background-color:var(--bs-option-bg-selected);border-color:var(--bs-primary)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.feedback-input{box-sizing:border-box;min-height:140px;line-height:1.4;resize:vertical;border-radius:8px;border:none;padding:var(--bs-spacing-s3);font-family:var(--bs-font-family), serif;font-size:var(--bs-font-small);font-weight:400;color:var(--bs-text-primary);background:var(--bs-bg-secondary);outline:none}.feedback-input::placeholder{color:var(--bs-text-secondary)}.feedback-input:focus{outline:2px solid var(--bs-input-border-focus);outline-offset:-2px}.submit-button{display:flex;align-items:center;justify-content:center;height:42px;flex-shrink:0;width:100%;border:none;font-family:var(--bs-font-family), serif;background-color:var(--bs-primary-button-bg);color:var(--bs-primary-button-text);border-radius:8px;padding:0 var(--bs-spacing-s4);font-weight:500;cursor:pointer;font-size:var(--bs-font-small)}.submit-button:disabled{opacity:0.6;cursor:not-allowed}.submit-button:active{opacity:0.9}.label-text{margin:0;color:var(--bs-text-secondary);font-size:var(--bs-font-xSmall);font-family:var(--bs-font-family), serif;font-weight:400}.text-danger{color:var(--bs-danger)}.text-link{color:var(--bs-primary);text-decoration:none;cursor:pointer}'}},[1,"feature-flag-feedback-modal",{theme:[513],flagKey:[1,"flag-key"],featureName:[1,"feature-name"],projectKey:[1,"project-key"],environmentKey:[1,"environment-key"],apiEndpoint:[1,"api-endpoint"],open:[1540],heading:[1],moodPrompt:[1,"mood-prompt"],ratingPrompt:[1,"rating-prompt"],feedbackLabel:[1,"feedback-label"],feedbackPlaceholder:[1,"feedback-placeholder"],submitLabel:[1,"submit-label"],privacyPolicyUrl:[1,"privacy-policy-url"],privacyPolicyLabel:[1,"privacy-policy-label"],privacyPolicyLinkLabel:[1,"privacy-policy-link-label"],closeLabel:[1,"close-label"],mood:[32],rating:[32],description:[32],isSubmitting:[32],error:[32]},[[8,"keydown","handleKeydown"]]]),n=o,b=function(){"undefined"!=typeof customElements&&["feature-flag-feedback-modal"].forEach((e=>{"feature-flag-feedback-modal"===e&&(customElements.get(i(e))||customElements.define(i(e),o))}))};export{n as FeatureFlagFeedbackModal,b as defineCustomElement}
@@ -0,0 +1,11 @@
1
+ import type { Components, JSX } from "../types/components";
2
+
3
+ interface FeatureFlagPreviewModal extends Components.FeatureFlagPreviewModal, HTMLElement {}
4
+ export const FeatureFlagPreviewModal: {
5
+ prototype: FeatureFlagPreviewModal;
6
+ new (): FeatureFlagPreviewModal;
7
+ };
8
+ /**
9
+ * Used to define this component and all nested components recursively.
10
+ */
11
+ export const defineCustomElement: () => void;
@@ -0,0 +1 @@
1
+ import{proxyCustomElement as e,HTMLElement as t,createEvent as a,h as s,Host as i,transformTag as r}from"@stencil/core/internal/client";const n="bs-flags-preview-state",o="https://flags.basestack.co/api/v1/flags/preview",l=[{slug:"header",description:"Controls the header visibility and styling",enabled:!1},{slug:"dashboard",description:"Modernized dashboard with improved workflows and a focused overview.",enabled:!1},{slug:"command-palette",description:"Quickly jump to actions and navigation without leaving your keyboard.",enabled:!1}],b="undefined"!=typeof window,d=(e=n)=>{if(!b)return{};try{const t=window.localStorage.getItem(e);if(!t)return{};const a=JSON.parse(t);return a&&"object"==typeof a?Object.entries(a).reduce(((e,[t,a])=>(e[t]=!!a,e)),{}):{}}catch(e){return console.warn("Failed to read feature flag state from storage",e),{}}},c=e(class extends t{constructor(e){super(),!1!==e&&this.__registerHost(),this.__attachShadow(),this.flagChange=a(this,"flagChange",7),this.closed=a(this,"closed",7),this.theme="light",this.apiEndpoint=o,this.storageKey=n,this.open=!1,this.heading="Feature preview",this.subtitle="Get early access to new features and let us know what you think.",this.closeLabel="Close",this.showCloseButton=!0,this.loadingLabel="Loading features…",this.emptyLabel="No feature flags available at the moment.",this.selectionPrompt="Choose a feature to view",this.selectionPlaceholder="Select a feature on the left to see its details.",this.enableLabel="Enable",this.enabledLabel="Disable",this.previewBadgeLabel="Preview",this.expiresSoonLabel="Expires soon",this.learnMoreLabel="Learn more",this.flags=[],this.loading=!1,this.selectionState=d(this.storageKey),this.closeModal=()=>{this.open=!1,this.closed.emit()},this.selectFlag=e=>{this.selectedFlagId=e},this.toggleSelectedFlag=()=>{if(!this.selectedFlagId)return;const e=!this.selectionState[this.selectedFlagId];this.selectionState=((e,t,a=n)=>{const s={...d(a),[e]:t};return((e,t=n)=>{if(b)try{window.localStorage.setItem(t,JSON.stringify(e))}catch(e){console.warn("Failed to persist feature flag state to storage",e)}})(s,a),s})(this.selectedFlagId,e,this.storageKey);const t=this.flags.find((e=>e.slug===this.selectedFlagId));this.flagChange?.emit({slug:this.selectedFlagId,enabled:e,flag:t})}}async componentWillLoad(){await this.loadFlags()}handleEndpointChange(e,t){e!==t&&this.loadFlags()}handleKeydown(e){"Escape"===e.key&&this.open&&this.closeModal()}async loadFlags(){this.loading=!0,this.error=void 0;try{const e=await async function(e={}){const{endpoint:t=o,projectKey:a,environmentKey:s,init:i}=e,r=t===o;if(!a&&!s&&!i?.headers&&r)return l;const n={Accept:"application/json"};a&&(n["x-project-key"]=a),s&&(n["x-environment-key"]=s);const b=await fetch(t,{...i,headers:{...n,...i?.headers}});if(!b.ok)throw Error(`Failed to fetch feature flags (${b.status})`);const d=await b.json(),c=Array.isArray(d)?d:Array.isArray(d?.flags)?d.flags:null;if(!Array.isArray(c))throw Error("Unexpected feature flag payload received from API");const g=c.map((e=>(e=>e?.slug&&e?.description?{slug:e.slug+"",description:e.description,enabled:e.enabled,expiredAt:e.expiredAt,name:e.name??e.slug,previewImage:e.previewImage,documentationLink:e.documentationLink,previewName:e.previewName,previewContent:e.previewContent}:null)(e))).filter((e=>!!e));if(0===g.length)throw Error("Feature flag payload did not include any usable flags");return g}({endpoint:this.apiEndpoint,projectKey:this.projectKey,environmentKey:this.environmentKey});this.flags=e,!this.selectedFlagId&&e.length>0&&(this.selectedFlagId=e[0].slug)}catch(e){this.error=e instanceof Error?e.message:"Unable to load feature flags"}finally{this.loading=!1}}selectFlagByKey(e,t){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),this.selectFlag(t))}get selectedFlag(){return this.flags.find((e=>e.slug===this.selectedFlagId))}renderFlagBadge(e){const t=!!this.selectionState[e.slug],a=e.previewName??e.name??e.slug;return s("button",{class:{"flag-item":!0,active:e.slug===this.selectedFlagId},type:"button",onClick:()=>this.selectFlag(e.slug),onKeyDown:t=>this.selectFlagByKey(t,e.slug)},s("div",{class:"flag-item-icon","aria-hidden":"true"},s("svg",{xmlns:"http://www.w3.org/2000/svg",height:"24px",viewBox:"0 -960 960 960",width:"24px",fill:"inherit","aria-hidden":"true"},s("path",t?{d:"M280-240q-100 0-170-70T40-480q0-100 70-170t170-70h400q100 0 170 70t70 170q0 100-70 170t-170 70H280Zm0-80h400q66 0 113-47t47-113q0-66-47-113t-113-47H280q-66 0-113 47t-47 113q0 66 47 113t113 47Zm400-40q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM480-480Z"}:{d:"M280-240q-100 0-170-70T40-480q0-100 70-170t170-70h400q100 0 170 70t70 170q0 100-70 170t-170 70H280Zm0-80h400q66 0 113-47t47-113q0-66-47-113t-113-47H280q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-40q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Zm200-120Z"}))),s("div",{class:"flag-item-text-wrapper"},s("div",{class:"flag-name"},a),s("div",{class:"tag tag-default"},s("span",null,e.expiredAt?this.expiresSoonLabel:this.previewBadgeLabel))),t?s("span",{class:"tag tag-enabled"},this.enabledLabel):null)}renderList(){return this.loading?s("div",{class:"empty-state"},s("p",{class:"label-text"},this.loadingLabel)):this.error?s("div",{class:"empty-state"},s("p",{class:"label-text text-danger"},this.error)):0===this.flags.length?s("div",{class:"empty-state"},s("p",{class:"label-text"},this.emptyLabel)):s("div",{class:"flag-list"},this.flags.map((e=>this.renderFlagBadge(e))))}renderDetails(){const e=this.selectedFlag,t=!!e&&!!this.selectionState[e.slug],a=e?.previewName??e?.name??e?.slug??this.selectionPrompt;return s("section",{class:"details"},s("header",{class:"details-header"},s("div",{class:"details-header-content"},s("p",{class:"label-text"},this.heading),s("h2",{class:"details-header-title"},a)),s("button",{class:"enable-button",type:"button",onClick:this.toggleSelectedFlag,disabled:!e,"aria-pressed":t},t?this.enabledLabel:this.enableLabel)),s("div",{class:"details-body"},e?s("div",{class:"details-content"},e.previewImage&&s("div",{class:"details-image-wrapper"},s("img",{src:e.previewImage,alt:e.name+" preview"})),e.previewContent?s("div",{class:"details-description",innerHTML:e.previewContent}):s("p",{class:"details-description"},e.description),e.documentationLink?s("a",{class:"details-link",href:e.documentationLink,target:"_blank",rel:"noreferrer"},this.learnMoreLabel):null):s("div",{class:"details-placeholder"},s("p",{class:"label-text"},this.selectionPlaceholder))))}render(){return s(i,{key:"6e019be658f154bd34c11218dd26b300e35c9219"},s("div",{key:"37582dbcd9de86bbdbd7188322c8d6728320648c",class:{"modal modal-large":!0,open:this.open},role:"dialog","aria-modal":"true","aria-hidden":!this.open,"aria-label":this.heading,onClick:e=>e.stopPropagation()},s("header",{key:"953f01dc7c481c8055e08336c147da88b75d7b9a",class:"modal-header"},s("h2",{key:"ed82ae0447773c75ccd5676fd6b8fac2c1e459a8",class:"modal-header-title"},this.heading),s("p",{key:"9f1259ceb1edce1a6f2015c4f2be268c7c066d57",class:"modal-header-label"},this.subtitle),this.showCloseButton?s("button",{class:"modal-close-button",type:"button",onClick:this.closeModal,"aria-label":this.closeLabel},s("svg",{xmlns:"http://www.w3.org/2000/svg",height:"24px",viewBox:"0 -960 960 960",width:"24px",fill:"inherit","aria-hidden":"true"},s("path",{d:"m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"}))):null),s("main",{key:"076abb011a394862a2bb33bd73c962eea3a28b62",class:"content-container"},s("aside",{key:"78ceb7167672d6f874275a7fe3f95bb0b81b10eb",class:"sidebar"},this.renderList()),this.renderDetails())),s("button",{key:"1ec64ac5132964042ec02af9118ba6abac28adc2",type:"button",class:{overlay:!0,open:this.open},tabIndex:0,"aria-label":this.closeLabel,onClick:this.closeModal,onKeyDown:e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),this.closeModal())}}))}static get watchers(){return{apiEndpoint:[{handleEndpointChange:0}]}}static get style(){return'*{box-sizing:border-box;margin:0}:host{--bs-font-family:"Roboto", system-ui, sans-serif;--bs-font-xxLarge:24px;--bs-font-xLarge:20px;--bs-font-large:18px;--bs-font-medium:16px;--bs-font-small:14px;--bs-font-xSmall:12px;--bs-spacing-s1:0.25rem;--bs-spacing-s2:0.5rem;--bs-spacing-s3:0.75rem;--bs-spacing-s4:1rem;--bs-spacing-s5:1.25rem;--bs-spacing-s6:1.875rem;--bs-spacing-s7:2.5rem;--bs-spacing-s8:3.125rem;--bs-white:#ffffff;--bs-black:#000000;--bs-gray-50:#f6f6f6;--bs-gray-100:#eeeeee;--bs-gray-200:#e2e2e2;--bs-gray-300:#cbcbcb;--bs-gray-400:#afafaf;--bs-gray-500:#757575;--bs-gray-600:#545454;--bs-gray-700:#333333;--bs-gray-800:#1f1f1f;--bs-gray-900:#141414;--bs-blue-50:#eff3fe;--bs-blue-100:#d4e2fc;--bs-blue-300:#5b91f5;--bs-blue-400:#276ef1;--bs-blue-500:#1e54b7;--bs-green-50:#e6f2ed;--bs-green-100:#addec9;--bs-green-500:#03703c;--bs-green-600:#03582f;--bs-red-300:#e85c4a;--bs-red-400:#e11900;--bs-primary:var(--bs-blue-400);--bs-danger:var(--bs-red-400);--bs-bg-primary:var(--bs-white);--bs-bg-secondary:var(--bs-gray-50);--bs-text-primary:var(--bs-black);--bs-text-secondary:var(--bs-gray-500);--bs-text-white:var(--bs-white);--bs-primary-button-bg:var(--bs-primary);--bs-primary-button-text:var(--bs-white);--bs-outline-button-text:var(--bs-text-primary);--bs-outline-button-border:var(--bs-gray-200);--bs-neutral-button-bg:"transparent";--bs-neutral-button-text:var(--bs-black);--bs-neutral-button-bg-hover:var(--bs-gray-100);--bs-neutral-button-bg-active:var(--bs-gray-50);--bs-default-tag-bg:var(--bs-gray-200);--bs-default-tag-text:var(--bs-text-primary);--bs-success-tag-bg:var(--bs-green-50);--bs-success-tag-text:var(--bs-green-500);--bs-option-bg-selected:var(--bs-blue-50);--bs-input-border-focus:var(--bs-black);--bs-border-light:var(--bs-gray-50);--bs-border-dark:var(--bs-gray-100);--bs-shadow-sm:rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;--bs-shadow-md:rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;font-family:var(--bs-font-family), serif;font-size:var(--bs-font-small);color:var(--bs-text-primary);line-height:1.4}:host([theme="dark"]){--bs-primary:var(--bs-blue-300);--bs-danger:var(--bs-red-300);--bs-bg-primary:var(--bs-gray-800);--bs-bg-secondary:var(--bs-gray-700);--bs-text-primary:var(--bs-gray-200);--bs-text-secondary:var(--bs-gray-400);--bs-text-white:var(--bs-white);--bs-primary-button-bg:var(--bs-blue-400);--bs-primary-button-text:var(--bs-white);--bs-outline-button-text:var(--bs-gray-300);--bs-outline-button-border:var(--bs-gray-600);--bs-neutral-button-bg:"transparent";--bs-neutral-button-text:var(--bs-gray-300);--bs-neutral-button-bg-hover:var(--bs-gray-600);--bs-neutral-button-bg-active:var(--bs-gray-700);--bs-default-tag-bg:var(--bs-gray-600);--bs-default-tag-text:var(--bs-text-primary);--bs-success-tag-bg:var(--bs-green-100);--bs-success-tag-text:var(--bs-green-600);--bs-option-bg-selected:rgba(91, 145, 245, 0.2);--bs-input-border-focus:var(--bs-gray-500);--bs-border-light:var(--bs-gray-600);--bs-border-dark:var(--bs-gray-700)}.overlay{position:fixed;inset:0;background:rgba(0, 0, 0, 0.35);opacity:0;pointer-events:none;transition:opacity 0.2s ease;border:none;padding:0;z-index:999}.overlay.open{opacity:1;pointer-events:auto}.modal{z-index:1000;position:fixed;top:50%;left:50%;transform:translate(-50%, -48%) scale(0.98);opacity:0;pointer-events:none;border-radius:8px;box-shadow:0 30px 70px rgba(0, 0, 0, 0.25);width:min(420px, 94vw);transition:opacity 0.2s ease, transform 0.22s ease;overflow:hidden;background-color:var(--bs-bg-primary)}.modal.open{opacity:1;pointer-events:auto;transform:translate(-50%, -50%) scale(1)}.modal-large{width:min(980px, 92vw)}.modal-header{position:relative;display:flex;flex-direction:column;padding:var(--bs-spacing-s5);border-bottom:1px solid var(--bs-border-dark);gap:var(--bs-spacing-s2)}.modal-header-label{margin:0;color:var(--bs-text-secondary);font-size:var(--bs-font-small);line-height:1.2}.modal-header-title{margin:0;font-weight:700;font-size:var(--bs-font-large);color:var(--bs-text-primary);line-height:1.2}.modal-close-button{position:absolute;height:32px;width:32px;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;top:var(--bs-spacing-s3);right:var(--bs-spacing-s3);border:none;background-color:var(--bs-neutral-button-bg);fill:var(--bs-neutral-button-text);cursor:pointer;border-radius:50%}.modal-close-button:not(:active):hover{background-color:var(--bs-neutral-button-bg-hover)}.modal-close-button:active{background-color:var(--bs-neutral-button-bg-active)}.content-container{display:grid;grid-template-columns:minmax(240px, 34%) 1fr;min-height:480px}.sidebar{display:flex;flex-direction:column;border-right:1px solid var(--bs-border-dark)}.flag-list{display:flex;flex-direction:column;max-height:calc(90vh - 110px);overflow:auto}.flag-item{width:100%;display:flex;align-items:center;gap:var(--bs-spacing-s3);padding:var(--bs-spacing-s4) var(--bs-spacing-s5);cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid var(--bs-border-dark);color:var(--bs-text-primary)}.flag-item.active{background-color:var(--bs-primary-button-bg);color:var(--bs-text-white)}.flag-item svg{fill:var(--bs-text-secondary)}.flag-item.active svg{fill:var(--bs-text-white)}.flag-item-icon{display:inline-flex}.flag-item-text-wrapper{display:flex;flex-direction:column;align-items:flex-start;flex-grow:1;gap:var(--bs-spacing-s2)}.flag-name{font-weight:500;line-height:1.3;font-size:var(--bs-font-small);font-family:var(--bs-font-family), serif}.tag{padding:var(--bs-spacing-s1) var(--bs-spacing-s2);font-weight:400;font-size:var(--bs-font-xSmall);font-family:var(--bs-font-family), serif;border-radius:4px}.tag-default{background:var(--bs-default-tag-bg);color:var(--bs-default-tag-text)}.tag-enabled{background:var(--bs-success-tag-bg);color:var(--bs-success-tag-text)}.details{display:flex;flex-direction:column;max-height:calc(90vh - 110px);overflow:auto}.details-header{background-color:var(--bs-bg-primary);position:sticky;z-index:10;top:0;left:0;display:flex;align-items:center;justify-content:space-between;padding:var(--bs-spacing-s4) var(--bs-spacing-s5) var(--bs-spacing-s5) var(--bs-spacing-s5);gap:var(--bs-spacing-s3)}.details-header-content{display:flex;flex-direction:column;gap:var(--bs-spacing-s2)}.details-header-title{margin:0;font-size:var(--bs-font-xLarge);font-family:var(--bs-font-family), serif;font-weight:700;line-height:1.2}.enable-button{border:1px solid var(--bs-outline-button-border);display:flex;flex-shrink:0;align-items:center;font-family:var(--bs-font-family), serif;background-color:transparent;color:var(--bs-outline-button-text);border-radius:8px;height:38px;padding:0 var(--bs-spacing-s4);font-weight:500;cursor:pointer;font-size:var(--bs-font-small)}.enable-button:disabled{opacity:0.6;cursor:not-allowed}.enable-button:active{opacity:0.7}.details-body{flex-grow:1;padding:0 var(--bs-spacing-s5) var(--bs-spacing-s5) var(--bs-spacing-s5)}.details-content{display:flex;flex-direction:column;gap:var(--bs-spacing-s5)}.details-image-wrapper{display:flex;flex-direction:column;position:relative;border-radius:8px;overflow:hidden;background:var(--bs-bg-secondary)}.details-image-wrapper img{width:100%;height:auto}.details-description{color:var(--bs-text-primary);font-size:var(--bs-font-small);font-family:var(--bs-font-family), serif;font-weight:400;line-height:1.4}.details-description h1,.details-description h2,.details-description h3,.details-description h4,.details-description h5,.details-description h6{margin:0.5em 0;line-height:1.3}.details-description h1{font-size:1.8em}.details-description h2{font-size:1.5em}.details-description h3{font-size:1.3em}.details-description p{margin:0.5em 0}.details-description ul,.details-description ol{margin:0.5em 0;padding-left:1.5em}.details-description li{margin:0.25em 0}.details-description a{color:var(--bs-primary);text-decoration:none}.details-description a:hover{text-decoration:underline}.details-description code{background-color:rgba(0, 0, 0, 0.1);padding:2px 4px;border-radius:3px;font-family:monospace;font-size:0.9em}.details-description blockquote{border-left:3px solid var(--bs-border-dark);padding-left:16px;margin:8px 0;color:var(--bs-text-secondary)}.details-description strong{font-weight:600}.details-description em{font-style:italic}.details-link{font-size:var(--bs-font-small);font-weight:400;color:var(--bs-primary);text-decoration:none;cursor:pointer}.details-link:hover{text-decoration:underline}.details-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;height:100%;background-color:var(--bs-bg-secondary);border-radius:8px;padding:var(--bs-spacing-s5)}.empty-state{padding:var(--bs-spacing-s4) var(--bs-spacing-s5)}.label-text{color:var(--bs-text-secondary);font-size:var(--bs-font-small);font-family:var(--bs-font-family), serif;font-weight:400;line-height:1.3}.text-danger{color:var(--bs-danger)}@media (max-width: 720px){.modal{width:96vw}.content-container{grid-template-columns:1fr;min-height:0}.sidebar{border-right:none;border-bottom:1px solid var(--bs-border-dark)}.flag-list{max-height:300px}.details{max-height:calc(90vh - 410px);min-height:180px}}'}},[1,"feature-flag-preview-modal",{theme:[513],apiEndpoint:[1,"api-endpoint"],projectKey:[1,"project-key"],environmentKey:[1,"environment-key"],storageKey:[1,"storage-key"],open:[1540],heading:[1],subtitle:[1],closeLabel:[1,"close-label"],showCloseButton:[4,"show-close-button"],loadingLabel:[1,"loading-label"],emptyLabel:[1,"empty-label"],selectionPrompt:[1,"selection-prompt"],selectionPlaceholder:[1,"selection-placeholder"],enableLabel:[1,"enable-label"],enabledLabel:[1,"enabled-label"],previewBadgeLabel:[1,"preview-badge-label"],expiresSoonLabel:[1,"expires-soon-label"],learnMoreLabel:[1,"learn-more-label"],flags:[32],loading:[32],error:[32],selectedFlagId:[32],selectionState:[32]},[[8,"keydown","handleKeydown"]],{apiEndpoint:[{handleEndpointChange:0}]}]),g=c,p=function(){"undefined"!=typeof customElements&&["feature-flag-preview-modal"].forEach((e=>{"feature-flag-preview-modal"===e&&(customElements.get(r(e))||customElements.define(r(e),c))}))};export{g as FeatureFlagPreviewModal,p as defineCustomElement}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Get the base path to where the assets can be found. Use "setAssetPath(path)"
3
+ * if the path needs to be customized.
4
+ */
5
+ export declare const getAssetPath: (path: string) => string;
6
+
7
+ /**
8
+ * Used to manually set the base path where assets can be found.
9
+ * If the script is used as "module", it's recommended to use "import.meta.url",
10
+ * such as "setAssetPath(import.meta.url)". Other options include
11
+ * "setAssetPath(document.currentScript.src)", or using a bundler's replace plugin to
12
+ * dynamically set the path at build time, such as "setAssetPath(process.env.ASSET_PATH)".
13
+ * But do note that this configuration depends on how your script is bundled, or lack of
14
+ * bundling, and where your assets can be loaded from. Additionally custom bundling
15
+ * will have to ensure the static assets are copied to its build directory.
16
+ */
17
+ export declare const setAssetPath: (path: string) => void;
18
+
19
+ /**
20
+ * Used to specify a nonce value that corresponds with an application's CSP.
21
+ * When set, the nonce will be added to all dynamically created script and style tags at runtime.
22
+ * Alternatively, the nonce value can be set on a meta tag in the DOM head
23
+ * (<meta name="csp-nonce" content="{ nonce value here }" />) which
24
+ * will result in the same behavior.
25
+ */
26
+ export declare const setNonce: (nonce: string) => void
27
+
28
+ export interface SetPlatformOptions {
29
+ raf?: (c: FrameRequestCallback) => number;
30
+ ael?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;
31
+ rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;
32
+ }
33
+ export declare const setPlatformOptions: (opts: SetPlatformOptions) => void;
@@ -0,0 +1 @@
1
+ export{getAssetPath,render,setAssetPath,setNonce,setPlatformOptions}from"@stencil/core/internal/client";
@@ -0,0 +1,47 @@
1
+ //#region src/lib/feature-flags.d.ts
2
+ type FeatureFlagState = Record<string, boolean>;
3
+ interface FeatureFlag {
4
+ slug: string;
5
+ description: string;
6
+ enabled?: boolean;
7
+ expiredAt?: string | null;
8
+ name?: string;
9
+ previewImage?: string;
10
+ documentationLink?: string;
11
+ previewName?: string;
12
+ previewContent?: string;
13
+ }
14
+ interface FetchFlagsOptions {
15
+ endpoint?: string;
16
+ projectKey?: string;
17
+ environmentKey?: string;
18
+ init?: RequestInit;
19
+ }
20
+ interface FlagSelectionPayload {
21
+ slug: string;
22
+ enabled: boolean;
23
+ flag?: FeatureFlag;
24
+ }
25
+ declare const DEFAULT_STORAGE_KEY = "bs-flags-preview-state";
26
+ declare const DEFAULT_FLAGS_ENDPOINT = "https://flags.basestack.co/api/v1/flags/preview";
27
+ declare function fetchFeatureFlags(options?: FetchFlagsOptions): Promise<FeatureFlag[]>;
28
+ declare const readSelectionState: (storageKey?: string) => FeatureFlagState;
29
+ declare const writeSelectionState: (state: FeatureFlagState, storageKey?: string) => void;
30
+ declare const toggleFlagState: (flagSlug: string, enabled: boolean, storageKey?: string) => FeatureFlagState;
31
+ //#endregion
32
+ //#region src/sdk/index.d.ts
33
+ declare const registerFeatureFlagComponents: () => Promise<void>;
34
+ declare class FeatureFlagClient {
35
+ private readonly endpoint?;
36
+ private readonly storageKey;
37
+ constructor(endpoint?: string | undefined, storageKey?: string);
38
+ listFlags(params?: {
39
+ projectKey?: string;
40
+ environmentKey?: string;
41
+ }): Promise<FeatureFlag[]>;
42
+ loadSelections(): FeatureFlagState;
43
+ setSelection(flagId: string, enabled: boolean): FeatureFlagState;
44
+ writeSelections(state: FeatureFlagState): void;
45
+ }
46
+ //#endregion
47
+ export { DEFAULT_FLAGS_ENDPOINT, DEFAULT_STORAGE_KEY, type FeatureFlag, FeatureFlagClient, type FeatureFlagState, type FlagSelectionPayload, fetchFeatureFlags, readSelectionState, registerFeatureFlagComponents, toggleFlagState, writeSelectionState };
@@ -0,0 +1 @@
1
+ const e=`bs-flags-preview-state`,t=`https://flags.basestack.co/api/v1/flags/preview`,n=[{slug:`header`,description:`Controls the header visibility and styling`,enabled:!1},{slug:`dashboard`,description:`Modernized dashboard with improved workflows and a focused overview.`,enabled:!1},{slug:`command-palette`,description:`Quickly jump to actions and navigation without leaving your keyboard.`,enabled:!1}],r=typeof window<`u`,i=e=>!e?.slug||!e?.description?null:{slug:String(e.slug),description:e.description,enabled:e.enabled,expiredAt:e.expiredAt,name:e.name??e.slug,previewImage:e.previewImage,documentationLink:e.documentationLink,previewName:e.previewName,previewContent:e.previewContent};async function a(e={}){let{endpoint:r=t,projectKey:a,environmentKey:o,init:s}=e,c=r===t;if(!a&&!o&&!s?.headers&&c)return n;let l={Accept:`application/json`};a&&(l[`x-project-key`]=a),o&&(l[`x-environment-key`]=o);let u=await fetch(r,{...s,headers:{...l,...s?.headers}});if(!u.ok)throw Error(`Failed to fetch feature flags (${u.status})`);let d=await u.json(),f=Array.isArray(d)?d:Array.isArray(d?.flags)?d.flags:null;if(!Array.isArray(f))throw Error(`Unexpected feature flag payload received from API`);let p=f.map(e=>i(e)).filter(e=>!!e);if(p.length===0)throw Error(`Feature flag payload did not include any usable flags`);return p}const o=(t=e)=>{if(!r)return{};try{let e=window.localStorage.getItem(t);if(!e)return{};let n=JSON.parse(e);return!n||typeof n!=`object`?{}:Object.entries(n).reduce((e,[t,n])=>(e[t]=!!n,e),{})}catch(e){return console.warn(`Failed to read feature flag state from storage`,e),{}}},s=(t,n=e)=>{if(r)try{window.localStorage.setItem(n,JSON.stringify(t))}catch(e){console.warn(`Failed to persist feature flag state to storage`,e)}},c=(t,n,r=e)=>{let i={...o(r),[t]:n};return s(i,r),i},l=async()=>{let[e,t]=await Promise.all([import(`../components/feature-flag-preview-modal`),import(`../components/feature-flag-feedback-modal`)]);typeof e.defineCustomElement==`function`&&e.defineCustomElement(),typeof t.defineCustomElement==`function`&&t.defineCustomElement()};var u=class{constructor(t,n=e){this.endpoint=t,this.storageKey=n}async listFlags(e){return a({endpoint:this.endpoint??t,projectKey:e?.projectKey,environmentKey:e?.environmentKey})}loadSelections(){return o(this.storageKey)}setSelection(e,t){return c(e,t,this.storageKey)}writeSelections(e){s(e,this.storageKey)}};export{t as DEFAULT_FLAGS_ENDPOINT,e as DEFAULT_STORAGE_KEY,u as FeatureFlagClient,a as fetchFeatureFlags,o as readSelectionState,l as registerFeatureFlagComponents,c as toggleFlagState,s as writeSelectionState};
@@ -0,0 +1,82 @@
1
+ import { type EventEmitter } from "../../stencil-public-runtime";
2
+ type Mood = "VERY_SAD" | "VERY_HAPPY" | "HAPPY" | "NEUTRAL" | "SAD";
3
+ export interface FeedbackPayload {
4
+ flagKey: string;
5
+ mood: Mood;
6
+ rating: number;
7
+ description: string;
8
+ }
9
+ export declare class FeatureFlagFeedbackModal {
10
+ theme: "light" | "dark";
11
+ /**
12
+ * The Basestack flag key this feedback is for.
13
+ */
14
+ flagKey: string;
15
+ /**
16
+ * Human-friendly name of the feature to display in the UI.
17
+ */
18
+ featureName?: string;
19
+ /**
20
+ * Project key header (x-project-key).
21
+ */
22
+ projectKey?: string;
23
+ /**
24
+ * Environment key header (x-environment-key).
25
+ */
26
+ environmentKey?: string;
27
+ /**
28
+ * Endpoint used to POST feedback. Defaults to the Basestack feedback API.
29
+ */
30
+ apiEndpoint: string;
31
+ /**
32
+ * Controls dialog visibility.
33
+ */
34
+ open: boolean;
35
+ /**
36
+ * Dialog heading text.
37
+ */
38
+ heading: string;
39
+ /**
40
+ * Label above the mood selector.
41
+ */
42
+ moodPrompt: string;
43
+ /**
44
+ * Label above the rating selector.
45
+ */
46
+ ratingPrompt: string;
47
+ /**
48
+ * Label above the textarea input.
49
+ */
50
+ feedbackLabel: string;
51
+ /**
52
+ * Placeholder text inside the textarea input.
53
+ */
54
+ feedbackPlaceholder: string;
55
+ /**
56
+ * Label for the submit button.
57
+ */
58
+ submitLabel: string;
59
+ privacyPolicyUrl: string;
60
+ privacyPolicyLabel: string;
61
+ privacyPolicyLinkLabel: string;
62
+ /**
63
+ * Label for the close controls.
64
+ */
65
+ closeLabel: string;
66
+ private mood;
67
+ private rating;
68
+ private description;
69
+ private isSubmitting;
70
+ private error?;
71
+ feedbackSent: EventEmitter<FeedbackPayload>;
72
+ closed: EventEmitter<void>;
73
+ protected handleKeydown(event: KeyboardEvent): void;
74
+ private closeModal;
75
+ private setMood;
76
+ private setRating;
77
+ private submitFeedback;
78
+ private renderMoodSelector;
79
+ private renderRatingSelector;
80
+ render(): any;
81
+ }
82
+ export {};
@@ -0,0 +1,97 @@
1
+ import { type EventEmitter } from "../../stencil-public-runtime";
2
+ import { type FlagSelectionPayload } from "../../lib/feature-flags";
3
+ export declare class FeatureFlagPreviewModal {
4
+ theme: "light" | "dark";
5
+ /**
6
+ * Endpoint used to fetch feature flags. If omitted, defaults to the Basestack preview API.
7
+ */
8
+ apiEndpoint: string;
9
+ /**
10
+ * Project key header (x-project-key).
11
+ */
12
+ projectKey?: string;
13
+ /**
14
+ * Environment key header (x-environment-key).
15
+ */
16
+ environmentKey?: string;
17
+ /**
18
+ * Local storage key used to persist enabled flags.
19
+ */
20
+ storageKey: string;
21
+ /**
22
+ * Controls dialog visibility.
23
+ */
24
+ open: boolean;
25
+ /**
26
+ * Dialog title.
27
+ */
28
+ heading: string;
29
+ /**
30
+ * Optional supporting copy under the title.
31
+ */
32
+ subtitle: string;
33
+ /**
34
+ * Label for the close button and overlay.
35
+ */
36
+ closeLabel: string;
37
+ /**
38
+ * When true, renders the close button.
39
+ */
40
+ showCloseButton: boolean;
41
+ /**
42
+ * Label shown when loading previews.
43
+ */
44
+ loadingLabel: string;
45
+ /**
46
+ * Label shown when no previews are available.
47
+ */
48
+ emptyLabel: string;
49
+ /**
50
+ * Title shown when nothing is selected.
51
+ */
52
+ selectionPrompt: string;
53
+ /**
54
+ * Placeholder text shown in the details panel when nothing is selected.
55
+ */
56
+ selectionPlaceholder: string;
57
+ /**
58
+ * Label for the primary enable button when a flag is disabled.
59
+ */
60
+ enableLabel: string;
61
+ /**
62
+ * Label for the primary enable button when a flag is enabled.
63
+ */
64
+ enabledLabel: string;
65
+ /**
66
+ * Label for the preview badge.
67
+ */
68
+ previewBadgeLabel: string;
69
+ /**
70
+ * Label for the expiration badge.
71
+ */
72
+ expiresSoonLabel: string;
73
+ /**
74
+ * Label for the documentation link.
75
+ */
76
+ learnMoreLabel: string;
77
+ private flags;
78
+ private loading;
79
+ private error?;
80
+ private selectedFlagId?;
81
+ private selectionState;
82
+ flagChange: EventEmitter<FlagSelectionPayload>;
83
+ closed: EventEmitter<void>;
84
+ componentWillLoad(): Promise<void>;
85
+ protected handleEndpointChange(newValue: string | undefined, oldValue: string | undefined): void;
86
+ protected handleKeydown(event: KeyboardEvent): void;
87
+ private loadFlags;
88
+ private closeModal;
89
+ private selectFlag;
90
+ private selectFlagByKey;
91
+ private toggleSelectedFlag;
92
+ private get selectedFlag();
93
+ private renderFlagBadge;
94
+ private renderList;
95
+ private renderDetails;
96
+ render(): any;
97
+ }