@widgetic/editor 4.0.1 โ†’ 4.0.3

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 (39) hide show
  1. package/dist/components/MockWidget.svelte +1 -1
  2. package/dist/components/WidgetPreview.svelte +36 -52
  3. package/dist/components/editors/EmbedEditor.svelte +1 -1
  4. package/dist/components/editors/PropsEditor.svelte +309 -227
  5. package/dist/components/ic/BrowserInputController.svelte +26 -19
  6. package/dist/components/ic/CheckboxInputController.svelte +1 -1
  7. package/dist/components/ic/ColorPickerInputController.svelte +37 -37
  8. package/dist/components/ic/DatePickerInputController.svelte +6 -6
  9. package/dist/components/ic/DropdownInputController.svelte +1 -1
  10. package/dist/components/ic/FontInputController.svelte +225 -116
  11. package/dist/components/ic/FontInputController.svelte.d.ts +4 -2
  12. package/dist/components/ic/NumberInputController.svelte +37 -37
  13. package/dist/components/ic/RadioGroupInputController.svelte +1 -1
  14. package/dist/components/ic/SliderRangeInputController.svelte +1 -1
  15. package/dist/components/ic/SwitchInputController.svelte +1 -1
  16. package/dist/components/ic/TextAreaInputController.svelte +213 -85
  17. package/dist/components/ic/TextAreaInputController.svelte.d.ts +1 -0
  18. package/dist/components/ic/TextAreaPreviewInputController.svelte +81 -79
  19. package/dist/components/ic/VectorInputController.svelte +34 -34
  20. package/dist/components/ic/shared/CategoryTabs.svelte +1 -0
  21. package/dist/components/ic/shared/RecentItemsSection.svelte +21 -6
  22. package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +1 -0
  23. package/dist/config.d.ts +6 -0
  24. package/dist/config.js +19 -7
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/stores/userSession.d.ts +1 -1
  28. package/dist/stores/userSession.js +17 -2
  29. package/dist/utils/FontManager.d.ts +3 -1
  30. package/dist/utils/FontManager.js +95 -103
  31. package/dist/utils/VectorManager.d.ts +3 -1
  32. package/dist/utils/VectorManager.js +30 -70
  33. package/dist/utils/colorTransparency.js +3 -3
  34. package/dist/utils/fontUtils.js +1 -1
  35. package/dist/utils/passiveEvents.d.ts +5 -0
  36. package/dist/utils/passiveEvents.js +9 -18
  37. package/dist/utils/recentColors.js +15 -15
  38. package/dist/utils/vectorUtils.js +1 -1
  39. package/package.json +1 -1
@@ -157,6 +157,18 @@
157
157
  let hasLoggedContent = false; // Prevents duplicate logging of content tab info
158
158
  let designState: Record<string, InputValue> = { ...initialDesignValues }; // Stores design property values
159
159
  let contentState: Record<string, InputValue> = { ...initialContentValues }; // Stores content property values
160
+
161
+ let prevInitialDesignValues = initialDesignValues;
162
+ $: if (initialDesignValues !== prevInitialDesignValues) {
163
+ prevInitialDesignValues = initialDesignValues;
164
+ designState = { ...initialDesignValues };
165
+ }
166
+
167
+ let prevInitialContentValues = initialContentValues;
168
+ $: if (initialContentValues !== prevInitialContentValues) {
169
+ prevInitialContentValues = initialContentValues;
170
+ contentState = { ...initialContentValues };
171
+ }
160
172
  let isGenericContentOpen = true; // Controls generic content section visibility
161
173
  let lastFocusedDesignController: string | null = null; // Tracks last focused design input
162
174
  let lastFocusedContentController: string | null = null; // Tracks last focused content input
@@ -226,7 +238,7 @@
226
238
  }
227
239
 
228
240
  if (keysToRemove.length > 0) {
229
- console.log('๐Ÿงน [PropsEditor] Clearing font weight states on init:', keysToRemove);
241
+ // console.log('๐Ÿงน [PropsEditor] Clearing font weight states on init:', keysToRemove);
230
242
  keysToRemove.forEach(key => localStorage.removeItem(key));
231
243
  }
232
244
  }
@@ -275,26 +287,8 @@
275
287
  // Initialize FontManager
276
288
  const initializeEditor = async () => {
277
289
  try {
278
- // console.log('๐Ÿš€ [PropsEditor] Starting initialization...', {
279
- // hasDesignSchema: !!designSchema,
280
- // hasContentSchema: !!contentSchema
281
- // });
282
-
283
- // Initialize FontManager when PropsEditor mounts
284
- // console.log('๐Ÿ“š [PropsEditor] Initializing FontManager...');
285
- const fontManagerInstance = await FontManager.getInstance(appConfig.GOOGLE_FONTS_API_KEY);
286
- // console.log('โœ… [PropsEditor] FontManager initialized');
287
-
288
- // Preload the "Mine" category fonts to prevent loading delay in FontInputController instances
289
- try {
290
- const customFonts = await fontManagerInstance.loadCustomFonts();
291
- console.log('โœ… [PropsEditor] Preloaded "Mine" category fonts:', {
292
- count: customFonts.length,
293
- fonts: customFonts.slice(0, 3).map(f => f.family)
294
- });
295
- } catch (error) {
296
- console.error('โŒ [PropsEditor] Failed to preload "Mine" category fonts:', error);
297
- }
290
+ // Initialize FontManager in background without blocking editor startup
291
+ void FontManager.getInstance(appConfig.GOOGLE_FONTS_API_KEY);
298
292
 
299
293
  // Log design tab info if schema exists
300
294
  if (designSchema && designSchema[0]?.components && !hasLoggedDesign) {
@@ -507,7 +501,7 @@
507
501
 
508
502
  // Update event handlers
509
503
  currentDraggable.on('drag:start', (event: any) => {
510
- console.log('[Draggable] drag:start event');
504
+ // console.log('[Draggable] drag:start event');
511
505
  isDragging = true; // Set flag immediately to prevent afterUpdate interference
512
506
 
513
507
  const dragHandle = event.originalSource?.querySelector('.drag-handle');
@@ -577,7 +571,7 @@
577
571
  if (deltaX > dragThreshold || deltaY > dragThreshold) {
578
572
  isDragStarted = true;
579
573
  // isDragging = true; - This is now set in drag:start
580
- console.log('[Draggable] isDragStarted set to true');
574
+ // console.log('[Draggable] isDragStarted set to true');
581
575
 
582
576
  requestAnimationFrame(() => {
583
577
  const mirror = document.querySelector('.draggable-mirror');
@@ -705,7 +699,7 @@
705
699
  });
706
700
 
707
701
  currentDraggable.on('sortable:sorted', (event: { newIndex: number; oldIndex: number }) => {
708
- console.log('[Draggable] sortable:sorted event fired', { oldIndex: event.oldIndex, newIndex: event.newIndex });
702
+ // console.log('[Draggable] sortable:sorted event fired', { oldIndex: event.oldIndex, newIndex: event.newIndex });
709
703
  try {
710
704
  const { oldIndex, newIndex } = event;
711
705
 
@@ -718,7 +712,7 @@
718
712
 
719
713
  // No need to do anything if the item was dropped in the same place
720
714
  if (oldIndex === newIndex) {
721
- console.log('[Drag] Item dropped in the same position. No change needed.');
715
+ // console.log('[Drag] Item dropped in the same position. No change needed.');
722
716
  isDragStarted = false;
723
717
  return;
724
718
  }
@@ -728,7 +722,7 @@
728
722
  isDragStarted = false;
729
723
  return;
730
724
  }
731
- console.log('[Drag] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
725
+ // console.log('[Drag] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
732
726
 
733
727
  const items = [...contentSchema.contentItems];
734
728
  const [movedItem] = items.splice(oldIndex, 1);
@@ -749,7 +743,7 @@
749
743
 
750
744
  const renumbered = renumberAutoItemTitles(items);
751
745
  contentSchema = { ...contentSchema, contentItems: renumbered };
752
- console.log('[Drag] After (reordered with indices):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
746
+ // console.log('[Drag] After (reordered with indices):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
753
747
  dispatch('updated', { contentSchema });
754
748
  dispatchContentItemsChanged(contentSchema.contentItems);
755
749
 
@@ -855,9 +849,11 @@
855
849
  };
856
850
 
857
851
  // Add this near other reactive declarations at the top level
858
- $: if (Array.isArray(contentSchema?.contentItems) && contentSchema.contentItems.length > 0 && !itemTemplate) {
859
- itemTemplate = JSON.parse(JSON.stringify(contentSchema.contentItems[0]));
860
- // console.log('Template initialized:', itemTemplate);
852
+ $: if (Array.isArray(contentSchema?.contentItems)) {
853
+ const templateSource = contentSchema.contentItems.find((item: ContentItem) => Array.isArray(item.properties) && item.properties.length > 0);
854
+ if (templateSource) {
855
+ itemTemplate = JSON.parse(JSON.stringify(templateSource));
856
+ }
861
857
  }
862
858
 
863
859
  // Populate contentState with per-item values keyed as "itemId__propName".
@@ -872,10 +868,12 @@
872
868
  if (contentState[key] === undefined) {
873
869
  // Prefer the parent's canonical saved-value map over the
874
870
  // schema mirror, which older compositions may not fill.
875
- const parentValue = parentItemValues?.[item.id]?.[prop.id || prop.name];
876
- const seed = parentValue !== undefined ? parentValue : prop.value;
877
- if (seed !== undefined) {
878
- patch[key] = seed;
871
+ const propKey = prop.id || prop.name;
872
+ const parentValue = parentItemValues?.[item.id]?.[propKey];
873
+ const liveOnItem = propKey != null ? (item as Record<string, unknown>)[propKey] : undefined;
874
+ const seed = pickLiveContentValue(parentValue, liveOnItem, prop.value);
875
+ if (seed !== undefined && seed !== '') {
876
+ patch[key] = seed as InputValue;
879
877
  needsUpdate = true;
880
878
  }
881
879
  }
@@ -883,7 +881,7 @@
883
881
  }
884
882
  if (needsUpdate) {
885
883
  contentState = { ...contentState, ...patch };
886
- console.log('[PropsEditor] Initialized content item state keys:', Object.keys(patch));
884
+ // console.log('[PropsEditor] Initialized content item state keys:', Object.keys(patch));
887
885
  }
888
886
  }
889
887
 
@@ -1022,63 +1020,61 @@
1022
1020
  * @param opacity - Optional opacity value for color properties
1023
1021
  */
1024
1022
  function updateState(schema: string, propertyId: string, value: InputValue, opacity?: number) {
1025
- requestAnimationFrame(() => {
1026
- if (schema === 'design') {
1027
- const newState = { ...designState };
1028
- newState[propertyId] = value;
1029
- if (opacity !== undefined) {
1030
- newState[`${propertyId}_opacity`] = opacity;
1031
- }
1032
- designState = newState;
1033
- } else {
1034
- const newState = { ...contentState };
1035
- newState[propertyId] = value;
1036
- if (opacity !== undefined) {
1037
- newState[`${propertyId}_opacity`] = opacity;
1038
- }
1039
- contentState = newState;
1040
-
1041
- // For content item properties (keyed as "itemId__propName"), also update item.properties[].value
1042
- if (propertyId.includes('__') && Array.isArray(contentSchema?.contentItems)) {
1043
- const [itemId, basePropName] = propertyId.split('__');
1044
- const item = contentSchema.contentItems.find((i: ContentItem) => i.id === itemId);
1045
- if (item) {
1046
- const prop = item.properties.find((p: any) => (p.id || p.name) === basePropName);
1047
- if (prop) prop.value = value;
1048
-
1049
- // Update the collapsible header title when a "title"-like property changes
1050
- const propName = (basePropName || '').toLowerCase();
1051
- if (propName.includes('title') || propName.includes('name') || propName.includes('label')) {
1052
- if (typeof value === 'string' && value.trim()) {
1053
- item.title = value;
1054
- contentSchema = contentSchema; // trigger Svelte reactivity
1055
- }
1023
+ if (schema === 'design') {
1024
+ const newState = { ...designState };
1025
+ newState[propertyId] = value;
1026
+ if (opacity !== undefined) {
1027
+ newState[`${propertyId}_opacity`] = opacity;
1028
+ }
1029
+ designState = newState;
1030
+ } else {
1031
+ const newState = { ...contentState };
1032
+ newState[propertyId] = value;
1033
+ if (opacity !== undefined) {
1034
+ newState[`${propertyId}_opacity`] = opacity;
1035
+ }
1036
+ contentState = newState;
1037
+
1038
+ // For content item properties (keyed as "itemId__propName"), also update item.properties[].value
1039
+ if (propertyId.includes('__') && Array.isArray(contentSchema?.contentItems)) {
1040
+ const [itemId, basePropName] = propertyId.split('__');
1041
+ const item = contentSchema.contentItems.find((i: ContentItem) => i.id === itemId);
1042
+ if (item) {
1043
+ const prop = item.properties.find((p: any) => (p.id || p.name) === basePropName);
1044
+ if (prop) prop.value = value;
1045
+
1046
+ // Update the collapsible header title when a "title"-like property changes
1047
+ const propName = (basePropName || '').toLowerCase();
1048
+ if (propName.includes('title') || propName.includes('name') || propName.includes('label')) {
1049
+ if (typeof value === 'string' && value.trim()) {
1050
+ item.title = value;
1051
+ contentSchema = contentSchema; // trigger Svelte reactivity
1056
1052
  }
1057
1053
  }
1058
1054
  }
1059
1055
  }
1056
+ }
1060
1057
 
1061
- // Dispatch with both composite key and item context for parent to handle
1062
- let itemId: string | undefined;
1063
- let originalPropertyId = propertyId;
1064
- if (schema === 'content' && propertyId.includes('__')) {
1065
- [itemId, originalPropertyId] = propertyId.split('__');
1066
- }
1067
-
1068
- dispatch('propertyChange', { schema, propertyId: originalPropertyId, value, opacity, itemId });
1058
+ // Dispatch with both composite key and item context for parent to handle
1059
+ let itemId: string | undefined;
1060
+ let originalPropertyId = propertyId;
1061
+ if (schema === 'content' && propertyId.includes('__')) {
1062
+ [itemId, originalPropertyId] = propertyId.split('__');
1063
+ }
1064
+
1065
+ dispatch('propertyChange', { schema, propertyId: originalPropertyId, value, opacity, itemId });
1069
1066
 
1070
- if (schema === 'content' && itemId && Array.isArray(contentSchema?.contentItems)) {
1071
- dispatchContentItemsChanged(contentSchema.contentItems);
1072
- }
1067
+ if (schema === 'content' && itemId && Array.isArray(contentSchema?.contentItems)) {
1068
+ dispatchContentItemsChanged(contentSchema.contentItems);
1069
+ }
1073
1070
 
1074
- // When editing a content item property, navigate widget to that item
1075
- if (itemId) {
1076
- const itemIndex = getContentItemIndex(itemId);
1077
- if (itemIndex >= 0) {
1078
- dispatch('goToItem', { index: itemIndex });
1079
- }
1071
+ // When editing a content item property, navigate widget to that item
1072
+ if (itemId) {
1073
+ const itemIndex = getContentItemIndex(itemId);
1074
+ if (itemIndex >= 0) {
1075
+ dispatch('goToItem', { index: itemIndex });
1080
1076
  }
1081
- });
1077
+ }
1082
1078
  }
1083
1079
 
1084
1080
  function getContentItemsMax(): number | null {
@@ -1133,10 +1129,10 @@
1133
1129
  lastFocusedContentController = controllerId;
1134
1130
  }
1135
1131
 
1136
- console.log(`Controller focused in ${currentTab} tab:`, {
1137
- controllerId,
1138
- ...controllerInfo
1139
- });
1132
+ // console.log(`Controller focused in ${currentTab} tab:`, {
1133
+ // controllerId,
1134
+ // ...controllerInfo
1135
+ // });
1140
1136
  }
1141
1137
  }
1142
1138
 
@@ -1149,12 +1145,12 @@
1149
1145
  const currentValue = tab ?? getActiveTab();
1150
1146
  const previousTab = currentTab;
1151
1147
 
1152
- console.log('๐Ÿ”„ [PropsEditor] Tab Change:', {
1153
- from: previousTab,
1154
- to: currentValue,
1155
- hasContentSchema: !!contentSchema,
1156
- isInitialLoad
1157
- });
1148
+ // console.log('๐Ÿ”„ [PropsEditor] Tab Change:', {
1149
+ // from: previousTab,
1150
+ // to: currentValue,
1151
+ // hasContentSchema: !!contentSchema,
1152
+ // isInitialLoad
1153
+ // });
1158
1154
 
1159
1155
  // Update tab state
1160
1156
  currentTab = currentValue;
@@ -1164,16 +1160,16 @@
1164
1160
 
1165
1161
  // Only proceed with content-related operations if contentSchema exists
1166
1162
  if (currentValue === 'content' && !contentSchema) {
1167
- console.log('โ„น๏ธ [PropsEditor] Content tab selected but no content schema available');
1163
+ // console.log('โ„น๏ธ [PropsEditor] Content tab selected but no content schema available');
1168
1164
  return;
1169
1165
  }
1170
1166
 
1171
1167
  // Only log state if content exists
1172
1168
  if (contentSchema) {
1173
- console.log('๐Ÿ’พ Storing', currentValue, 'tab state:', {
1174
- openItems: Array.from(openItems),
1175
- openAccordion: currentValue === 'design' ? designOpenAccordion : contentOpenAccordion
1176
- });
1169
+ // console.log('๐Ÿ’พ Storing', currentValue, 'tab state:', {
1170
+ // openItems: Array.from(openItems),
1171
+ // openAccordion: currentValue === 'design' ? designOpenAccordion : contentOpenAccordion
1172
+ // });
1177
1173
  }
1178
1174
 
1179
1175
  // First find and remove all modal-related elements, but preserve draggable
@@ -1194,15 +1190,15 @@
1194
1190
  '.command-dialog'
1195
1191
  ].join(','));
1196
1192
 
1197
- console.log('๐Ÿงน Cleaning up modals:', {
1198
- count: modalElements.length,
1199
- elements: Array.from(modalElements).map(el => ({
1200
- className: el.className,
1201
- role: el.getAttribute('role'),
1202
- state: el.getAttribute('data-state'),
1203
- side: el.getAttribute('data-side')
1204
- }))
1205
- });
1193
+ // console.log('๐Ÿงน Cleaning up modals:', {
1194
+ // count: modalElements.length,
1195
+ // elements: Array.from(modalElements).map(el => ({
1196
+ // className: el.className,
1197
+ // role: el.getAttribute('role'),
1198
+ // state: el.getAttribute('data-state'),
1199
+ // side: el.getAttribute('data-side')
1200
+ // }))
1201
+ // });
1206
1202
 
1207
1203
  // Remove each modal immediately, but protect scroll-related and draggable elements
1208
1204
  modalElements.forEach(element => {
@@ -1232,7 +1228,7 @@
1232
1228
  // Clear the dropdowns store
1233
1229
  const currentDropdowns = get(openDropdowns);
1234
1230
  if (currentDropdowns.size > 0) {
1235
- console.log('๐Ÿ—‘๏ธ Clearing dropdowns:', Array.from(currentDropdowns));
1231
+ // console.log('๐Ÿ—‘๏ธ Clearing dropdowns:', Array.from(currentDropdowns));
1236
1232
  }
1237
1233
  openDropdowns.set(new Set());
1238
1234
 
@@ -1250,7 +1246,7 @@
1250
1246
  if (currentValue === 'content' && contentSchema) {
1251
1247
  // Wait for tab content to be rendered
1252
1248
  setTimeout(async () => {
1253
- console.log('๐ŸŽฏ Checking draggable state in content tab');
1249
+ // console.log('๐ŸŽฏ Checking draggable state in content tab');
1254
1250
 
1255
1251
  // Check if draggable needs initialization
1256
1252
  const containers = document.querySelectorAll('.content-items');
@@ -1260,10 +1256,10 @@
1260
1256
  !document.querySelector('.draggable-container');
1261
1257
 
1262
1258
  if (needsInit) {
1263
- console.log('๐Ÿ”„ Reinitializing draggable');
1259
+ // console.log('๐Ÿ”„ Reinitializing draggable');
1264
1260
  await initializeDraggable();
1265
1261
  } else {
1266
- console.log('โœ… Draggable already initialized and working');
1262
+ // console.log('โœ… Draggable already initialized and working');
1267
1263
  }
1268
1264
  }, 50);
1269
1265
  }
@@ -1274,10 +1270,10 @@
1274
1270
 
1275
1271
  // Only log if content exists
1276
1272
  if (contentSchema) {
1277
- console.log('๐Ÿ”„ Restoring state for', currentValue, 'tab:', {
1278
- openItems: Array.from(openItems),
1279
- accordionToOpen
1280
- });
1273
+ // console.log('๐Ÿ”„ Restoring state for', currentValue, 'tab:', {
1274
+ // openItems: Array.from(openItems),
1275
+ // accordionToOpen
1276
+ // });
1281
1277
  }
1282
1278
 
1283
1279
  // Restore accordion states after a short delay to ensure DOM is ready
@@ -1290,7 +1286,7 @@
1290
1286
  }
1291
1287
  }
1292
1288
  if (itemsToOpen.length > 0) {
1293
- console.log('๐Ÿ“‚ Opened accordions:', itemsToOpen);
1289
+ // console.log('๐Ÿ“‚ Opened accordions:', itemsToOpen);
1294
1290
  }
1295
1291
  }, 50);
1296
1292
 
@@ -1301,7 +1297,7 @@
1301
1297
  : lastFocusedContentController;
1302
1298
 
1303
1299
  if (controllerToFocus) {
1304
- console.log('๐ŸŽฏ Focusing last focused controller:', controllerToFocus);
1300
+ // console.log('๐ŸŽฏ Focusing last focused controller:', controllerToFocus);
1305
1301
  activeControllerId = controllerToFocus;
1306
1302
  focusController(controllerToFocus);
1307
1303
  } else {
@@ -1312,7 +1308,7 @@
1312
1308
  if (firstController) {
1313
1309
  const id = firstController.getAttribute('id');
1314
1310
  if (id) {
1315
- console.log('๐ŸŽฏ Focusing first controller:', id);
1311
+ // console.log('๐ŸŽฏ Focusing first controller:', id);
1316
1312
  activeControllerId = id;
1317
1313
  focusController(id);
1318
1314
  }
@@ -1325,12 +1321,30 @@
1325
1321
  * Input Controller Management
1326
1322
  * Handles rendering and configuration of different input types
1327
1323
  */
1324
+ function fontIcVisibleLabel(propertyId: string, schemaLabel?: string, description?: string): string {
1325
+ const fromSchema = String(schemaLabel || '').trim();
1326
+ if (fromSchema) return fromSchema;
1327
+ const trimmed = String(description || '').trim();
1328
+ if (trimmed && !/^(titleFont|subtitleFont|headingFont|descriptionFont|font|titeFont)$/i.test(trimmed)) {
1329
+ return trimmed;
1330
+ }
1331
+ return '';
1332
+ }
1333
+
1328
1334
  function renderInputController(schema: string, property: any, propertyIndex: number, isFirstGroup: boolean, contentItemId?: string) {
1329
1335
  const basePropertyId = property.id || property.name;
1330
1336
  const propertyId = contentItemId ? `${contentItemId}__${basePropertyId}` : basePropertyId;
1337
+ const controllerId = `${schema}-${propertyId}`;
1331
1338
  const { type, inputController } = property;
1339
+ if (!inputController?.type) {
1340
+ console.warn('[PropsEditor] Skipping property without inputController.type:', propertyId, property);
1341
+ return {
1342
+ component: null,
1343
+ props: { id: controllerId },
1344
+ header: { label: basePropertyId, helpText: '' }
1345
+ };
1346
+ }
1332
1347
  const InputControllerComponent = getInputControllerComponent(inputController.type.toLowerCase());
1333
- const controllerId = `${schema}-${propertyId}`;
1334
1348
 
1335
1349
  const stateValue = schema === 'design' ? designState[propertyId] : contentState[propertyId];
1336
1350
  let initialValue = stateValue !== undefined ? stateValue : (property.value !== undefined ? property.value : property.defaultValue);
@@ -1373,6 +1387,9 @@
1373
1387
  onOpacityChange: (opacity: number) => updateState(schema, propertyId, initialValue, opacity),
1374
1388
  portalTarget,
1375
1389
  ...restInputController,
1390
+ ...(String(inputController.type || '').toLowerCase() === 'font'
1391
+ ? { label: fontIcVisibleLabel(propertyId, label, description) }
1392
+ : {}),
1376
1393
  ...property.constraints,
1377
1394
  ...additionalProps,
1378
1395
  config: inputController.config,
@@ -1381,10 +1398,10 @@
1381
1398
  toggleDropdown: (id: string) => {
1382
1399
  const currentSet = get(openDropdowns);
1383
1400
  if (currentSet.has(id)) {
1384
- console.log(`[toggleDropdown] CLOSING ${id}`);
1401
+ // console.log(`[toggleDropdown] CLOSING ${id}`);
1385
1402
  openDropdowns.set(new Set());
1386
1403
  } else {
1387
- console.log(`[toggleDropdown] OPENING ${id} (closing all others)`);
1404
+ // console.log(`[toggleDropdown] OPENING ${id} (closing all others)`);
1388
1405
  openDropdowns.set(new Set([id]));
1389
1406
  }
1390
1407
  },
@@ -1414,7 +1431,9 @@
1414
1431
  'data-visibility-dependent': hasVisibilityCondition
1415
1432
  },
1416
1433
  header: {
1417
- label: description || propertyId,
1434
+ label: String(inputController.type || '').toLowerCase() === 'font'
1435
+ ? fontIcVisibleLabel(propertyId, label, description)
1436
+ : (description || propertyId),
1418
1437
  helpText: inputController.helpText
1419
1438
  }
1420
1439
  };
@@ -1464,7 +1483,7 @@
1464
1483
  * Used when changing tabs or when scroll events occur
1465
1484
  */
1466
1485
  function closeAllDropdowns() {
1467
- console.log('Closing all dropdowns');
1486
+ // console.log('Closing all dropdowns');
1468
1487
  openDropdowns.set(new Set());
1469
1488
  }
1470
1489
 
@@ -1528,7 +1547,7 @@
1528
1547
  * @param itemId - Identifier for the content item to toggle
1529
1548
  */
1530
1549
  function toggleItem(itemId: string) {
1531
- console.log('๐Ÿ”„ Toggling item:', itemId, 'Current state:', openItems.has(itemId));
1550
+ // console.log('๐Ÿ”„ Toggling item:', itemId, 'Current state:', openItems.has(itemId));
1532
1551
 
1533
1552
  if (openItems.has(itemId)) {
1534
1553
  openItems.delete(itemId);
@@ -1548,11 +1567,11 @@
1548
1567
  openItems = new Set(openItems);
1549
1568
  previouslyOpen = new Set(previouslyOpen);
1550
1569
 
1551
- console.log('โœ… New state:', {
1552
- isOpen: openItems.has(itemId),
1553
- openItems: Array.from(openItems),
1554
- previouslyOpen: Array.from(previouslyOpen)
1555
- });
1570
+ // console.log('โœ… New state:', {
1571
+ // isOpen: openItems.has(itemId),
1572
+ // openItems: Array.from(openItems),
1573
+ // previouslyOpen: Array.from(previouslyOpen)
1574
+ // });
1556
1575
  }
1557
1576
 
1558
1577
  /** Returns the index of a content item by its id, or -1 if not found */
@@ -1617,25 +1636,25 @@
1617
1636
  * Controls input focus behavior and tab switching
1618
1637
  */
1619
1638
  function focusController(controllerId: string) {
1620
- console.log('Attempting to focus controller:', controllerId);
1639
+ // console.log('Attempting to focus controller:', controllerId);
1621
1640
  activeControllerId = controllerId;
1622
1641
  const element = document.getElementById(controllerId);
1623
1642
  if (element) {
1624
1643
  const componentInstance = element.querySelector(':scope > *');
1625
1644
  if (componentInstance && 'focus' in componentInstance) {
1626
- console.log('Calling focus method on component');
1645
+ // console.log('Calling focus method on component');
1627
1646
  (componentInstance as any).focus();
1628
1647
  } else {
1629
1648
  const input = element.querySelector('input, textarea, [role="slider"], button');
1630
1649
  if (input instanceof HTMLElement) {
1631
- console.log('Found focusable element:', input);
1650
+ // console.log('Found focusable element:', input);
1632
1651
  input.focus();
1633
1652
  } else {
1634
- console.log('No focusable element found in controller');
1653
+ // console.log('No focusable element found in controller');
1635
1654
  }
1636
1655
  }
1637
1656
  } else {
1638
- console.log('Controller element not found:', controllerId);
1657
+ // console.log('Controller element not found:', controllerId);
1639
1658
  }
1640
1659
  }
1641
1660
 
@@ -1660,7 +1679,7 @@
1660
1679
  contentSchema.contentItems.forEach((item: ContentItem) => {
1661
1680
  if (item.id) {
1662
1681
  openItems.add(item.id);
1663
- console.log("Added content item:", item.id);
1682
+ // console.log("Added content item:", item.id);
1664
1683
  }
1665
1684
  });
1666
1685
  } else {
@@ -1712,74 +1731,75 @@
1712
1731
  * and appends it to the content items list
1713
1732
  */
1714
1733
  function formatDefaultItemTitle(itemNumber: number): string {
1715
- if (itemNumber < 10) return `Item 0${itemNumber}`;
1716
- return `Item ${itemNumber}`;
1734
+ return formatNumberedTitle('Item', itemNumber);
1717
1735
  }
1718
1736
 
1719
- function formatSlideTitle(slideNumber: number): string {
1720
- return `Slide ${slideNumber}`;
1737
+ /** "Tile 1", "Photo 12", "Item 03" โ†’ prefix used for the next auto title. */
1738
+ function getNumberedTitlePrefix(title: string | undefined): string | null {
1739
+ if (!title?.trim()) return null;
1740
+ const match = title.trim().match(/^(.+?)\s+(\d+)$/);
1741
+ return match?.[1] ?? null;
1721
1742
  }
1722
1743
 
1723
- function formatPhotoTitle(photoNumber: number): string {
1724
- return `Photo ${photoNumber}`;
1725
- }
1726
-
1727
- function isAutoGeneratedItemTitle(title: string | undefined): boolean {
1728
- if (!title?.trim()) return true;
1729
- const trimmed = title.trim();
1744
+ function isKnownGenericAutoTitle(title: string): boolean {
1730
1745
  return (
1731
- /^Item 0?\d+$/i.test(trimmed) ||
1732
- /^Slide \d+$/i.test(trimmed) ||
1733
- /^Photo \d+$/i.test(trimmed)
1746
+ /^Item 0?\d+$/i.test(title) ||
1747
+ /^Slide \d+$/i.test(title) ||
1748
+ /^Photo \d+$/i.test(title) ||
1749
+ /^Image \d+$/i.test(title)
1734
1750
  );
1735
1751
  }
1736
1752
 
1737
- function isSlideAutoTitle(title: string | undefined): boolean {
1738
- return !!title?.trim() && /^Slide \d+$/i.test(title.trim());
1739
- }
1740
-
1741
- function isPhotoAutoTitle(title: string | undefined): boolean {
1742
- return !!title?.trim() && /^Photo \d+$/i.test(title.trim());
1753
+ /** Most common "Name N" prefix in the list (Tile / Photo / Slide / Item / โ€ฆ). */
1754
+ function detectDominantTitlePrefix(items: ContentItem[]): string {
1755
+ const counts = new Map<string, number>();
1756
+ for (const item of items) {
1757
+ const prefix = getNumberedTitlePrefix(item.title);
1758
+ if (!prefix) continue;
1759
+ counts.set(prefix, (counts.get(prefix) || 0) + 1);
1760
+ }
1761
+ let bestPrefix = 'Item';
1762
+ let bestCount = 0;
1763
+ for (const [prefix, count] of counts) {
1764
+ if (count > bestCount) {
1765
+ bestPrefix = prefix;
1766
+ bestCount = count;
1767
+ }
1768
+ }
1769
+ return bestPrefix;
1743
1770
  }
1744
1771
 
1745
- function formatAutoItemTitle(title: string | undefined, position: number): string {
1746
- if (isSlideAutoTitle(title)) return formatSlideTitle(position);
1747
- if (isPhotoAutoTitle(title)) return formatPhotoTitle(position);
1748
- return formatDefaultItemTitle(position);
1772
+ function formatNumberedTitle(prefix: string, itemNumber: number): string {
1773
+ if (/^item$/i.test(prefix) && itemNumber < 10) return `Item 0${itemNumber}`;
1774
+ return `${prefix} ${itemNumber}`;
1749
1775
  }
1750
1776
 
1751
- type AutoTitlePattern = 'photo' | 'slide' | 'item';
1752
-
1753
- /** Pick Photo / Slide / Item naming from the dominant pattern in existing items. */
1754
- function detectDominantAutoTitlePattern(items: ContentItem[]): AutoTitlePattern {
1755
- let photoCount = 0;
1756
- let slideCount = 0;
1757
- let itemCount = 0;
1758
-
1759
- for (const item of items) {
1760
- const title = item.title?.trim() || '';
1761
- if (/^Photo \d+$/i.test(title)) photoCount++;
1762
- else if (/^Slide \d+$/i.test(title)) slideCount++;
1763
- else if (/^Item 0?\d+$/i.test(title) || !title) itemCount++;
1777
+ function isAutoGeneratedItemTitle(title: string | undefined, items?: ContentItem[]): boolean {
1778
+ if (!title?.trim()) return true;
1779
+ const trimmed = title.trim();
1780
+ if (isKnownGenericAutoTitle(trimmed)) return true;
1781
+ if (items && items.length) {
1782
+ const prefix = getNumberedTitlePrefix(trimmed);
1783
+ const dominant = detectDominantTitlePrefix(items);
1784
+ return !!prefix && prefix.toLowerCase() === dominant.toLowerCase();
1764
1785
  }
1786
+ return false;
1787
+ }
1765
1788
 
1766
- if (photoCount > 0 && photoCount >= slideCount && photoCount >= itemCount) return 'photo';
1767
- if (slideCount > 0 && slideCount >= itemCount) return 'slide';
1768
- return 'item';
1789
+ function formatAutoItemTitle(title: string | undefined, position: number): string {
1790
+ const prefix = getNumberedTitlePrefix(title);
1791
+ if (prefix) return formatNumberedTitle(prefix, position);
1792
+ return formatDefaultItemTitle(position);
1769
1793
  }
1770
1794
 
1771
1795
  function formatNewItemTitle(items: ContentItem[]): string {
1772
- const nextNumber = items.length + 1;
1773
- const pattern = detectDominantAutoTitlePattern(items);
1774
- if (pattern === 'photo') return formatPhotoTitle(nextNumber);
1775
- if (pattern === 'slide') return formatSlideTitle(nextNumber);
1776
- return formatDefaultItemTitle(nextNumber);
1796
+ return formatNumberedTitle(detectDominantTitlePrefix(items), items.length + 1);
1777
1797
  }
1778
1798
 
1779
1799
  function renumberAutoItemTitles(items: ContentItem[]): ContentItem[] {
1780
1800
  return items.map((item, index) => {
1781
1801
  const position = index + 1;
1782
- if (!isAutoGeneratedItemTitle(item.title)) {
1802
+ if (!isAutoGeneratedItemTitle(item.title, items)) {
1783
1803
  return { ...item, orderNumber: position };
1784
1804
  }
1785
1805
  const nextTitle = formatAutoItemTitle(item.title, position);
@@ -1801,10 +1821,11 @@
1801
1821
  }
1802
1822
  }
1803
1823
  }
1804
- if (item.title && item.title.trim() && !isAutoGeneratedItemTitle(item.title)) {
1824
+ const itemList = Array.isArray(contentSchema?.contentItems) ? contentSchema.contentItems : [];
1825
+ if (item.title && item.title.trim() && !isAutoGeneratedItemTitle(item.title, itemList)) {
1805
1826
  return item.title;
1806
1827
  }
1807
- if (isAutoGeneratedItemTitle(item.title)) {
1828
+ if (isAutoGeneratedItemTitle(item.title, itemList)) {
1808
1829
  return formatAutoItemTitle(item.title, index + 1);
1809
1830
  }
1810
1831
  return formatDefaultItemTitle(index + 1);
@@ -1860,7 +1881,7 @@
1860
1881
  return;
1861
1882
  }
1862
1883
  // Log before add
1863
- console.log('[Add] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
1884
+ // console.log('[Add] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
1864
1885
  // Generate a unique ID for the new item
1865
1886
  let uniqueId: string;
1866
1887
  do {
@@ -1868,13 +1889,20 @@
1868
1889
  } while (contentSchema.contentItems.some((item: ContentItem) => item.id === uniqueId));
1869
1890
  const newTitle = formatNewItemTitle(contentSchema.contentItems);
1870
1891
  // Create the new item (copy template or last item structure)
1871
- const template = contentSchema.contentItems[0];
1892
+ const template =
1893
+ (Array.isArray(contentSchema.contentItems) &&
1894
+ contentSchema.contentItems.find((item: ContentItem) => Array.isArray(item.properties) && item.properties.length > 0)) ||
1895
+ itemTemplate;
1896
+ if (!template || !Array.isArray(template.properties) || template.properties.length === 0) {
1897
+ console.warn('[Add] Cannot add item: template has no properties');
1898
+ return;
1899
+ }
1872
1900
  const newItem: ContentItem = {
1873
1901
  ...template,
1874
1902
  id: uniqueId,
1875
1903
  title: newTitle,
1876
1904
  orderNumber: contentSchema.contentItems.length + 1,
1877
- properties: template.properties.map((p: any) => ({ ...p })),
1905
+ properties: cloneProperties(template.properties),
1878
1906
  };
1879
1907
  // Remove the new item if it exists elsewhere (shouldn't, but for safety)
1880
1908
  const nextItems = [...contentSchema.contentItems.filter((i: ContentItem) => i.id !== uniqueId), newItem];
@@ -1896,8 +1924,8 @@
1896
1924
  dispatch('goToItem', { index: newItemIndex });
1897
1925
  }
1898
1926
  // Log after add
1899
- console.log('[Add] After (should be at end):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
1900
- console.log('[Add] New item added:', newItem);
1927
+ // console.log('[Add] After (should be at end):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
1928
+ // console.log('[Add] New item added:', newItem);
1901
1929
 
1902
1930
  // Ensure DOM order matches array order after adding new item
1903
1931
  setTimeout(() => {
@@ -1934,20 +1962,47 @@
1934
1962
  }
1935
1963
  }
1936
1964
 
1937
- // Helper function to clone properties
1965
+ function unwrapContentMediaValue(raw: unknown): unknown {
1966
+ if (raw && typeof raw === 'object' && 'url' in (raw as Record<string, unknown>)) {
1967
+ return (raw as { url?: string }).url ?? '';
1968
+ }
1969
+ return raw;
1970
+ }
1971
+
1972
+ function pickLiveContentValue(...candidates: unknown[]): unknown {
1973
+ for (const candidate of candidates) {
1974
+ const unwrapped = unwrapContentMediaValue(candidate);
1975
+ if (unwrapped === undefined || unwrapped === null || unwrapped === '') continue;
1976
+ return unwrapped;
1977
+ }
1978
+ return '';
1979
+ }
1980
+
1981
+ function emptyValueForNewItemProperty(prop: any): unknown {
1982
+ const icType = String(prop?.inputController?.type || '').toLowerCase();
1983
+ if (['browser', 'image', 'file', 'video', 'audio'].includes(icType)) {
1984
+ return '';
1985
+ }
1986
+ if (typeof prop?.defaultValue === 'boolean' || typeof prop?.defaultValue === 'number') {
1987
+ return prop.defaultValue;
1988
+ }
1989
+ return '';
1990
+ }
1991
+
1992
+ // Clone controller schema for a new item. Do not copy Unsplash/sample defaults
1993
+ // into value โ€” those overwrite live gallery URLs in the preview overlay.
1938
1994
  function cloneProperties(properties: Array<any>): Property[] {
1939
1995
  if (!properties || !Array.isArray(properties)) {
1940
1996
  return [];
1941
1997
  }
1942
1998
 
1943
1999
  return properties.map(prop => {
1944
- // Create a deep clone of each property
1945
2000
  const clonedProp: Property = {
1946
2001
  id: prop.id || prop.name,
1947
2002
  name: prop.name || prop.id,
1948
2003
  type: prop.type || 'string', // Ensure type is set
1949
2004
  defaultValue: prop.defaultValue,
1950
- value: prop.defaultValue, // Reset to default value
2005
+ value: emptyValueForNewItemProperty(prop),
1951
2006
  inputController: {
1952
2007
  type: prop.inputController?.type || 'text', // Ensure inputController.type is set
1953
2008
  description: prop.inputController?.description,
@@ -1979,8 +2034,13 @@
1979
2034
  const key = p.id || p.name;
1980
2035
  // Canonical state first; schema mirror/defaultValue are legacy
1981
2036
  // seeds for keys the editor never touched.
2037
+ const liveOnItem = key != null ? (item as Record<string, unknown>)[key] : undefined;
1982
2038
  props[key] = normalizeContentPropertyValue(
1983
- contentState[`${item.id}__${key}`] ?? p.value ?? p.defaultValue
2039
+ pickLiveContentValue(
2040
+ contentState[`${item.id}__${key}`],
2041
+ p.value,
2042
+ liveOnItem
2043
+ )
1984
2044
  );
1985
2045
  });
1986
2046
  // Carry opacity state so parent saves don't lose per-property
@@ -1995,7 +2055,7 @@
1995
2055
  }
1996
2056
  return { id: item.id, title: item.title, ...props };
1997
2057
  });
1998
- console.log('[PropsEditor] Dispatching contentItemsChanged:', serialized.length, 'items');
2058
+ // console.log('[PropsEditor] Dispatching contentItemsChanged:', serialized.length, 'items');
1999
2059
  dispatch('contentItemsChanged', { items: serialized });
2000
2060
  }
2001
2061
 
@@ -2012,7 +2072,7 @@
2012
2072
  }
2013
2073
 
2014
2074
  // Log before deletion
2015
- console.log('[Delete] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
2075
+ // console.log('[Delete] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
2016
2076
  const deletedIndex = contentSchema.contentItems.findIndex((i: ContentItem) => i.id === itemId);
2017
2077
  const initialLength = contentSchema.contentItems.length;
2018
2078
  contentSchema.contentItems = contentSchema.contentItems.filter((i: ContentItem) => i.id !== itemId);
@@ -2024,7 +2084,7 @@
2024
2084
  console.warn('๐Ÿค” No item found with ID:', itemId);
2025
2085
  } else {
2026
2086
  // Log after deletion
2027
- console.log('[Delete] After:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
2087
+ // console.log('[Delete] After:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
2028
2088
  // Notify parent about the updated items list
2029
2089
  dispatch('updated', { contentSchema });
2030
2090
  dispatchContentItemsChanged(contentSchema.contentItems);
@@ -2034,7 +2094,7 @@
2034
2094
  dispatch('goToItem', { index: targetIndex });
2035
2095
  }
2036
2096
  setTimeout(() => {
2037
- console.log('๐Ÿ”„ Reinitializing draggable after deletion');
2097
+ // console.log('๐Ÿ”„ Reinitializing draggable after deletion');
2038
2098
  initializeDraggable();
2039
2099
  }, 0);
2040
2100
  }
@@ -2099,13 +2159,13 @@
2099
2159
  // Initialize the correct tab based on schema availability
2100
2160
  const initialTab = getActiveTab();
2101
2161
  if (initialTab === 'content' && contentSchema) {
2102
- console.log('๐ŸŽฏ Initializing content tab as default');
2162
+ // console.log('๐ŸŽฏ Initializing content tab as default');
2103
2163
  currentTab = 'content';
2104
2164
 
2105
2165
  // Wait for DOM to be ready
2106
2166
  setTimeout(async () => {
2107
2167
  try {
2108
- console.log('๐Ÿ”„ Initializing draggable for content tab');
2168
+ // console.log('๐Ÿ”„ Initializing draggable for content tab');
2109
2169
  await initializeDraggable();
2110
2170
 
2111
2171
  // Set initial states
@@ -2180,7 +2240,7 @@
2180
2240
  const newTab = getActiveTab();
2181
2241
  if (defaultTab !== newTab) {
2182
2242
  defaultTab = newTab;
2183
- console.log('๐Ÿ“‘ [PropsEditor] Updated defaultTab:', { from: defaultTab, to: newTab, hasDesignSchema: !!designSchema, hasContentSchema: !!contentSchema });
2243
+ // console.log('๐Ÿ“‘ [PropsEditor] Updated defaultTab:', { from: defaultTab, to: newTab, hasDesignSchema: !!designSchema, hasContentSchema: !!contentSchema });
2184
2244
  }
2185
2245
  }
2186
2246
 
@@ -2252,7 +2312,7 @@
2252
2312
 
2253
2313
  // Add a reactive log after render to show the current array order and titles
2254
2314
  $: if (contentSchema && Array.isArray(contentSchema?.contentItems)) {
2255
- console.log('[Render] Current contentItems order:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
2315
+ // console.log('[Render] Current contentItems order:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
2256
2316
  }
2257
2317
 
2258
2318
  /** Safe accessor โ€” always returns an array even when contentSchema.contentItems is missing or not an array */
@@ -2268,7 +2328,7 @@
2268
2328
  // Helper: log DOM visual order for debugging
2269
2329
  function logOrder(label: string) {
2270
2330
  const titles = Array.from(document.querySelectorAll('.content-item .content-title')).map(el => el.textContent?.trim());
2271
- console.log(`[Order-check] ${label}:`, titles);
2331
+ // console.log(`[Order-check] ${label}:`, titles);
2272
2332
  const transforms = Array.from(document.querySelectorAll('.content-item')).map(el => ({ id: (el as HTMLElement).dataset.id, transform: (el as HTMLElement).style.transform }));
2273
2333
  console.table(transforms);
2274
2334
  }
@@ -2337,9 +2397,9 @@
2337
2397
  >
2338
2398
  <Tabs.Trigger
2339
2399
  value="design"
2340
- class="body flex-1 h-full rounded-[11px] transition-colors duration-250 text-grey5 hover:text-grey7 hover:underline data-[state=active]:bg-white data-[state=active]:text-grey7 data-[state=active]:hover:text-grey7"
2400
+ class="flex-1 h-full rounded-[11px] transition-colors duration-250 text-grey5 hover:text-grey7 hover:underline data-[state=active]:bg-white data-[state=active]:text-grey7 data-[state=active]:hover:text-grey7"
2341
2401
  >
2342
- {designTabTitle}
2402
+ <span class="body">{designTabTitle}</span>
2343
2403
  </Tabs.Trigger>
2344
2404
  {#if contentSchema}
2345
2405
  <Tabs.Trigger
@@ -2347,9 +2407,9 @@
2347
2407
  initializeDraggable();
2348
2408
  }}
2349
2409
  value="content"
2350
- class="body flex-1 h-full rounded-[11px] transition-colors duration-250 text-grey5 hover:text-grey7 hover:underline data-[state=active]:bg-white data-[state=active]:text-grey7 data-[state=active]:hover:text-grey7"
2410
+ class="flex-1 h-full rounded-[11px] transition-colors duration-250 text-grey5 hover:text-grey7 hover:underline data-[state=active]:bg-white data-[state=active]:text-grey7 data-[state=active]:hover:text-grey7"
2351
2411
  >
2352
- {contentTabTitle}
2412
+ <span class="body">{contentTabTitle}</span>
2353
2413
  </Tabs.Trigger>
2354
2414
  {/if}
2355
2415
  </Tabs.List>
@@ -2415,24 +2475,27 @@
2415
2475
  {@const reactiveValue = designState[stateKey]}
2416
2476
 
2417
2477
  {#if isVisible}
2478
+ {@const isFontIc = property.inputController.type.toLowerCase() === 'font'}
2418
2479
  <div
2419
- class="ictrl input-controller flex items-center justify-between w-full p-0"
2480
+ class="ictrl input-controller flex {isFontIc ? 'font-ic-host flex-col items-stretch' : 'items-center justify-between'} w-full p-0"
2420
2481
  data-state={$openDropdowns?.has(props.controllerId) ? 'open' : 'closed'}
2421
- class:double-height={property.inputController.type.toLowerCase() === 'font'}
2482
+ class:double-height={isFontIc}
2422
2483
  class:position-height={property.inputController.type.toLowerCase() === 'position'}
2423
2484
  class:fixed-position-height={property.inputController.type.toLowerCase() === 'fixedposition'}
2424
2485
  class:text-area-preview-height={property.inputController.type.toLowerCase() === 'textarea' || property.inputController.type.toLowerCase() === 'textArea'}
2425
2486
  class:position-controller={property.inputController.type.toLowerCase() === 'position'}
2426
2487
  class:position-dragging={property.inputController.type.toLowerCase() === 'position' && isDragging}
2427
2488
  >
2489
+ {#if !isFontIc}
2428
2490
  <div class="ic-header-ct flex-1 min-w-0">
2429
2491
  <InputControllerHeader
2430
2492
  label={header.label}
2431
2493
  helpText={header.helpText}
2432
2494
  />
2433
2495
  </div>
2496
+ {/if}
2434
2497
 
2435
- <div class="ic-input-ct flex-shrink-0 ml-auto" style="width: 185px;">
2498
+ <div class="ic-input-ct {isFontIc ? 'w-full' : 'flex-shrink-0 ml-auto'}" style={isFontIc ? '' : 'width: 185px;'}>
2436
2499
  <svelte:component
2437
2500
  this={InputComponent}
2438
2501
  {...props}
@@ -2509,6 +2572,7 @@
2509
2572
  {/if}
2510
2573
 
2511
2574
  <Collapsible.Content>
2575
+ {#if isGenericContentOpen}
2512
2576
  <Card.Content class="p-0 px-4">
2513
2577
  <div class="pt-[14px] pb-[16px]">
2514
2578
  <div class="properties-list flex flex-col" style="gap: 8px;">
@@ -2525,17 +2589,16 @@
2525
2589
  {@const isVisible = shouldShowProperty(property, contentSchema?.genericContent?.properties || [], 'content')}
2526
2590
 
2527
2591
  {#if isVisible}
2528
- <div class="ictrl input-controller flex items-center justify-between w-full p-0 browser-input-wrapper"
2529
- class:double-height={property.inputController.type.toLowerCase() === 'font'}
2592
+ {@const isFontIc = property.inputController.type.toLowerCase() === 'font'}
2593
+ <div class="ictrl input-controller flex {isFontIc ? 'font-ic-host flex-col items-stretch' : 'items-center justify-between'} w-full p-0 browser-input-wrapper"
2594
+ class:double-height={isFontIc}
2530
2595
  >
2531
- <!-- Left side - Label -->
2532
- {#if InputComponent !== BrowserInputController}
2596
+ {#if !isFontIc && InputComponent !== BrowserInputController}
2533
2597
  <InputControllerHeader
2534
2598
  label={header.label}
2535
2599
  helpText={header.helpText}
2536
2600
  />
2537
- {:else}
2538
- <!-- Special case for BrowserInputController -->
2601
+ {:else if !isFontIc}
2539
2602
  <div class="flex-1 min-w-0 pr-4">
2540
2603
  <InputControllerHeader
2541
2604
  label={header.label}
@@ -2544,8 +2607,7 @@
2544
2607
  </div>
2545
2608
  {/if}
2546
2609
 
2547
- <!-- Right side - Input Component -->
2548
- <div class="ic-input-ct flex-shrink-0" style="width: 185px;">
2610
+ <div class="ic-input-ct {isFontIc ? 'w-full' : 'flex-shrink-0'}" style={isFontIc ? '' : 'width: 185px;'}>
2549
2611
  <svelte:component
2550
2612
  this={InputComponent}
2551
2613
  {...props}
@@ -2559,6 +2621,7 @@
2559
2621
  </div>
2560
2622
  </div>
2561
2623
  </Card.Content>
2624
+ {/if}
2562
2625
  </Collapsible.Content>
2563
2626
  </Collapsible.Root>
2564
2627
  </Card.Root>
@@ -2609,6 +2672,7 @@
2609
2672
  {/if}
2610
2673
 
2611
2674
  <Collapsible.Content>
2675
+ {#if isItemOpen(item.id)}
2612
2676
  <Card.Content class="p-0 px-4">
2613
2677
  <div class="pt-[14px] pb-[16px]">
2614
2678
  <div class="properties-list flex flex-col" style="gap: 8px;">
@@ -2625,18 +2689,17 @@
2625
2689
  {@const header = result.header}
2626
2690
  {@const isVisible = shouldShowProperty(property, item.properties, 'content')}
2627
2691
 
2628
- {#if isVisible}
2629
- <div class="ictrl input-controller flex items-center justify-between w-full p-0 browser-input-wrapper"
2630
- class:double-height={property.inputController.type.toLowerCase() === 'font'}
2692
+ {#if isVisible && InputComponent}
2693
+ {@const isFontIc = (property.inputController?.type || '').toLowerCase() === 'font'}
2694
+ <div class="ictrl input-controller flex {isFontIc ? 'font-ic-host flex-col items-stretch' : 'items-center justify-between'} w-full p-0 browser-input-wrapper"
2695
+ class:double-height={isFontIc}
2631
2696
  >
2632
- <!-- Left side - Label -->
2633
- {#if InputComponent !== BrowserInputController}
2697
+ {#if !isFontIc && InputComponent !== BrowserInputController}
2634
2698
  <InputControllerHeader
2635
2699
  label={header.label}
2636
2700
  helpText={header.helpText}
2637
2701
  />
2638
- {:else}
2639
- <!-- Special case for BrowserInputController -->
2702
+ {:else if !isFontIc}
2640
2703
  <div class="flex-1 min-w-0 pr-4">
2641
2704
  <InputControllerHeader
2642
2705
  label={header.label}
@@ -2645,8 +2708,7 @@
2645
2708
  </div>
2646
2709
  {/if}
2647
2710
 
2648
- <!-- Right side - Input Component -->
2649
- <div class="ic-input-ct flex-shrink-0" style="width: 185px;">
2711
+ <div class="ic-input-ct {isFontIc ? 'w-full' : 'flex-shrink-0'}" style={isFontIc ? '' : 'width: 185px;'}>
2650
2712
  <svelte:component
2651
2713
  this={InputComponent}
2652
2714
  {...props}
@@ -2690,6 +2752,7 @@
2690
2752
  </div>
2691
2753
  </div>
2692
2754
  </Card.Content>
2755
+ {/if}
2693
2756
  </Collapsible.Content>
2694
2757
  </Collapsible.Root>
2695
2758
  </Card.Root>
@@ -3283,6 +3346,23 @@
3283
3346
  flex-shrink: 0 !important;
3284
3347
  }
3285
3348
 
3349
+ /* FontIC is two stacked 185px rows with its own labels. Do not squeeze
3350
+ the whole controller into the single 185px color column (that hid labels). */
3351
+ :global(.input-controller.font-ic-host) {
3352
+ display: flex !important;
3353
+ flex-direction: column !important;
3354
+ align-items: stretch !important;
3355
+ justify-content: flex-start !important;
3356
+ }
3357
+
3358
+ :global(.input-controller.font-ic-host > div:first-child),
3359
+ :global(.input-controller.font-ic-host > div:last-child) {
3360
+ width: 100% !important;
3361
+ max-width: 100% !important;
3362
+ flex: none !important;
3363
+ margin-right: 0 !important;
3364
+ }
3365
+
3286
3366
  /* New animation system */
3287
3367
  :global(.accordion-content) {
3288
3368
  overflow: hidden;
@@ -3370,7 +3450,9 @@
3370
3450
 
3371
3451
  /* Specific style for font controllers */
3372
3452
  :global(.input-controller.double-height) {
3373
- height: 104px !important;
3453
+ height: auto !important;
3454
+ min-height: 104px;
3455
+ align-items: flex-start !important;
3374
3456
  }
3375
3457
 
3376
3458
  /* Ensure content containers maintain width */