@showwhat/configurator 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @showwhat/configurator
2
2
 
3
- A reusable React component library for visually editing showwhat feature flag definitions. Provides a complete rule-builder UI bring your own store and persistence.
3
+ A reusable React component library for visually editing showwhat definitions like Swagger UI for your flag and config rules.
4
+
5
+ Provides a complete rule-builder UI while letting your app own storage, workflow, and persistence.
4
6
 
5
7
  ## Installation
6
8
 
package/dist/index.d.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  import { Condition, Definition, Definitions, Variation, Presets, ConditionEvaluator, Resolution } from 'showwhat';
2
2
  import { ClassValue } from 'clsx';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
- import * as class_variance_authority_types from 'class-variance-authority/types';
5
4
  import * as React$1 from 'react';
6
5
  import React__default, { Component, ReactNode, ErrorInfo, ComponentType } from 'react';
7
- import { VariantProps } from 'class-variance-authority';
8
6
  import { Select as Select$1, Separator as Separator$1, ScrollArea as ScrollArea$1, Dialog as Dialog$1, DropdownMenu as DropdownMenu$1, Label as Label$1, Switch as Switch$1, Popover as Popover$1, Tabs as Tabs$1 } from 'radix-ui';
9
7
 
10
8
  type DefinitionListProps = {
@@ -86,6 +84,7 @@ type ValueInputProps = {
86
84
  type DateTimeInputProps = {
87
85
  value: string;
88
86
  onChange: (value: string) => void;
87
+ disabled?: boolean;
89
88
  };
90
89
  type ValidationIssueDisplay = {
91
90
  path: (string | number)[];
@@ -115,11 +114,34 @@ declare const BUILTIN_CONDITION_TYPES: ConditionTypeMeta[];
115
114
  declare const CONDITION_TYPE_MAP: Map<string, ConditionTypeMeta>;
116
115
  declare function getConditionMeta(type: string): ConditionTypeMeta | undefined;
117
116
 
118
- declare const buttonVariants: (props?: ({
119
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
120
- size?: "sm" | "default" | "xs" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
121
- } & class_variance_authority_types.ClassProp) | undefined) => string;
122
- declare function Button({ className, variant, size, asChild, ref, ...props }: React__default.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
117
+ declare const buttonVariantStyles: {
118
+ readonly default: "bg-primary text-primary-foreground hover:bg-primary/90";
119
+ readonly destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40";
120
+ readonly outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50";
121
+ readonly secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80";
122
+ readonly ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50";
123
+ readonly link: "text-primary underline-offset-4 hover:underline";
124
+ };
125
+ declare const buttonSizeStyles: {
126
+ readonly default: "h-9 px-4 py-2 has-[>svg]:px-3";
127
+ readonly xs: "h-6 gap-1 rounded px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3";
128
+ readonly sm: "h-8 gap-1.5 rounded px-3 has-[>svg]:px-2.5";
129
+ readonly lg: "h-10 rounded px-6 has-[>svg]:px-4";
130
+ readonly icon: "size-9";
131
+ readonly "icon-xs": "size-6 rounded [&_svg:not([class*='size-'])]:size-3";
132
+ readonly "icon-sm": "size-8";
133
+ readonly "icon-lg": "size-10";
134
+ };
135
+ type ButtonVariant = keyof typeof buttonVariantStyles;
136
+ type ButtonSize = keyof typeof buttonSizeStyles;
137
+ declare function buttonVariants(opts?: {
138
+ variant?: ButtonVariant;
139
+ size?: ButtonSize;
140
+ className?: string;
141
+ }): string;
142
+ declare function Button({ className, variant, size, asChild, ref, ...props }: React__default.ComponentProps<"button"> & {
143
+ variant?: ButtonVariant;
144
+ size?: ButtonSize;
123
145
  asChild?: boolean;
124
146
  }): react_jsx_runtime.JSX.Element;
125
147
 
@@ -136,10 +158,21 @@ declare function SelectLabel({ className, ...props }: React__default.ComponentPr
136
158
  declare function SelectItem({ className, children, ...props }: React__default.ComponentProps<typeof Select$1.Item>): react_jsx_runtime.JSX.Element;
137
159
  declare function SelectSeparator({ className, ...props }: React__default.ComponentProps<typeof Select$1.Separator>): react_jsx_runtime.JSX.Element;
138
160
 
139
- declare const badgeVariants: (props?: ({
140
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
141
- } & class_variance_authority_types.ClassProp) | undefined) => string;
142
- declare function Badge({ className, variant, asChild, ...props }: React__default.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
161
+ declare const badgeVariantStyles: {
162
+ readonly default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90";
163
+ readonly secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90";
164
+ readonly destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90";
165
+ readonly outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground";
166
+ readonly ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground";
167
+ readonly link: "text-primary underline-offset-4 [a&]:hover:underline";
168
+ };
169
+ type BadgeVariant = keyof typeof badgeVariantStyles;
170
+ declare function badgeVariants(opts?: {
171
+ variant?: BadgeVariant;
172
+ className?: string;
173
+ }): string;
174
+ declare function Badge({ className, variant, asChild, ...props }: React__default.ComponentProps<"span"> & {
175
+ variant?: BadgeVariant;
143
176
  asChild?: boolean;
144
177
  }): react_jsx_runtime.JSX.Element;
145
178
 
@@ -187,7 +220,7 @@ declare function TabsContent({ className, ...props }: React__default.ComponentPr
187
220
 
188
221
  declare function ValueInput({ value, onChange, placeholder }: ValueInputProps): react_jsx_runtime.JSX.Element;
189
222
 
190
- declare function DateTimeInput({ value, onChange }: DateTimeInputProps): react_jsx_runtime.JSX.Element;
223
+ declare function DateTimeInput({ value, onChange, disabled }: DateTimeInputProps): react_jsx_runtime.JSX.Element;
191
224
 
192
225
  declare function ValidationMessage({ errors }: ValidationMessageProps): react_jsx_runtime.JSX.Element | null;
193
226
 
package/dist/index.js CHANGED
@@ -275,27 +275,20 @@ function OperatorSelect({ value, onChange, options, disabled }) {
275
275
  import { useCallback, useState } from "react";
276
276
 
277
277
  // src/components/ui/badge.tsx
278
- import { cva } from "class-variance-authority";
279
278
  import { Slot } from "radix-ui";
280
279
  import { jsx as jsx6 } from "react/jsx-runtime";
281
- var badgeVariants = cva(
282
- "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
283
- {
284
- variants: {
285
- variant: {
286
- default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
287
- secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
288
- destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
289
- outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
290
- ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
291
- link: "text-primary underline-offset-4 [a&]:hover:underline"
292
- }
293
- },
294
- defaultVariants: {
295
- variant: "default"
296
- }
297
- }
298
- );
280
+ var badgeBase = "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3";
281
+ var badgeVariantStyles = {
282
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
283
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
284
+ destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
285
+ outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
286
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
287
+ link: "text-primary underline-offset-4 [a&]:hover:underline"
288
+ };
289
+ function badgeVariants(opts) {
290
+ return cn(badgeBase, badgeVariantStyles[opts?.variant ?? "default"], opts?.className);
291
+ }
299
292
  function Badge({
300
293
  className,
301
294
  variant = "default",
@@ -316,7 +309,7 @@ function Badge({
316
309
 
317
310
  // src/components/condition-builder/TagInput.tsx
318
311
  import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
319
- function TagInput({ value, onChange, placeholder }) {
312
+ function TagInput({ value, onChange, placeholder, disabled }) {
320
313
  const values = Array.isArray(value) ? value.filter(Boolean) : value ? [value] : [];
321
314
  const [text, setText] = useState("");
322
315
  const emit = useCallback(
@@ -365,32 +358,39 @@ function TagInput({ value, onChange, placeholder }) {
365
358
  },
366
359
  [addValues]
367
360
  );
368
- return /* @__PURE__ */ jsxs3("div", { className: "border-input focus-within:border-ring focus-within:ring-ring/50 flex min-h-9 flex-1 flex-wrap items-center gap-1 rounded-md border px-2 py-1 focus-within:ring-[3px]", children: [
369
- values.map((v, i) => /* @__PURE__ */ jsxs3(Badge, { variant: "outline", className: "bg-muted gap-1 font-mono text-xs", children: [
370
- v,
371
- /* @__PURE__ */ jsx7(
372
- "button",
373
- {
374
- type: "button",
375
- className: "text-muted-foreground hover:text-foreground ml-0.5 cursor-pointer leading-none",
376
- onClick: () => removeValue(i),
377
- "aria-label": `Remove ${v}`,
378
- children: "\xD7"
379
- }
380
- )
381
- ] }, `${v}-${i}`)),
382
- /* @__PURE__ */ jsx7(
383
- "input",
384
- {
385
- className: "min-w-[80px] flex-1 bg-transparent py-0.5 font-mono text-sm outline-none placeholder:text-muted-foreground",
386
- value: text,
387
- placeholder: values.length === 0 ? placeholder ?? "type and press Enter" : "",
388
- onChange: (e) => setText(e.target.value),
389
- onKeyDown: handleKeyDown,
390
- onPaste: handlePaste
391
- }
392
- )
393
- ] });
361
+ return /* @__PURE__ */ jsxs3(
362
+ "div",
363
+ {
364
+ className: `border-input focus-within:border-ring focus-within:ring-ring/50 flex min-h-9 flex-1 flex-wrap items-center gap-1 rounded-md border px-2 py-1 focus-within:ring-[3px]${disabled ? " opacity-50" : ""}`,
365
+ children: [
366
+ values.map((v, i) => /* @__PURE__ */ jsxs3(Badge, { variant: "outline", className: "bg-muted gap-1 font-mono text-xs", children: [
367
+ v,
368
+ !disabled && /* @__PURE__ */ jsx7(
369
+ "button",
370
+ {
371
+ type: "button",
372
+ className: "text-muted-foreground hover:text-foreground ml-0.5 cursor-pointer leading-none",
373
+ onClick: () => removeValue(i),
374
+ "aria-label": `Remove ${v}`,
375
+ children: "\xD7"
376
+ }
377
+ )
378
+ ] }, `${v}-${i}`)),
379
+ /* @__PURE__ */ jsx7(
380
+ "input",
381
+ {
382
+ className: "min-w-[80px] flex-1 bg-transparent py-0.5 font-mono text-sm outline-none placeholder:text-muted-foreground",
383
+ value: text,
384
+ placeholder: values.length === 0 ? placeholder ?? "type and press Enter" : "",
385
+ onChange: (e) => setText(e.target.value),
386
+ onKeyDown: handleKeyDown,
387
+ onPaste: handlePaste,
388
+ disabled
389
+ }
390
+ )
391
+ ]
392
+ }
393
+ );
394
394
  }
395
395
 
396
396
  // src/components/condition-builder/condition-builders.ts
@@ -489,7 +489,7 @@ import { useCallback as useCallback4, useMemo as useMemo2 } from "react";
489
489
  // src/components/condition-builder/NumberTagInput.tsx
490
490
  import { useCallback as useCallback3, useState as useState2 } from "react";
491
491
  import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
492
- function NumberTagInput({ value, onChange, placeholder }) {
492
+ function NumberTagInput({ value, onChange, placeholder, disabled }) {
493
493
  const values = Array.isArray(value) ? value : [value];
494
494
  const [text, setText] = useState2("");
495
495
  const emit = useCallback3(
@@ -538,33 +538,40 @@ function NumberTagInput({ value, onChange, placeholder }) {
538
538
  },
539
539
  [addValues]
540
540
  );
541
- return /* @__PURE__ */ jsxs5("div", { className: "border-input focus-within:border-ring focus-within:ring-ring/50 flex min-h-9 flex-1 flex-wrap items-center gap-1 rounded-md border px-2 py-1 focus-within:ring-[3px]", children: [
542
- values.map((v, i) => /* @__PURE__ */ jsxs5(Badge, { variant: "outline", className: "bg-muted gap-1 font-mono text-xs", children: [
543
- v,
544
- /* @__PURE__ */ jsx9(
545
- "button",
546
- {
547
- type: "button",
548
- className: "text-muted-foreground hover:text-foreground ml-0.5 cursor-pointer leading-none",
549
- onClick: () => removeValue(i),
550
- "aria-label": `Remove ${v}`,
551
- children: "\xD7"
552
- }
553
- )
554
- ] }, `${v}-${i}`)),
555
- /* @__PURE__ */ jsx9(
556
- "input",
557
- {
558
- className: "min-w-[80px] flex-1 bg-transparent py-0.5 font-mono text-sm outline-none placeholder:text-muted-foreground",
559
- type: "number",
560
- value: text,
561
- placeholder: values.length === 0 ? placeholder ?? "type and press Enter" : "",
562
- onChange: (e) => setText(e.target.value),
563
- onKeyDown: handleKeyDown,
564
- onPaste: handlePaste
565
- }
566
- )
567
- ] });
541
+ return /* @__PURE__ */ jsxs5(
542
+ "div",
543
+ {
544
+ className: `border-input focus-within:border-ring focus-within:ring-ring/50 flex min-h-9 flex-1 flex-wrap items-center gap-1 rounded-md border px-2 py-1 focus-within:ring-[3px]${disabled ? " opacity-50" : ""}`,
545
+ children: [
546
+ values.map((v, i) => /* @__PURE__ */ jsxs5(Badge, { variant: "outline", className: "bg-muted gap-1 font-mono text-xs", children: [
547
+ v,
548
+ !disabled && /* @__PURE__ */ jsx9(
549
+ "button",
550
+ {
551
+ type: "button",
552
+ className: "text-muted-foreground hover:text-foreground ml-0.5 cursor-pointer leading-none",
553
+ onClick: () => removeValue(i),
554
+ "aria-label": `Remove ${v}`,
555
+ children: "\xD7"
556
+ }
557
+ )
558
+ ] }, `${v}-${i}`)),
559
+ /* @__PURE__ */ jsx9(
560
+ "input",
561
+ {
562
+ className: "min-w-[80px] flex-1 bg-transparent py-0.5 font-mono text-sm outline-none placeholder:text-muted-foreground",
563
+ type: "number",
564
+ value: text,
565
+ placeholder: values.length === 0 ? placeholder ?? "type and press Enter" : "",
566
+ onChange: (e) => setText(e.target.value),
567
+ onKeyDown: handleKeyDown,
568
+ onPaste: handlePaste,
569
+ disabled
570
+ }
571
+ )
572
+ ]
573
+ }
574
+ );
568
575
  }
569
576
 
570
577
  // src/components/condition-builder/NumberConditionEditor.tsx
@@ -663,7 +670,7 @@ function fromLocalDatetime(local) {
663
670
  if (Number.isNaN(d.getTime())) return local;
664
671
  return d.toISOString();
665
672
  }
666
- function DateTimeInput({ value, onChange }) {
673
+ function DateTimeInput({ value, onChange, disabled }) {
667
674
  const [rawValue, setRawValue] = useState3(value);
668
675
  const [showRaw, setShowRaw] = useState3(false);
669
676
  const prevValueRef = useRef(value);
@@ -682,7 +689,8 @@ function DateTimeInput({ value, onChange }) {
682
689
  onChange: (e) => {
683
690
  setRawValue(e.target.value);
684
691
  onChange(e.target.value);
685
- }
692
+ },
693
+ disabled
686
694
  }
687
695
  ),
688
696
  /* @__PURE__ */ jsx11(
@@ -704,7 +712,8 @@ function DateTimeInput({ value, onChange }) {
704
712
  className: "h-8 flex-1 text-xs",
705
713
  type: "datetime-local",
706
714
  value: toLocalDatetime(value),
707
- onChange: (e) => onChange(fromLocalDatetime(e.target.value))
715
+ onChange: (e) => onChange(fromLocalDatetime(e.target.value)),
716
+ disabled
708
717
  }
709
718
  ),
710
719
  /* @__PURE__ */ jsx11(
@@ -910,38 +919,35 @@ function getConditionMeta(type) {
910
919
  }
911
920
 
912
921
  // src/components/ui/button.tsx
913
- import { cva as cva2 } from "class-variance-authority";
914
922
  import { Slot as Slot2 } from "radix-ui";
915
923
  import { jsx as jsx17 } from "react/jsx-runtime";
916
- var buttonVariants = cva2(
917
- "inline-flex shrink-0 items-center justify-center gap-2 rounded text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 hover:cursor-pointer",
918
- {
919
- variants: {
920
- variant: {
921
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
922
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
923
- outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
924
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
925
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
926
- link: "text-primary underline-offset-4 hover:underline"
927
- },
928
- size: {
929
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
930
- xs: "h-6 gap-1 rounded px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
931
- sm: "h-8 gap-1.5 rounded px-3 has-[>svg]:px-2.5",
932
- lg: "h-10 rounded px-6 has-[>svg]:px-4",
933
- icon: "size-9",
934
- "icon-xs": "size-6 rounded [&_svg:not([class*='size-'])]:size-3",
935
- "icon-sm": "size-8",
936
- "icon-lg": "size-10"
937
- }
938
- },
939
- defaultVariants: {
940
- variant: "default",
941
- size: "default"
942
- }
943
- }
944
- );
924
+ var buttonBase = "inline-flex shrink-0 items-center justify-center gap-2 rounded text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 hover:cursor-pointer";
925
+ var buttonVariantStyles = {
926
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
927
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
928
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
929
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
930
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
931
+ link: "text-primary underline-offset-4 hover:underline"
932
+ };
933
+ var buttonSizeStyles = {
934
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
935
+ xs: "h-6 gap-1 rounded px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
936
+ sm: "h-8 gap-1.5 rounded px-3 has-[>svg]:px-2.5",
937
+ lg: "h-10 rounded px-6 has-[>svg]:px-4",
938
+ icon: "size-9",
939
+ "icon-xs": "size-6 rounded [&_svg:not([class*='size-'])]:size-3",
940
+ "icon-sm": "size-8",
941
+ "icon-lg": "size-10"
942
+ };
943
+ function buttonVariants(opts) {
944
+ return cn(
945
+ buttonBase,
946
+ buttonVariantStyles[opts?.variant ?? "default"],
947
+ buttonSizeStyles[opts?.size ?? "default"],
948
+ opts?.className
949
+ );
950
+ }
945
951
  function Button({
946
952
  className,
947
953
  variant = "default",
@@ -2978,17 +2984,24 @@ function createPresetConditionMeta(presets) {
2978
2984
  type: name,
2979
2985
  label: capitalize(name),
2980
2986
  description,
2981
- defaults: { ...baseDefaults, ...preset.defaults, type: name }
2987
+ defaults: { ...baseDefaults, ...preset.overrides, type: name }
2982
2988
  };
2983
2989
  });
2984
2990
  }
2985
- function createPresetEditor(presetName, builtinType, presetKey) {
2991
+ function createPresetEditor(presetName, builtinType, presetKey, overrides = {}) {
2992
+ const lockedFields = new Set(Object.keys(overrides));
2986
2993
  function PresetConditionEditor({ condition, onChange }) {
2987
2994
  const rec = useMemo10(() => condition, [condition]);
2988
2995
  const update = useCallback11(
2989
2996
  (field, value) => {
2990
2997
  onChange(
2991
- buildCustomCondition({ ...rec, [field]: value, key: presetKey, type: presetName })
2998
+ buildCustomCondition({
2999
+ ...rec,
3000
+ [field]: value,
3001
+ ...overrides,
3002
+ key: presetKey,
3003
+ type: presetName
3004
+ })
2992
3005
  );
2993
3006
  },
2994
3007
  [rec, onChange]
@@ -3012,6 +3025,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3012
3025
  })
3013
3026
  );
3014
3027
  };
3028
+ const opLocked = lockedFields.has("op");
3029
+ const valueLocked = lockedFields.has("value");
3015
3030
  return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
3016
3031
  /* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
3017
3032
  /* @__PURE__ */ jsx45(
@@ -3019,7 +3034,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3019
3034
  {
3020
3035
  value: String(rec.op ?? "eq"),
3021
3036
  onChange: handleOpChange,
3022
- options: OP_OPTIONS
3037
+ options: OP_OPTIONS,
3038
+ disabled: opLocked
3023
3039
  }
3024
3040
  ),
3025
3041
  isArray ? /* @__PURE__ */ jsx45(
@@ -3027,7 +3043,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3027
3043
  {
3028
3044
  value: rec.value ?? "",
3029
3045
  onChange: (v) => update("value", v),
3030
- placeholder: `e.g. ${presetKey} value`
3046
+ placeholder: `e.g. ${presetKey} value`,
3047
+ disabled: valueLocked
3031
3048
  }
3032
3049
  ) : isRegex ? /* @__PURE__ */ jsx45(
3033
3050
  Input,
@@ -3035,7 +3052,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3035
3052
  className: "h-8 font-mono text-sm",
3036
3053
  value: String(rec.value ?? ""),
3037
3054
  placeholder: "e.g. ^test.*$",
3038
- onChange: (e) => update("value", e.target.value)
3055
+ onChange: (e) => update("value", e.target.value),
3056
+ disabled: valueLocked
3039
3057
  }
3040
3058
  ) : /* @__PURE__ */ jsx45(
3041
3059
  Input,
@@ -3043,7 +3061,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3043
3061
  className: "h-8 text-sm",
3044
3062
  value: String(rec.value ?? ""),
3045
3063
  placeholder: `e.g. ${presetKey} value`,
3046
- onChange: (e) => update("value", e.target.value)
3064
+ onChange: (e) => update("value", e.target.value),
3065
+ disabled: valueLocked
3047
3066
  }
3048
3067
  )
3049
3068
  ] });
@@ -3065,6 +3084,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3065
3084
  })
3066
3085
  );
3067
3086
  };
3087
+ const numOpLocked = lockedFields.has("op");
3088
+ const numValueLocked = lockedFields.has("value");
3068
3089
  return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
3069
3090
  /* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
3070
3091
  /* @__PURE__ */ jsx45(
@@ -3072,7 +3093,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3072
3093
  {
3073
3094
  value: String(rec.op ?? "eq"),
3074
3095
  onChange: handleNumOpChange,
3075
- options: OP_OPTIONS2
3096
+ options: OP_OPTIONS2,
3097
+ disabled: numOpLocked
3076
3098
  }
3077
3099
  ),
3078
3100
  isNumArray ? /* @__PURE__ */ jsx45(
@@ -3080,7 +3102,8 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3080
3102
  {
3081
3103
  value: rec.value ?? [],
3082
3104
  onChange: (v) => update("value", v),
3083
- placeholder: `e.g. ${presetKey} value`
3105
+ placeholder: `e.g. ${presetKey} value`,
3106
+ disabled: numValueLocked
3084
3107
  }
3085
3108
  ) : /* @__PURE__ */ jsx45(
3086
3109
  Input,
@@ -3089,12 +3112,14 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3089
3112
  className: "h-8 font-mono text-sm",
3090
3113
  value: rec.value !== void 0 ? String(rec.value) : "",
3091
3114
  placeholder: "e.g. 100",
3092
- onChange: (e) => update("value", e.target.value === "" ? "" : Number(e.target.value))
3115
+ onChange: (e) => update("value", e.target.value === "" ? "" : Number(e.target.value)),
3116
+ disabled: numValueLocked
3093
3117
  }
3094
3118
  )
3095
3119
  ] });
3096
3120
  }
3097
- case "bool":
3121
+ case "bool": {
3122
+ const boolValueLocked = lockedFields.has("value");
3098
3123
  return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
3099
3124
  /* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
3100
3125
  /* @__PURE__ */ jsx45(OperatorSelect, { value: "eq", options: OP_OPTIONS4, disabled: true }),
@@ -3103,6 +3128,7 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3103
3128
  {
3104
3129
  value: String(rec.value ?? "true"),
3105
3130
  onValueChange: (v) => update("value", v === "true"),
3131
+ disabled: boolValueLocked,
3106
3132
  children: [
3107
3133
  /* @__PURE__ */ jsx45(SelectTrigger, { className: "h-8 text-sm", children: /* @__PURE__ */ jsx45(SelectValue, {}) }),
3108
3134
  /* @__PURE__ */ jsxs31(SelectContent, { children: [
@@ -3113,7 +3139,10 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3113
3139
  }
3114
3140
  )
3115
3141
  ] });
3116
- case "datetime":
3142
+ }
3143
+ case "datetime": {
3144
+ const dtOpLocked = lockedFields.has("op");
3145
+ const dtValueLocked = lockedFields.has("value");
3117
3146
  return /* @__PURE__ */ jsxs31(ConditionRow, { children: [
3118
3147
  /* @__PURE__ */ jsx45(KeyInput, { value: presetKey, disabled: true }),
3119
3148
  /* @__PURE__ */ jsx45(
@@ -3121,11 +3150,20 @@ function createPresetEditor(presetName, builtinType, presetKey) {
3121
3150
  {
3122
3151
  value: String(rec.op ?? "eq"),
3123
3152
  onChange: (v) => update("op", v),
3124
- options: OP_OPTIONS3
3153
+ options: OP_OPTIONS3,
3154
+ disabled: dtOpLocked
3125
3155
  }
3126
3156
  ),
3127
- /* @__PURE__ */ jsx45(DateTimeInput, { value: String(rec.value ?? ""), onChange: (v) => update("value", v) })
3157
+ /* @__PURE__ */ jsx45(
3158
+ DateTimeInput,
3159
+ {
3160
+ value: String(rec.value ?? ""),
3161
+ onChange: (v) => update("value", v),
3162
+ disabled: dtValueLocked
3163
+ }
3164
+ )
3128
3165
  ] });
3166
+ }
3129
3167
  }
3130
3168
  return null;
3131
3169
  }
@@ -3137,7 +3175,10 @@ function createPresetUI(presets) {
3137
3175
  const editorOverrides = /* @__PURE__ */ new Map();
3138
3176
  for (const [name, preset] of Object.entries(presets)) {
3139
3177
  if (PRIMITIVE_TYPES.has(preset.type) && preset.key) {
3140
- editorOverrides.set(name, createPresetEditor(name, preset.type, preset.key));
3178
+ editorOverrides.set(
3179
+ name,
3180
+ createPresetEditor(name, preset.type, preset.key, preset.overrides)
3181
+ );
3141
3182
  }
3142
3183
  }
3143
3184
  return { extraConditionTypes, editorOverrides };
@@ -3202,7 +3243,7 @@ function useConfiguratorSelector(selector) {
3202
3243
  // src/configurator/PreviewPanel.tsx
3203
3244
  import { useCallback as useCallback14, useEffect, useMemo as useMemo11, useRef as useRef5, useState as useState11 } from "react";
3204
3245
  import { ChevronRight as ChevronRight2, Eye as Eye2, Loader2, Maximize2, Play } from "lucide-react";
3205
- import { resolve } from "showwhat";
3246
+ import { resolve, builtinEvaluators } from "showwhat";
3206
3247
  import { DefinitionInactiveError, DefinitionNotFoundError, VariationNotFoundError } from "showwhat";
3207
3248
 
3208
3249
  // src/configurator/selectors.ts
@@ -3407,38 +3448,49 @@ function PreviewPanel() {
3407
3448
  const result = await resolve({
3408
3449
  definitions: { [selectedKey]: definitions[selectedKey] },
3409
3450
  context,
3410
- options: fallback ? { fallback } : void 0
3451
+ options: {
3452
+ evaluators: builtinEvaluators,
3453
+ ...fallback ? { fallback } : void 0
3454
+ }
3411
3455
  });
3412
3456
  if (controller.signal.aborted) return;
3413
3457
  const resolution = result[selectedKey];
3414
- setPreviewResult({
3415
- status: "success",
3416
- value: resolution.value,
3417
- meta: resolution.meta
3418
- });
3419
- } catch (err) {
3420
- if (controller.signal.aborted) return;
3421
- if (err instanceof DefinitionInactiveError) {
3458
+ if (resolution.success) {
3422
3459
  setPreviewResult({
3423
- status: "inactive",
3424
- message: `"${selectedKey}" is inactive`
3425
- });
3426
- } else if (err instanceof VariationNotFoundError) {
3427
- setPreviewResult({
3428
- status: "no-match",
3429
- message: "No variation matched the given context"
3430
- });
3431
- } else if (err instanceof DefinitionNotFoundError) {
3432
- setPreviewResult({
3433
- status: "error",
3434
- message: `Definition "${selectedKey}" not found`
3460
+ status: "success",
3461
+ value: resolution.value,
3462
+ meta: resolution.meta
3435
3463
  });
3436
3464
  } else {
3437
- setPreviewResult({
3438
- status: "error",
3439
- message: err instanceof Error ? err.message : "Unknown error"
3440
- });
3465
+ const err = resolution.error;
3466
+ if (err instanceof DefinitionInactiveError) {
3467
+ setPreviewResult({
3468
+ status: "inactive",
3469
+ message: `"${selectedKey}" is inactive`
3470
+ });
3471
+ } else if (err instanceof VariationNotFoundError) {
3472
+ setPreviewResult({
3473
+ status: "no-match",
3474
+ message: "No variation matched the given context"
3475
+ });
3476
+ } else if (err instanceof DefinitionNotFoundError) {
3477
+ setPreviewResult({
3478
+ status: "error",
3479
+ message: `Definition "${selectedKey}" not found`
3480
+ });
3481
+ } else {
3482
+ setPreviewResult({
3483
+ status: "error",
3484
+ message: err.message
3485
+ });
3486
+ }
3441
3487
  }
3488
+ } catch (err) {
3489
+ if (controller.signal.aborted) return;
3490
+ setPreviewResult({
3491
+ status: "error",
3492
+ message: err instanceof Error ? err.message : "Unknown error"
3493
+ });
3442
3494
  } finally {
3443
3495
  if (!controller.signal.aborted) {
3444
3496
  setIsResolving(false);