@widgetic/editor 4.0.7 → 4.0.9

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 (57) hide show
  1. package/README.md +12 -1
  2. package/dist/components/WidgetPreview.svelte +32 -1
  3. package/dist/components/WidgetPreview.svelte.d.ts +5 -0
  4. package/dist/components/editors/PropsEditor.svelte +171 -87
  5. package/dist/components/editors/PropsEditor.svelte.d.ts +3 -2
  6. package/dist/components/ic/AngleInputController.svelte +21 -7
  7. package/dist/components/ic/AngleInputController.svelte.d.ts +16 -6
  8. package/dist/components/ic/BrowserInputController.svelte +28 -15
  9. package/dist/components/ic/BrowserInputController.svelte.d.ts +25 -19
  10. package/dist/components/ic/CheckboxInputController.svelte +21 -7
  11. package/dist/components/ic/CheckboxInputController.svelte.d.ts +25 -15
  12. package/dist/components/ic/ColorPickerInputController.svelte +47 -19
  13. package/dist/components/ic/ColorPickerInputController.svelte.d.ts +42 -17
  14. package/dist/components/ic/DateFieldInputController.svelte +21 -7
  15. package/dist/components/ic/DateFieldInputController.svelte.d.ts +24 -14
  16. package/dist/components/ic/DatePickerInputController.svelte +25 -8
  17. package/dist/components/ic/DatePickerInputController.svelte.d.ts +21 -8
  18. package/dist/components/ic/DropdownInputController.svelte +39 -14
  19. package/dist/components/ic/DropdownInputController.svelte.d.ts +33 -17
  20. package/dist/components/ic/FixedPositionInputController.svelte +24 -10
  21. package/dist/components/ic/FixedPositionInputController.svelte.d.ts +16 -4
  22. package/dist/components/ic/FontInputController.svelte +70 -41
  23. package/dist/components/ic/FontInputController.svelte.d.ts +33 -18
  24. package/dist/components/ic/NumberInputController.svelte +23 -9
  25. package/dist/components/ic/NumberInputController.svelte.d.ts +18 -7
  26. package/dist/components/ic/PositionInputController.svelte +17 -5
  27. package/dist/components/ic/PositionInputController.svelte.d.ts +20 -11
  28. package/dist/components/ic/RadioGroupInputController.svelte +21 -7
  29. package/dist/components/ic/RadioGroupInputController.svelte.d.ts +22 -12
  30. package/dist/components/ic/RangeCalendarInputController.svelte +34 -16
  31. package/dist/components/ic/RangeCalendarInputController.svelte.d.ts +25 -11
  32. package/dist/components/ic/RoundedCornersInputController.svelte +24 -10
  33. package/dist/components/ic/RoundedCornersInputController.svelte.d.ts +26 -16
  34. package/dist/components/ic/SliderInputController.svelte +41 -18
  35. package/dist/components/ic/SliderInputController.svelte.d.ts +35 -19
  36. package/dist/components/ic/SliderRangeInputController.svelte +27 -11
  37. package/dist/components/ic/SliderRangeInputController.svelte.d.ts +22 -9
  38. package/dist/components/ic/SwitchInputController.svelte +17 -5
  39. package/dist/components/ic/SwitchInputController.svelte.d.ts +14 -5
  40. package/dist/components/ic/TagsInputController.svelte +21 -7
  41. package/dist/components/ic/TagsInputController.svelte.d.ts +16 -6
  42. package/dist/components/ic/TextAreaInputController.svelte +18 -12
  43. package/dist/components/ic/TextAreaInputController.svelte.d.ts +6 -4
  44. package/dist/components/ic/TextAreaPreviewInputController.svelte +21 -7
  45. package/dist/components/ic/TextAreaPreviewInputController.svelte.d.ts +16 -6
  46. package/dist/components/ic/TextInputController.svelte +23 -8
  47. package/dist/components/ic/TextInputController.svelte.d.ts +20 -8
  48. package/dist/components/ic/TimeInputController.svelte +17 -5
  49. package/dist/components/ic/TimeInputController.svelte.d.ts +14 -5
  50. package/dist/components/ic/VectorInputController.svelte +29 -10
  51. package/dist/components/ic/VectorInputController.svelte.d.ts +33 -18
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +1 -0
  54. package/dist/schemaViews.d.ts +105 -0
  55. package/dist/schemaViews.js +11 -0
  56. package/dist/utils/FontManager.js +1 -1
  57. package/package.json +9 -6
package/README.md CHANGED
@@ -9,7 +9,7 @@ Based on the Widget's design schema and content schema it generates 2 panels of
9
9
 
10
10
  1. add the component as a dependency in the npm package of the project
11
11
  2. import the component in the project:
12
- `import PropsEditor from '@widgetic/props-editor';`
12
+ `import PropsEditor from '@widgetic/editor';`
13
13
  3. load the design and content schemas and pass them as props to the component:
14
14
  ```js
15
15
  let designSchemaUrl = '/design-debug-schema.json';
@@ -71,6 +71,17 @@ npm i --legacy-peer-deps
71
71
  Note: use --legacy-peer-deps if you get peer dependency errors
72
72
  as our project uses svelte 5 and some of the dependencies are still using svelte 4.
73
73
 
74
+ ### Sibling packages
75
+
76
+ This app compiles sibling `@widgetic/*` source in dev through the dev-alias
77
+ setup `@widgetic/dev-tools` owns. The alias tables, the `$lib` remap, and
78
+ everything else the mapping needs come from the installed
79
+ `@widgetic/dev-tools` dependency. An ordinary `npm install` is the whole
80
+ setup — there is no linking step.
81
+
82
+ The why and the silent-degrade failure mode live in
83
+ [the dev-tools README](../dev-tools/README.md).
84
+
74
85
  ## Start the development server
75
86
 
76
87
  ```bash
@@ -10,6 +10,7 @@
10
10
  resize: { width: number; height: number };
11
11
  mobileViewport: { width: number; height: number };
12
12
  desktopViewport: void;
13
+ deviceView: { mobile: boolean };
13
14
  fitToArea: void;
14
15
  iframeLoad: Event;
15
16
  iframeError: Event;
@@ -64,6 +65,9 @@
64
65
  export let enableDrag: boolean = false;
65
66
  /** W×H number inputs in the chrome header, live-synced with resize. */
66
67
  export let showDimensionInputs: boolean = false;
68
+ /** Desktop / mobile pair in the chrome header, before the dimension inputs. */
69
+ export let showChromeDeviceToggle: boolean = false;
70
+ export let deviceMobile: boolean = false;
67
71
  /**
68
72
  * concentric — always resize from center (widget-creator / Canvas).
69
73
  * corner — opposite corner fixed; hold Alt for concentric (composition page).
@@ -886,8 +890,35 @@
886
890
  <span class="preview-chrome-drag-grip shrink-0 text-gray-400 text-xs leading-none" title="Drag preview" aria-hidden="true">⠿</span>
887
891
  {/if}
888
892
  <span class="preview-chrome-title text-[11px] font-semibold uppercase tracking-wide text-gray-500 shrink-0">Preview</span>
893
+ {#if showChromeDeviceToggle}
894
+ <div
895
+ class="preview-chrome-device-toggle ml-auto flex shrink-0 items-center overflow-hidden rounded border border-gray-300"
896
+ onpointerdown={(event) => event.stopPropagation()}
897
+ >
898
+ <button
899
+ type="button"
900
+ class="preview-chrome-device-desktop flex h-6 w-6 items-center justify-center {!deviceMobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
901
+ aria-label="Desktop preview"
902
+ aria-pressed={!deviceMobile}
903
+ onclick={() => dispatch('deviceView', { mobile: false })}
904
+ onpointerdown={(event) => event.stopPropagation()}
905
+ >
906
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
907
+ </button>
908
+ <button
909
+ type="button"
910
+ class="preview-chrome-device-mobile flex h-6 w-6 items-center justify-center border-l border-gray-300 {deviceMobile ? 'bg-gray-100 text-gray-800' : 'bg-white text-gray-400'} hover:text-gray-800 focus:outline-none focus-visible:outline-none"
911
+ aria-label="Mobile preview"
912
+ aria-pressed={deviceMobile}
913
+ onclick={() => dispatch('deviceView', { mobile: true })}
914
+ onpointerdown={(event) => event.stopPropagation()}
915
+ >
916
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
917
+ </button>
918
+ </div>
919
+ {/if}
889
920
  {#if showDimensionInputs}
890
- <div class="preview-chrome-dimensions ml-auto flex items-center gap-1.5">
921
+ <div class="preview-chrome-dimensions flex items-center gap-1.5 {showChromeDeviceToggle ? '' : 'ml-auto'}">
891
922
  <label class="preview-dim-label flex items-center gap-1 text-[10px] font-semibold text-gray-500">
892
923
  <span>W</span>
893
924
  <input
@@ -45,6 +45,8 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
45
45
  enableResize?: boolean;
46
46
  /** Drag only via the preview chrome header (keeps iframe interactive). */ enableDrag?: boolean;
47
47
  /** W×H number inputs in the chrome header, live-synced with resize. */ showDimensionInputs?: boolean;
48
+ /** Desktop / mobile pair in the chrome header, before the dimension inputs. */ showChromeDeviceToggle?: boolean;
49
+ deviceMobile?: boolean;
48
50
  /**
49
51
  * concentric — always resize from center (widget-creator / Canvas).
50
52
  * corner — opposite corner fixed; hold Alt for concentric (composition page).
@@ -86,6 +88,9 @@ declare const WidgetPreview: $$__sveltets_2_IsomorphicComponent<{
86
88
  height: number;
87
89
  }>;
88
90
  desktopViewport: CustomEvent<void>;
91
+ deviceView: CustomEvent<{
92
+ mobile: boolean;
93
+ }>;
89
94
  fitToArea: CustomEvent<void>;
90
95
  iframeLoad: CustomEvent<Event>;
91
96
  iframeError: CustomEvent<Event>;
@@ -15,7 +15,16 @@
15
15
  // Svelte imports
16
16
  import { onMount, onDestroy, createEventDispatcher, afterUpdate } from 'svelte';
17
17
  const dispatch = createEventDispatcher();
18
- import { writable, get } from 'svelte/store';
18
+ import { writable, get, type Writable } from 'svelte/store';
19
+ import type {
20
+ ContentSchemaItemView,
21
+ ContentSchemaPropertyView,
22
+ ContentSchemaView,
23
+ DesignSchemaComponentView,
24
+ DesignSchemaPropertyGroupView,
25
+ DesignSchemaPropertyView,
26
+ DesignSchemaView,
27
+ } from '../../schemaViews.js';
19
28
 
20
29
  /**
21
30
  * COMPONENTS IMPORTS
@@ -61,6 +70,7 @@
61
70
  TagsInputController,
62
71
  DateFieldInputController
63
72
  } from '../ic';
73
+ import type { InputControllerConfig } from '../ic';
64
74
 
65
75
  /**
66
76
  * UTILS IMPORTS
@@ -84,15 +94,6 @@
84
94
  */
85
95
  type InputValue = string | number | boolean | any | null;
86
96
 
87
- interface PropertyGroup {
88
- name: string;
89
- properties: Array<Property>;
90
- visibilityCondition?: {
91
- dependsOn: string | string[];
92
- condition: string;
93
- };
94
- }
95
-
96
97
  // Add this interface near the other interfaces at the top of the file
97
98
  interface ContentProperty {
98
99
  id: string;
@@ -117,9 +118,10 @@
117
118
  // Dimensions
118
119
  export let width: string = "450px";
119
120
  export let height: string = "720px";
120
- // Schemas
121
- export let designSchema: any = null;
122
- export let contentSchema: any | undefined = undefined;
121
+ // Schemas — typed against the package's canonical schema views
122
+ // (`$lib/schemaViews`), the same shapes the consumers normalize to.
123
+ export let designSchema: DesignSchemaView | null = null;
124
+ export let contentSchema: ContentSchemaView | undefined = undefined;
123
125
  export let itemTemplate: any = null; // Store template for new items
124
126
  // Tab titles
125
127
  export let designTabTitle: string = 'Design';
@@ -214,7 +216,7 @@
214
216
  const DRAG_SHIFT_X = 24; // shift mirror left so cursor is over drag handle
215
217
 
216
218
  // Helper function to get visible groups
217
- function getVisibleGroups(components: Array<{ propertyGroups: PropertyGroup[] }>): PropertyGroup[] {
219
+ function getVisibleGroups(components: DesignSchemaComponentView[]): PropertyGroup[] {
218
220
  return components?.reduce((acc: PropertyGroup[], component) => {
219
221
  const visibleGroups = component.propertyGroups.filter((group: PropertyGroup) =>
220
222
  shouldShowPropertyGroup(group, component.propertyGroups[0].properties, 'design')
@@ -228,7 +230,7 @@
228
230
 
229
231
  // Update the function with proper type annotations
230
232
  function getAccordionItemPosition(
231
- component: { propertyGroups: PropertyGroup[] },
233
+ component: DesignSchemaComponentView,
232
234
  propertyGroup: PropertyGroup,
233
235
  groupIndex: number
234
236
  ): 'first' | 'middle' | 'last' | 'only' {
@@ -1357,7 +1359,44 @@
1357
1359
  return '';
1358
1360
  }
1359
1361
 
1360
- function renderInputController(schema: string, property: any, propertyIndex: number, isFirstGroup: boolean, contentItemId?: string) {
1362
+ /**
1363
+ * The bag `renderInputController()` builds for one controller instance.
1364
+ * Declared keys are what PropsEditor itself always supplies; the index
1365
+ * signature carries the schema-driven constraint keys (`min`, `max`,
1366
+ * `options`, …) whose set depends on the property definition.
1367
+ * `describeInputController` picks from this bag per controller kind, so
1368
+ * anything a controller never reads never reaches it.
1369
+ */
1370
+ interface InputControllerProps {
1371
+ id: string;
1372
+ value: InputValue;
1373
+ opacity?: number;
1374
+ onChange: (value: InputValue) => void;
1375
+ onOpacityChange: (opacity: number) => void;
1376
+ portalTarget: string | HTMLElement;
1377
+ header?: { label: string; helpText?: string };
1378
+ config: InputControllerConfig;
1379
+ controllerId: string;
1380
+ openDropdowns: Writable<Set<string>>;
1381
+ toggleDropdown: (id: string) => void;
1382
+ onFocus: () => void;
1383
+ [schemaKey: string]: any;
1384
+ }
1385
+
1386
+ /**
1387
+ * What `renderInputController()` hands the template.
1388
+ * `component` is a 20-member union of controller component types — typing
1389
+ * it as the union makes `<svelte:component this={...}>` collapse attribute
1390
+ * checking to `never` (every prop errors); `any` restores the spread's
1391
+ * intended looseness.
1392
+ */
1393
+ interface RenderedInputController {
1394
+ component: any; // union of controller components — see comment above
1395
+ props: InputControllerProps;
1396
+ header: { label: string; helpText: string };
1397
+ }
1398
+
1399
+ function renderInputController(schema: string, property: any, propertyIndex: number, isFirstGroup: boolean, contentItemId?: string): RenderedInputController {
1361
1400
  const basePropertyId = property.id || property.name;
1362
1401
  const propertyId = contentItemId ? `${contentItemId}__${basePropertyId}` : basePropertyId;
1363
1402
  const controllerId = `${schema}-${propertyId}`;
@@ -1368,7 +1407,7 @@
1368
1407
  component: null,
1369
1408
  props: { id: controllerId },
1370
1409
  header: { label: basePropertyId, helpText: '' }
1371
- };
1410
+ } as RenderedInputController;
1372
1411
  }
1373
1412
  const InputControllerComponent = getInputControllerComponent(inputController.type.toLowerCase());
1374
1413
 
@@ -1392,17 +1431,11 @@
1392
1431
 
1393
1432
  const { description, label, ...restInputController } = inputController;
1394
1433
 
1395
- // Only set shouldFocus on initial mount or explicit focus request
1396
- const shouldFocus = activeControllerId === controllerId;
1397
-
1398
1434
  // Add special handling for position controller
1399
1435
  const additionalProps = inputController.type.toLowerCase() === 'position'
1400
1436
  ? { onDragStateChange: (dragging: boolean) => isDragging = dragging }
1401
1437
  : {};
1402
1438
 
1403
- // Add visibility dependency tracking
1404
- const hasVisibilityCondition = property.visibilityCondition !== undefined;
1405
-
1406
1439
  return {
1407
1440
  component: InputControllerComponent,
1408
1441
  props: {
@@ -1431,7 +1464,6 @@
1431
1464
  openDropdowns.set(new Set([id]));
1432
1465
  }
1433
1466
  },
1434
- shouldFocus,
1435
1467
  onFocus: () => {
1436
1468
  const newControllerId = `${schema}-${propertyId}`;
1437
1469
  if (schema === 'design' && lastFocusedDesignController !== newControllerId) {
@@ -1453,8 +1485,7 @@
1453
1485
  groupName: schema
1454
1486
  });
1455
1487
  }
1456
- },
1457
- 'data-visibility-dependent': hasVisibilityCondition
1488
+ }
1458
1489
  },
1459
1490
  header: {
1460
1491
  label: String(inputController.type || '').toLowerCase() === 'font'
@@ -1501,31 +1532,102 @@
1501
1532
  }
1502
1533
  }
1503
1534
 
1504
- /** Preserve the schema factory's props while narrowing each concrete render branch. */
1505
- function describeInputController(component: ReturnType<typeof getInputControllerComponent> | null, props: ReturnType<typeof renderInputController>['props']): ControllerDescriptor | null {
1535
+ /**
1536
+ * Narrows the render bag down to exactly the props the chosen controller
1537
+ * declares. Each pick literal is contextually typed against that
1538
+ * controller's declared props interface, so passing a key the controller
1539
+ * never reads — or forgetting a required one — is a compile error here
1540
+ * instead of a silent no-op at runtime.
1541
+ */
1542
+ function describeInputController(component: ReturnType<typeof getInputControllerComponent> | null, props: InputControllerProps): ControllerDescriptor | null {
1506
1543
  switch (component) {
1507
- case TextInputController: return { kind: 'textinput', props };
1508
- case TextAreaPreviewInputController: return { kind: 'textarea', props };
1509
- case NumberInputController: return { kind: 'number', props };
1510
- case SliderInputController: return { kind: 'slider', props };
1511
- case SliderRangeInputController: return { kind: 'sliderrange', props };
1512
- case DropdownInputController: return { kind: 'dropdown', props };
1513
- case DatePickerInputController: return { kind: 'datepicker', props };
1514
- case DateFieldInputController: return { kind: 'datefield', props };
1515
- case RangeCalendarInputController: return { kind: 'rangecalendar', props };
1516
- case SwitchInputController: return { kind: 'switch', props };
1517
- case ColorPickerInputController: return { kind: 'colorpicker', props };
1518
- case CheckboxInputController: return { kind: 'checkbox', props };
1519
- case RadioGroupInputController: return { kind: 'radiogroup', props };
1520
- case FontInputController: return { kind: 'font', props };
1521
- case PositionInputController: return { kind: 'position', props };
1522
- case BrowserInputController: return { kind: 'browser', props };
1523
- case FixedPositionInputController: return { kind: 'fixedposition', props };
1524
- case VectorInputController: return { kind: 'vector', props };
1525
- case AngleInputController: return { kind: 'angle', props };
1526
- case TimeInputController: return { kind: 'time', props };
1527
- case RoundedCornersInputController: return { kind: 'roundedcorners', props };
1528
- case TagsInputController: return { kind: 'tags', props };
1544
+ case TextInputController: return { kind: 'textinput', props: {
1545
+ label: props.label, value: props.value, onChange: props.onChange,
1546
+ config: props.config, type: props.type, placeholder: props.placeholder,
1547
+ description: props.description, onFocus: props.onFocus } };
1548
+ case TextAreaPreviewInputController: return { kind: 'textarea', props: {
1549
+ label: props.label, value: props.value, onChange: props.onChange,
1550
+ config: props.config, description: props.description, portalTarget: props.portalTarget } };
1551
+ case NumberInputController: return { kind: 'number', props: {
1552
+ label: props.label, value: props.value, onChange: props.onChange, config: props.config,
1553
+ helpText: props.helpText, description: props.description, onFocus: props.onFocus } };
1554
+ case SliderInputController: return { kind: 'slider', props: {
1555
+ height: props.height, elementsGap: props.elementsGap, thumbSize: props.thumbSize,
1556
+ sliderType: props.sliderType, barHeight: props.barHeight, label: props.label,
1557
+ value: props.value, onChange: props.onChange, config: props.config,
1558
+ helpText: props.helpText, min: props.min, max: props.max, step: props.step,
1559
+ onFocus: props.onFocus, autofocus: props.autofocus } };
1560
+ case SliderRangeInputController: return { kind: 'sliderrange', props: {
1561
+ label: props.label, value: props.value, onChange: props.onChange, config: props.config,
1562
+ helpText: props.helpText, min: props.min, max: props.max, step: props.step,
1563
+ onFocus: props.onFocus } };
1564
+ case DropdownInputController: return { kind: 'dropdown', props: {
1565
+ label: props.label, defaultSelectedLabel: props.defaultSelectedLabel, value: props.value,
1566
+ onChange: props.onChange, config: props.config, options: props.options,
1567
+ description: props.description, controllerId: props.controllerId,
1568
+ openDropdowns: props.openDropdowns, toggleDropdown: props.toggleDropdown,
1569
+ onFocus: props.onFocus } };
1570
+ case DatePickerInputController: return { kind: 'datepicker', props: {
1571
+ label: props.label, value: props.value, onChange: props.onChange,
1572
+ description: props.description, controllerId: props.controllerId,
1573
+ openDropdowns: props.openDropdowns, toggleDropdown: props.toggleDropdown,
1574
+ onFocus: props.onFocus } };
1575
+ case DateFieldInputController: return { kind: 'datefield', props: {
1576
+ label: props.label, value: props.value, onChange: props.onChange, config: props.config,
1577
+ helpText: props.helpText, onFocus: props.onFocus } };
1578
+ case RangeCalendarInputController: return { kind: 'rangecalendar', props: {
1579
+ label: props.label, value: props.value, onChange: props.onChange,
1580
+ helpText: props.helpText, controllerId: props.controllerId,
1581
+ openDropdowns: props.openDropdowns, toggleDropdown: props.toggleDropdown,
1582
+ onFocus: props.onFocus } };
1583
+ case SwitchInputController: return { kind: 'switch', props: {
1584
+ label: props.label, value: props.value, onChange: props.onChange,
1585
+ description: props.description, onFocus: props.onFocus } };
1586
+ case ColorPickerInputController: return { kind: 'colorpicker', props: {
1587
+ label: props.label, value: props.value, onChange: props.onChange,
1588
+ controllerId: props.controllerId, id: props.id, config: props.config,
1589
+ helpText: props.helpText, saveRecentColors: props.saveRecentColors,
1590
+ showOpacityControls: props.showOpacityControls, autofocus: props.autofocus,
1591
+ portalTarget: props.portalTarget, pickerSize: props.pickerSize,
1592
+ sliderWidth: props.sliderWidth, opacity: props.opacity, showRing: props.showRing,
1593
+ onFocus: props.onFocus, onOpacityChange: props.onOpacityChange } };
1594
+ case CheckboxInputController: return { kind: 'checkbox', props: {
1595
+ value: props.value, onChange: props.onChange, config: props.config,
1596
+ description: props.description, onFocus: props.onFocus, options: props.options } };
1597
+ case RadioGroupInputController: return { kind: 'radiogroup', props: {
1598
+ value: props.value, onChange: props.onChange, config: props.config,
1599
+ description: props.description, onFocus: props.onFocus, options: props.options } };
1600
+ case FontInputController: return { kind: 'font', props: {
1601
+ label: props.label, style: props.style, value: props.value, onChange: props.onChange,
1602
+ description: props.description, controllerId: props.controllerId,
1603
+ openDropdowns: props.openDropdowns, toggleDropdown: props.toggleDropdown,
1604
+ hideRecentFromList: props.hideRecentFromList, hideTrashIcon: props.hideTrashIcon } };
1605
+ case PositionInputController: return { kind: 'position', props: {
1606
+ value: props.value, onChange: props.onChange, onFocus: props.onFocus,
1607
+ maskThumb: props.maskThumb, onDragStateChange: props.onDragStateChange } };
1608
+ case BrowserInputController: return { kind: 'browser', props: {
1609
+ header: props.header, label: props.label, value: props.value,
1610
+ onChange: props.onChange, portalTarget: props.portalTarget, config: props.config } };
1611
+ case FixedPositionInputController: return { kind: 'fixedposition', props: {
1612
+ value: props.value, onChange: props.onChange, onFocus: props.onFocus, id: props.id } };
1613
+ case VectorInputController: return { kind: 'vector', props: {
1614
+ label: props.label, value: props.value, onChange: props.onChange,
1615
+ description: props.description, controllerId: props.controllerId,
1616
+ openDropdowns: props.openDropdowns, toggleDropdown: props.toggleDropdown,
1617
+ onFocus: props.onFocus, hideRecentFromList: props.hideRecentFromList,
1618
+ hideTrashIcon: props.hideTrashIcon } };
1619
+ case AngleInputController: return { kind: 'angle', props: {
1620
+ label: props.label, value: props.value, onChange: props.onChange, config: props.config,
1621
+ helpText: props.helpText, onFocus: props.onFocus } };
1622
+ case TimeInputController: return { kind: 'time', props: {
1623
+ label: props.label, value: props.value, onChange: props.onChange,
1624
+ helpText: props.helpText, onFocus: props.onFocus } };
1625
+ case RoundedCornersInputController: return { kind: 'roundedcorners', props: {
1626
+ value: props.value, onChange: props.onChange, helpText: props.helpText,
1627
+ onFocus: props.onFocus } };
1628
+ case TagsInputController: return { kind: 'tags', props: {
1629
+ label: props.label, value: props.value, onChange: props.onChange, config: props.config,
1630
+ helpText: props.helpText, onFocus: props.onFocus } };
1529
1631
  default: return null;
1530
1632
  }
1531
1633
  }
@@ -1636,39 +1738,16 @@
1636
1738
  }
1637
1739
 
1638
1740
  /**
1639
- * Interface definitions for content structure
1741
+ * SCHEMA SHAPES — aliases of the package's canonical schema views
1742
+ * (`$lib/schemaViews`, exported at the package root). PropsEditor used to
1743
+ * declare stricter local duplicates, which made every array flowing from
1744
+ * the open view props unassignable to them; the canonical names are the
1745
+ * same types now, so the contract holds across the package boundary.
1640
1746
  */
1641
- interface Property {
1642
- id: string;
1643
- name?: string;
1644
- type: string;
1645
- defaultValue?: any;
1646
- value?: any; // Add value property to interface
1647
- constraints?: Record<string, any>;
1648
- inputController: {
1649
- type: string;
1650
- description?: string;
1651
- helpText?: string;
1652
- config?: Record<string, any>;
1653
- };
1654
- }
1655
-
1656
- interface ContentItem {
1657
- // Saved content may carry schema-defined values alongside its metadata.
1658
- [propertyName: string]: unknown;
1659
- id: string;
1660
- properties: Array<Property>;
1661
- orderNumber?: number;
1662
- title?: string;
1663
- }
1664
-
1665
- interface ContentSchema {
1666
- genericContent: {
1667
- title: string;
1668
- properties: Array<Property>;
1669
- };
1670
- contentItems: ContentItem[];
1671
- }
1747
+ type Property = DesignSchemaPropertyView;
1748
+ type PropertyGroup = DesignSchemaPropertyGroupView;
1749
+ type ContentItem = ContentSchemaItemView;
1750
+ type ContentSchema = ContentSchemaView;
1672
1751
 
1673
1752
  onMount(() => {
1674
1753
  const resizeObserver = new ResizeObserver(() => {
@@ -2091,7 +2170,13 @@
2091
2170
  const key = p.id || p.name;
2092
2171
  // Canonical state first; schema mirror/defaultValue are legacy
2093
2172
  // seeds for keys the editor never touched.
2094
- const liveOnItem = key != null ? (item as Record<string, unknown>)[key] : undefined;
2173
+ const liveOnItem =
2174
+ key != null
2175
+ ? // Items legitimately carry canonical per-key state alongside
2176
+ // `properties`; route through unknown to the index signature
2177
+ // honestly.
2178
+ (item as unknown as Record<string, unknown>)[key]
2179
+ : undefined;
2095
2180
  props[key] = normalizeContentPropertyValue(
2096
2181
  pickLiveContentValue(
2097
2182
  contentState[`${item.id}__${key}`],
@@ -2587,8 +2672,7 @@
2587
2672
  <InputControllerRenderer
2588
2673
  controller={describeInputController(InputComponent, {
2589
2674
  ...props,
2590
- value: reactiveValue !== undefined ? reactiveValue : props.value,
2591
- shouldFocus: activeControllerId === props.id
2675
+ value: reactiveValue !== undefined ? reactiveValue : props.value
2592
2676
  })}
2593
2677
  />
2594
2678
  </div>
@@ -2698,7 +2782,7 @@
2698
2782
 
2699
2783
  <div class="ic-input-ct {isFontIc ? 'w-full' : 'shrink-0'}" style={isFontIc ? '' : 'width: 185px;'}>
2700
2784
  <InputControllerRenderer
2701
- controller={describeInputController(InputComponent, { ...props, header, shouldFocus: activeControllerId === props.id })}
2785
+ controller={describeInputController(InputComponent, { ...props, header })}
2702
2786
  />
2703
2787
  </div>
2704
2788
  </div>
@@ -2796,7 +2880,7 @@
2796
2880
 
2797
2881
  <div class="ic-input-ct {isFontIc ? 'w-full' : 'shrink-0'}" style={isFontIc ? '' : 'width: 185px;'}>
2798
2882
  <InputControllerRenderer
2799
- controller={describeInputController(InputComponent, { ...props, header, shouldFocus: activeControllerId === props.id })}
2883
+ controller={describeInputController(InputComponent, { ...props, header })}
2800
2884
  />
2801
2885
  </div>
2802
2886
  </div>
@@ -1,3 +1,4 @@
1
+ import type { ContentSchemaView, DesignSchemaView } from '../../schemaViews.js';
1
2
  /**
2
3
  * STYLES from Widgetic Design System
3
4
  * Note: props-editor.css global rules (body, *) moved to app.css (standalone only).
@@ -24,8 +25,8 @@ declare const PropsEditor: $$__sveltets_2_IsomorphicComponent<{
24
25
  style?: string;
25
26
  width?: string;
26
27
  height?: string;
27
- designSchema?: any;
28
- contentSchema?: any | undefined;
28
+ designSchema?: DesignSchemaView | null;
29
+ contentSchema?: ContentSchemaView | undefined;
29
30
  itemTemplate?: any;
30
31
  designTabTitle?: string;
31
32
  contentTabTitle?: string;
@@ -11,19 +11,33 @@
11
11
  * - Smooth transitions and visual feedback
12
12
  -->
13
13
 
14
+ <script module lang="ts">
15
+ import type { InputControllerConfig } from './index';
16
+
17
+ /** Declared props contract — export lets below are annotated from it so the
18
+ * interface cannot drift from what the component actually reads. */
19
+ export interface AngleInputControllerProps {
20
+ label: string;
21
+ value?: number;
22
+ onChange: (newValue: number) => void;
23
+ config?: InputControllerConfig;
24
+ helpText?: string;
25
+ onFocus?: () => void;
26
+ }
27
+ </script>
28
+
14
29
  <script lang="ts">
15
30
  import { onMount, onDestroy } from 'svelte';
16
31
  import { Button, Input } from '@widgetic/design-system/components';
17
32
  import InputControllerHeader from './InputControllerHeader.svelte';
18
33
  import NumberControls from './NumberControls.svelte';
19
- import type { InputControllerConfig } from './index';
20
34
 
21
- export let label: string;
22
- export let value: number = 0;
23
- export let onChange: (newValue: number) => void;
24
- export let config: InputControllerConfig = {};
25
- export let helpText: string | undefined = undefined;
26
- export let onFocus: () => void = () => {};
35
+ export let label: AngleInputControllerProps['label'];
36
+ export let value: AngleInputControllerProps['value'] = 0;
37
+ export let onChange: AngleInputControllerProps['onChange'];
38
+ export let config: AngleInputControllerProps['config'] = {};
39
+ export let helpText: AngleInputControllerProps['helpText'] = undefined;
40
+ export let onFocus: AngleInputControllerProps['onFocus'] = () => {};
27
41
 
28
42
  // Circle indicator state
29
43
  let isDragging = false;
@@ -1,4 +1,14 @@
1
1
  import type { InputControllerConfig } from './index';
2
+ /** Declared props contract — export lets below are annotated from it so the
3
+ * interface cannot drift from what the component actually reads. */
4
+ export interface AngleInputControllerProps {
5
+ label: string;
6
+ value?: number;
7
+ onChange: (newValue: number) => void;
8
+ config?: InputControllerConfig;
9
+ helpText?: string;
10
+ onFocus?: () => void;
11
+ }
2
12
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
3
13
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
4
14
  $$bindings?: Bindings;
@@ -13,12 +23,12 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
13
23
  z_$$bindings?: Bindings;
14
24
  }
15
25
  declare const AngleInputController: $$__sveltets_2_IsomorphicComponent<{
16
- label: string;
17
- value?: number;
18
- onChange: (newValue: number) => void;
19
- config?: InputControllerConfig;
20
- helpText?: string | undefined;
21
- onFocus?: () => void;
26
+ label: AngleInputControllerProps["label"];
27
+ value?: AngleInputControllerProps["value"];
28
+ onChange: AngleInputControllerProps["onChange"];
29
+ config?: AngleInputControllerProps["config"];
30
+ helpText?: AngleInputControllerProps["helpText"];
31
+ onFocus?: AngleInputControllerProps["onFocus"];
22
32
  }, {
23
33
  [evt: string]: CustomEvent<any>;
24
34
  }, {}, {}, string>;
@@ -1,3 +1,25 @@
1
+ <script module lang="ts">
2
+ import type { InputControllerConfig } from './index';
3
+
4
+ interface FileValue {
5
+ id: string;
6
+ file: string;
7
+ url: string;
8
+ hash?: string; // Add hash property for ThumbHash
9
+ }
10
+
11
+ /** Declared props contract — export lets below are annotated from it so the
12
+ * interface cannot drift from what the component actually reads. */
13
+ export interface BrowserInputControllerProps {
14
+ header?: { label: string; helpText?: string };
15
+ label?: string;
16
+ value?: string | FileValue;
17
+ onChange?: (newValue: string | FileValue) => void;
18
+ portalTarget?: string | HTMLElement;
19
+ config?: InputControllerConfig;
20
+ }
21
+ </script>
22
+
1
23
  <script lang="ts">
2
24
  /**
3
25
  * BrowserInputController Component
@@ -16,8 +38,6 @@
16
38
  import X from '../icons/x.svelte';
17
39
  // console.log("BrowserInputController - Tooltip:", Tooltip);
18
40
 
19
- // config imports
20
- import type { InputControllerConfig } from './index';
21
41
 
22
42
  // utils imports
23
43
  import MediaManager from '../../utils/MediaManager';
@@ -32,26 +52,19 @@
32
52
 
33
53
  import { InputControllerHeader } from './index';
34
54
 
35
- interface FileValue {
36
- id: string;
37
- file: string;
38
- url: string;
39
- hash?: string; // Add hash property for ThumbHash
40
- }
41
-
42
55
  // Component props with default values
43
- export let header: { label: string; helpText?: string } | undefined = undefined;
44
- export let label: string = '';
45
- export let value: string | FileValue = '';
46
- export let onChange: (newValue: string | FileValue) => void = () => {};
47
- export let portalTarget: string | HTMLElement = 'portal-element';
56
+ export let header: BrowserInputControllerProps['header'] = undefined;
57
+ export let label: BrowserInputControllerProps['label'] = '';
58
+ export let value: BrowserInputControllerProps['value'] = '';
59
+ export let onChange: BrowserInputControllerProps['onChange'] = () => {};
60
+ export let portalTarget: BrowserInputControllerProps['portalTarget'] = 'portal-element';
48
61
 
49
62
  /** Full-panel overlay when portaled to props-editor panel (not document body). */
50
63
  $: fillEditorPanel =
51
64
  portalTarget === 'props-editor-file-browser-portal' ||
52
65
  (portalTarget instanceof HTMLElement &&
53
66
  portalTarget.id === 'props-editor-file-browser-portal');
54
- export let config: InputControllerConfig = {};
67
+ export let config: BrowserInputControllerProps['config'] = {};
55
68
 
56
69
  // Extract allowed file types from config or default to all files
57
70
  const fileTypes = config.fileTypes || ['*'];