@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
@@ -77,8 +77,11 @@
77
77
  * @prop {Function} toggleDropdown - Function to toggle dropdown state
78
78
  * @prop {Function} onFocus - Optional focus handler
79
79
  */
80
- export let label: string;
81
- export let value: { family: string; weight: string; category: string } | null = null;
80
+ export let label: string = '';
81
+ let className = '';
82
+ export { className as class };
83
+ export let style = '';
84
+ export let value: { family: string; weight: string; category: string } | string | null = null;
82
85
  export let onChange: ((newValue: { family: string; weight: string; category: string }) => void) | undefined = undefined;
83
86
  export let description: string | undefined = undefined;
84
87
  export let controllerId: string;
@@ -91,7 +94,7 @@
91
94
 
92
95
  // Font weight state - remove localStorage check on init
93
96
  let selectedWeight = `${controllerId}-Regular`;
94
- let availableWeights: string[] = [];
97
+ let availableWeights: string[] = ['Regular', 'Medium', 'SemiBold', 'Bold'];
95
98
 
96
99
  // Font search and filter state
97
100
  let allFonts: FontItem[] = [];
@@ -103,6 +106,9 @@
103
106
 
104
107
  // Font loading optimization
105
108
  let fontListContainer: HTMLElement;
109
+ let familyTriggerEl: HTMLDivElement | undefined;
110
+ let familyPopoverStyle = '';
111
+ let familyPopoverSide: 'top' | 'bottom' = 'bottom';
106
112
  let fontObserver = new IntersectionObserver(() => {});
107
113
 
108
114
  // Initialize FontManager instance
@@ -120,6 +126,46 @@
120
126
  export let hideRecentFromList: boolean = false;
121
127
  export let hideTrashIcon: boolean = true;
122
128
 
129
+ function updateFamilyPopoverBox() {
130
+ if (!familyTriggerEl || typeof window === 'undefined') return;
131
+ const rect = familyTriggerEl.getBoundingClientRect();
132
+ const pad = 12;
133
+ const viewportH = window.visualViewport?.height ?? window.innerHeight;
134
+ const below = viewportH - rect.bottom - pad;
135
+ const above = rect.top - pad;
136
+ const openAbove = above > below;
137
+ familyPopoverSide = openAbove ? 'top' : 'bottom';
138
+ const available = Math.max(0, openAbove ? above : below);
139
+ const height = Math.min(565, available);
140
+ const width = Math.min(374, Math.max(240, window.innerWidth - 16));
141
+ familyPopoverStyle = `width: ${width}px; height: ${height}px; max-width: calc(100vw - 16px); max-height: ${height}px;`;
142
+ }
143
+
144
+ function handleFamilyPopoverViewportChange() {
145
+ if ($openDropdowns?.has(controllerId)) {
146
+ updateFamilyPopoverBox();
147
+ }
148
+ }
149
+
150
+ function coerceFontValue(raw: typeof value): { family: string; weight: string; category: string } | null {
151
+ if (!raw) return null;
152
+ if (typeof raw === 'object' && raw.family) {
153
+ return {
154
+ family: raw.family,
155
+ weight: raw.weight || 'Regular',
156
+ category: raw.category || 'Sans Serif',
157
+ };
158
+ }
159
+ if (typeof raw === 'string' && raw.trim()) {
160
+ const family = raw.split(',')[0].replace(/['"]/g, '').trim();
161
+ if (!family) return null;
162
+ return { family, weight: 'Regular', category: 'Sans Serif' };
163
+ }
164
+ return null;
165
+ }
166
+
167
+ $: resolvedFont = coerceFontValue(value);
168
+
123
169
  // Add scroll state tracking
124
170
  let isScrolled = false;
125
171
 
@@ -188,9 +234,12 @@
188
234
  onMount(async () => {
189
235
  try {
190
236
  if (browser) {
191
- // If we have an initial value, use its weight
192
- if (value?.weight) {
193
- selectedWeight = `${controllerId}-${value.weight}`;
237
+ const coerced = coerceFontValue(value);
238
+ if (coerced?.weight) {
239
+ selectedWeight = `${controllerId}-${coerced.weight}`;
240
+ }
241
+ if (typeof value === 'string' && coerced && onChange) {
242
+ onChange(coerced);
194
243
  }
195
244
 
196
245
  // Load saved filter
@@ -328,13 +377,13 @@
328
377
  preloadedFonts = fontManagerInstance.getPreloadedFonts('Mine');
329
378
 
330
379
  if (preloadedFonts.length > 0) {
331
- console.log(`✅ [FontInput:${controllerId}] Using preloaded custom fonts for Mine category:`, {
332
- count: preloadedFonts.length,
333
- fonts: preloadedFonts.slice(0, 3).map(f => f.family)
334
- });
380
+ // console.log(`✅ [FontInput:${controllerId}] Using preloaded custom fonts for Mine category:`, {
381
+ // count: preloadedFonts.length,
382
+ // fonts: preloadedFonts.slice(0, 3).map(f => f.family)
383
+ // });
335
384
  } else {
336
385
  // If no preloaded fonts found, load them now
337
- console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
386
+ // console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
338
387
  preloadedFonts = await fontManagerInstance.loadCustomFonts();
339
388
  }
340
389
 
@@ -399,13 +448,13 @@
399
448
  initialFonts = fontManagerInstance.getPreloadedFonts('Mine');
400
449
 
401
450
  if (initialFonts.length > 0) {
402
- console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts:`, {
403
- count: initialFonts.length,
404
- fonts: initialFonts.slice(0, 3).map(f => f.family)
405
- });
451
+ // console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts:`, {
452
+ // count: initialFonts.length,
453
+ // fonts: initialFonts.slice(0, 3).map(f => f.family)
454
+ // });
406
455
  } else {
407
456
  // If no preloaded fonts, load them now
408
- console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
457
+ // console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
409
458
  initialFonts = await fontManagerInstance.loadCustomFonts();
410
459
  }
411
460
  } else if (currentFilter === 'All') {
@@ -515,14 +564,14 @@
515
564
  const preloadedCustomFonts = fontManagerInstance.getPreloadedFonts('Mine');
516
565
 
517
566
  if (preloadedCustomFonts.length > 0) {
518
- console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts in initializeFonts:`, {
519
- count: preloadedCustomFonts.length,
520
- fonts: preloadedCustomFonts.slice(0, 3).map(f => f.family)
521
- });
567
+ // console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts in initializeFonts:`, {
568
+ // count: preloadedCustomFonts.length,
569
+ // fonts: preloadedCustomFonts.slice(0, 3).map(f => f.family)
570
+ // });
522
571
  return preloadedCustomFonts;
523
572
  } else {
524
573
  // If no preloaded fonts, load them now
525
- console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found in initializeFonts, loading them now...`);
574
+ // console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found in initializeFonts, loading them now...`);
526
575
  const customFonts = await fontManagerInstance.loadCustomFonts();
527
576
  return customFonts;
528
577
  }
@@ -892,16 +941,16 @@
892
941
 
893
942
  if (preloadedCustomFonts.length > 0) {
894
943
  // Use preloaded custom fonts
895
- console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts for empty search:`, {
896
- count: preloadedCustomFonts.length,
897
- fonts: preloadedCustomFonts.slice(0, 3).map(f => f.family)
898
- });
944
+ // console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts for empty search:`, {
945
+ // count: preloadedCustomFonts.length,
946
+ // fonts: preloadedCustomFonts.slice(0, 3).map(f => f.family)
947
+ // });
899
948
  allFonts = preloadedCustomFonts;
900
949
  filteredFonts = [];
901
950
  updateFilteredFonts();
902
951
  } else {
903
952
  // If no preloaded fonts, load them asynchronously
904
- console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found for empty search, loading them now...`);
953
+ // console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found for empty search, loading them now...`);
905
954
  fontManagerInstance.loadCustomFonts().then(customFonts => {
906
955
  allFonts = customFonts || [];
907
956
  filteredFonts = [];
@@ -999,13 +1048,13 @@
999
1048
 
1000
1049
  if (fonts.length === 0) {
1001
1050
  // If no preloaded custom fonts found, load them
1002
- console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
1051
+ // console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
1003
1052
  fonts = await fontManagerInstance.loadCustomFonts();
1004
1053
  } else {
1005
- console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts:`, {
1006
- count: fonts.length,
1007
- fonts: fonts.slice(0, 3).map(f => f.family)
1008
- });
1054
+ // console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts:`, {
1055
+ // count: fonts.length,
1056
+ // fonts: fonts.slice(0, 3).map(f => f.family)
1057
+ // });
1009
1058
  }
1010
1059
  } else {
1011
1060
  // For other categories, first try to get preloaded fonts
@@ -1368,8 +1417,8 @@
1368
1417
  }
1369
1418
  }
1370
1419
 
1371
- $: if (value?.family) {
1372
- fetchFontWeights(value.family);
1420
+ $: if (resolvedFont?.family) {
1421
+ fetchFontWeights(resolvedFont.family);
1373
1422
  }
1374
1423
 
1375
1424
  /**
@@ -1453,6 +1502,8 @@
1453
1502
  capture: true,
1454
1503
  passive: true
1455
1504
  });
1505
+ window.addEventListener('resize', handleFamilyPopoverViewportChange);
1506
+ window.visualViewport?.addEventListener('resize', handleFamilyPopoverViewportChange);
1456
1507
 
1457
1508
  // Add touch listeners with passive option
1458
1509
  window.addEventListener('touchstart', () => {}, { passive: true });
@@ -1462,6 +1513,8 @@
1462
1513
  // Update onDestroy to remove all listeners
1463
1514
  onDestroy(() => {
1464
1515
  window.removeEventListener('scroll', handleGlobalScroll, true);
1516
+ window.removeEventListener('resize', handleFamilyPopoverViewportChange);
1517
+ window.visualViewport?.removeEventListener('resize', handleFamilyPopoverViewportChange);
1465
1518
  window.removeEventListener('touchstart', () => {});
1466
1519
  window.removeEventListener('touchmove', () => {});
1467
1520
 
@@ -1476,10 +1529,10 @@
1476
1529
  clearTimeout(initialLoadTimeout);
1477
1530
  }
1478
1531
 
1479
- console.log('🧹 [FontInput] Cleanup complete:', {
1480
- controllerId,
1481
- lastFilter: currentFilter
1482
- });
1532
+ // console.log('🧹 [FontInput] Cleanup complete:', {
1533
+ // controllerId,
1534
+ // lastFilter: currentFilter
1535
+ // });
1483
1536
  });
1484
1537
 
1485
1538
  // Add this function to the script section
@@ -1530,12 +1583,12 @@
1530
1583
  // Update filtered fonts
1531
1584
  updateFilteredFonts();
1532
1585
 
1533
- console.log('✅ [FontInput] Font removed:', {
1534
- fontFamily,
1535
- remainingFonts: allFonts.length,
1536
- filteredFonts: filteredFonts.length,
1537
- recentFonts: recentFonts.length
1538
- });
1586
+ // console.log('✅ [FontInput] Font removed:', {
1587
+ // fontFamily,
1588
+ // remainingFonts: allFonts.length,
1589
+ // filteredFonts: filteredFonts.length,
1590
+ // recentFonts: recentFonts.length
1591
+ // });
1539
1592
  } catch (error) {
1540
1593
  console.error('❌ [FontInput] Error removing font:', error);
1541
1594
  }
@@ -1547,11 +1600,12 @@
1547
1600
  if (browser) {
1548
1601
  localStorage.setItem(FONT_WEIGHT_KEY(controllerId), selectedWeight);
1549
1602
  }
1603
+ const current = coerceFontValue(value);
1550
1604
  if (onChange) {
1551
1605
  onChange({
1552
- family: value?.family || '',
1606
+ family: current?.family || '',
1553
1607
  weight: weight,
1554
- category: value?.category || ''
1608
+ category: current?.category || 'Sans Serif'
1555
1609
  });
1556
1610
  }
1557
1611
  }
@@ -1587,7 +1641,7 @@
1587
1641
  <div class="flex items-center flex-1 min-w-0">
1588
1642
  <span
1589
1643
  class="text-[24px] truncate"
1590
- style="font-family: '${font.family}', sans-serif; font-display: swap;"
1644
+ style="font-family: '${font.family}', sans-serif; font-display: swap; color: inherit; -webkit-text-fill-color: currentColor;"
1591
1645
  >
1592
1646
  ${font.family}
1593
1647
  </span>
@@ -1597,7 +1651,7 @@
1597
1651
  }
1598
1652
 
1599
1653
  function isFontSelected(font: FontItem) {
1600
- return value?.family === font.family;
1654
+ return resolvedFont?.family === font.family;
1601
1655
  }
1602
1656
 
1603
1657
  function isCustomFont(font: FontItem) {
@@ -1617,34 +1671,34 @@
1617
1671
  }
1618
1672
  </script>
1619
1673
 
1620
- <!-- Font Input Container -->
1621
- <div class="flex flex-col widgetic-colors">
1622
- <!-- Font Family Section -->
1623
- <div class="flex flex-col gap-[8px]">
1624
- <div class="flex items-center w-full relative h-[48px]">
1625
- <div class="w-[185px] flex-shrink-0 ml-auto">
1626
- <InputControllerHeader
1627
- label={label}
1628
- helpText={description}
1629
- />
1630
- </div>
1631
-
1632
- <div class="absolute right-0">
1633
- <div class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green">
1674
+ <!-- Dual IC: header zone always on the left (even if label is empty); 185px inputs on the right. -->
1675
+ <div class="font-ic flex flex-col gap-2 w-full min-w-0 widgetic-colors {className}" style={style}>
1676
+ <div class="font-family-row flex items-center justify-between w-full min-w-0 gap-2" style="min-height: 48px;">
1677
+ <div class="font-family-label-ct flex-1 min-w-0">
1678
+ <InputControllerHeader label={label || ''} />
1679
+ </div>
1680
+ <div
1681
+ class="font-family-input-ct h-12 shrink-0 rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green"
1682
+ style="width: 185px; max-width: 185px; min-width: 185px;"
1683
+ bind:this={familyTriggerEl}
1684
+ >
1634
1685
  <!-- Font Family Button -->
1635
1686
  <Popover.Root
1636
1687
  open={$openDropdowns?.has(controllerId)}
1637
1688
  onOpenChange={(isOpen) => {
1638
1689
  if (isOpen !== $openDropdowns?.has(controllerId)) {
1639
- if (!isOpen && value?.family && !shouldSkipFont(value.family)) {
1690
+ if (!isOpen && resolvedFont?.family && !shouldSkipFont(resolvedFont.family)) {
1640
1691
  fontUtils.addRecentFont({
1641
- family: value.family,
1642
- category: normalizeCategory(value.category) as FontItem['category'],
1643
- isCustom: fontManagerInstance?.isCustomFont(value.family) || false
1692
+ family: resolvedFont.family,
1693
+ category: normalizeCategory(resolvedFont.category) as FontItem['category'],
1694
+ isCustom: fontManagerInstance?.isCustomFont(resolvedFont.family) || false
1644
1695
  });
1645
1696
  } else if (isOpen) {
1646
- // Reset active search index when opening the dropdown
1647
1697
  activeSearchIndex = -1;
1698
+ void tick().then(() => {
1699
+ updateFamilyPopoverBox();
1700
+ requestAnimationFrame(updateFamilyPopoverBox);
1701
+ });
1648
1702
 
1649
1703
  // When opening, ensure we have fonts ready
1650
1704
  if (filteredFonts.length === 0 && allFonts.length > 0) {
@@ -1665,7 +1719,7 @@
1665
1719
  }
1666
1720
  }}
1667
1721
  >
1668
- <Popover.Trigger class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent pl-3 pr-[16px] group"
1722
+ <Popover.Trigger tabindex="-1" class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent pl-3 pr-[16px] group"
1669
1723
  onmouseenter={() => {
1670
1724
  // Preload fonts when hovering over the button
1671
1725
  // This reduces the delay when actually clicking to open
@@ -1676,20 +1730,27 @@
1676
1730
  >
1677
1731
  <span
1678
1732
  class="truncate flex-1 text-left subtext text-grey7"
1679
- style="font-family: '{value?.family || 'system-ui'}', sans-serif; font-display: swap;"
1733
+ style="font-family: '{resolvedFont?.family || 'system-ui'}', sans-serif; font-display: swap;"
1680
1734
  >
1681
- {value?.family || "Select font..."}
1735
+ {resolvedFont?.family || "Select font..."}
1682
1736
  </span>
1683
1737
  <IconCaretDownFilled class="w-5 h-5 shrink-0 text-grey5 group-hover:text-grey7" />
1684
1738
  </Popover.Trigger>
1685
1739
 
1686
- <Popover.Content class="font-input-popover w-[374px] h-[565px] !p-0 widgetic-colors widgetic-radius rounded-medium modal-shadow border border-grey2">
1687
- <div class="flex flex-col h-full font-popover-content">
1740
+ <Popover.Content
1741
+ collisionPadding={12}
1742
+ side={familyPopoverSide}
1743
+ sideOffset={4}
1744
+ class="font-input-popover !p-0 widgetic-colors widgetic-radius rounded-medium modal-shadow border border-grey2"
1745
+ style={familyPopoverStyle}
1746
+ >
1747
+ <div class="flex flex-col h-full min-h-0 font-popover-content">
1688
1748
  <!-- Title and upload icon -->
1689
- <div class="relative pt-6 pb-[11px] px-5">
1749
+ <div class="font-popover-header shrink-0 relative pt-6 pb-[11px] px-5">
1690
1750
  <div class="h5 text-grey7 widgetic-colors">Fonts</div>
1691
1751
  <button
1692
1752
  type="button"
1753
+ tabindex="-1"
1693
1754
  class="absolute right-6 top-[26px] bg-transparent border-0 cursor-pointer p-0 rounded-full hover:bg-grey3 transition-all duration-300"
1694
1755
  onclick={() => {
1695
1756
  try {
@@ -1708,7 +1769,7 @@
1708
1769
  {/if}
1709
1770
 
1710
1771
  <!-- Search input -->
1711
- <div class="relative px-4">
1772
+ <div class="font-popover-search shrink-0 relative px-4">
1712
1773
  <SearchInput
1713
1774
  placeholder="Search fonts..."
1714
1775
  bind:value={searchTerm}
@@ -1733,7 +1794,7 @@
1733
1794
  </div>
1734
1795
  <!-- Filter toggle group - sticky header -->
1735
1796
  {#if !searchTerm}
1736
- <div class="sticky top-0 bg-white z-10">
1797
+ <div class="font-popover-tabs shrink-0 sticky top-0 bg-white z-10">
1737
1798
  <CategoryTabs
1738
1799
  categories={filterOptions}
1739
1800
  activeCategory={currentFilter}
@@ -1772,6 +1833,7 @@
1772
1833
  displayMode="list"
1773
1834
  hideTrashIcon={hideTrashIcon}
1774
1835
  itemType="font"
1836
+ selectionToken={resolvedFont?.family || ''}
1775
1837
  />
1776
1838
  {/if}
1777
1839
 
@@ -1809,22 +1871,23 @@
1809
1871
  {#each filteredFonts as font, index (`${font.family}-${searchTerm ? 'search' : currentFilter}-${index}`)}
1810
1872
  <!-- svelte-ignore a11y_click_events_have_key_events -->
1811
1873
  <div
1812
- class="w-full py-1 px-4 cursor-pointer overflow-hidden relative rounded-small h-[42px] flex items-center
1813
- {value?.family === font.family ? 'bg-sage-green text-white selected-item' :
1814
- index === activeSearchIndex && searchTerm ? 'bg-light-green text-sage-green' :
1874
+ class="font-list-row w-full py-1 px-4 cursor-pointer overflow-hidden relative rounded-small h-[42px] flex items-center
1875
+ {isFontSelected(font) ? 'bg-sage-green selected-item' :
1876
+ index === activeSearchIndex && searchTerm ? 'bg-light-green' :
1815
1877
  'hover:bg-light-green'}"
1816
1878
  data-font={font.family}
1817
1879
  use:observeFont
1818
1880
  onclick={() => handleFontSelect(font)}
1819
1881
  onmouseenter={() => handleMouseEnter(index)}
1820
1882
  role="button"
1821
- tabindex="0"
1883
+ tabindex="-1"
1884
+ style="color: {isFontSelected(font) ? '#ffffff' : 'hsl(var(--grey7))'};"
1822
1885
  >
1823
1886
  <div class="flex items-center flex-1 min-w-0 group">
1824
- <Check class="mr-2 h-6 w-6 flex-shrink-0 {value?.family === font.family ? 'text-white' : 'text-sage-green opacity-0'}"/>
1887
+ <Check class="mr-2 h-6 w-6 flex-shrink-0 {isFontSelected(font) ? 'text-white' : 'text-sage-green opacity-0'}"/>
1825
1888
  <span
1826
1889
  class="text-[24px] truncate {index === activeSearchIndex && searchTerm ? 'font-medium' : ''}"
1827
- style="font-family: '{font.family}', sans-serif; font-display: swap;"
1890
+ style="font-family: '{font.family}', sans-serif; font-display: swap; color: inherit; -webkit-text-fill-color: currentColor;"
1828
1891
  >
1829
1892
  {@html highlightMatch(font.family, searchTerm)}
1830
1893
  </span>
@@ -1861,15 +1924,13 @@
1861
1924
  </Popover.Content>
1862
1925
  </Popover.Root>
1863
1926
  </div>
1864
- </div>
1865
- </div>
1927
+ </div>
1866
1928
 
1867
- <!-- Font Weight Section -->
1868
- <div class="flex items-center w-full relative h-[48px]">
1869
- <div class="w-[185px]"></div>
1870
- <div class="absolute right-0">
1871
- <div class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green">
1872
- <!-- Font Weight Button -->
1929
+ <div class="font-weight-row flex items-center justify-between w-full min-w-0 gap-2" style="min-height: 48px;">
1930
+ <div class="font-weight-label-ct flex-1 min-w-0">
1931
+ <InputControllerHeader label={label ? 'Font Weight' : ''} />
1932
+ </div>
1933
+ <div class="font-weight-input-ct h-12 shrink-0 rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green" style="width: 185px; max-width: 185px; min-width: 185px;">
1873
1934
  <Popover.Root
1874
1935
  open={$openDropdowns?.has('weight-' + controllerId)}
1875
1936
  onOpenChange={(isOpen) => {
@@ -1878,35 +1939,35 @@
1878
1939
  }
1879
1940
  }}
1880
1941
  >
1881
- <Popover.Trigger class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent pl-3 pr-[16px] group">
1942
+ <Popover.Trigger tabindex="-1" class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent pl-3 pr-[16px] group">
1882
1943
  <span class="truncate flex-1 text-left subtext text-grey7">
1883
- {displayWeight}
1944
+ {displayWeight || 'Regular'}
1884
1945
  </span>
1885
1946
  <IconCaretDownFilled class="w-5 h-5 shrink-0 text-grey5 group-hover:text-grey7" />
1886
1947
  </Popover.Trigger>
1887
1948
  <Popover.Content class="p-0 w-[150px]">
1888
1949
  <Command.Root>
1889
- <Command.Group>
1890
- {#each availableWeights as weight}
1891
- <Command.Item
1892
- value={weight}
1893
- onSelect={() => handleWeightSelect(weight)}
1894
- class="px-2 py-2"
1895
- >
1896
- <Check
1897
- class="mr-1 h-4 w-4 {displayWeight === weight ? 'opacity-100' : 'opacity-0'}"
1898
- />
1899
- <span class="truncate">{weight}</span>
1900
- </Command.Item>
1901
- {/each}
1902
- </Command.Group>
1950
+ <Command.List>
1951
+ <Command.Group>
1952
+ {#each availableWeights as weight}
1953
+ <Command.Item
1954
+ value={weight}
1955
+ onSelect={() => handleWeightSelect(weight)}
1956
+ class="px-2 py-2"
1957
+ >
1958
+ <Check
1959
+ class="mr-1 h-4 w-4 {displayWeight === weight ? 'opacity-100' : 'opacity-0'}"
1960
+ />
1961
+ <span class="truncate">{weight}</span>
1962
+ </Command.Item>
1963
+ {/each}
1964
+ </Command.Group>
1965
+ </Command.List>
1903
1966
  </Command.Root>
1904
1967
  </Popover.Content>
1905
1968
  </Popover.Root>
1906
- </div>
1907
1969
  </div>
1908
1970
  </div>
1909
- </div>
1910
1971
 
1911
1972
  {#if description}
1912
1973
  <p class="subtext text-grey5">{description}</p>
@@ -1914,6 +1975,30 @@
1914
1975
  </div>
1915
1976
 
1916
1977
  <style>
1978
+ .font-family-row,
1979
+ .font-weight-row {
1980
+ display: flex;
1981
+ align-items: center;
1982
+ justify-content: space-between;
1983
+ width: 100%;
1984
+ }
1985
+
1986
+ .font-family-label-ct,
1987
+ .font-weight-label-ct {
1988
+ flex: 1 1 auto;
1989
+ min-width: 0;
1990
+ }
1991
+
1992
+ .font-family-input-ct,
1993
+ .font-weight-input-ct {
1994
+ width: 185px !important;
1995
+ max-width: 185px !important;
1996
+ min-width: 185px !important;
1997
+ margin-left: auto;
1998
+ flex-shrink: 0;
1999
+ position: relative;
2000
+ }
2001
+
1917
2002
  /* Update button styles */
1918
2003
  :global(button.bg-transparent) {
1919
2004
  background: transparent !important;
@@ -1932,9 +2017,11 @@
1932
2017
  scrollbar-color: hsl(var(--grey3)) transparent;
1933
2018
  }
1934
2019
 
1935
- /* Ensure buttons take full width */
1936
- :global(.popover-trigger),
1937
- :global(.popover-trigger button) {
2020
+ /* Trigger fills the 185px column only — do not leak width:100% to other ICs */
2021
+ .font-family-input-ct :global(.popover-trigger),
2022
+ .font-family-input-ct :global(.popover-trigger button),
2023
+ .font-weight-input-ct :global(.popover-trigger),
2024
+ .font-weight-input-ct :global(.popover-trigger button) {
1938
2025
  width: 100% !important;
1939
2026
  height: 100% !important;
1940
2027
  }
@@ -1953,12 +2040,28 @@
1953
2040
  background-color: hsl(var(--light-green)) !important;
1954
2041
  }
1955
2042
 
1956
- /* Optimize font popover rendering */
2043
+ .font-family-input-ct :global(button:focus),
2044
+ .font-family-input-ct :global(button:focus-visible),
2045
+ .font-weight-input-ct :global(button:focus),
2046
+ .font-weight-input-ct :global(button:focus-visible),
2047
+ :global(.font-input-popover button:focus),
2048
+ :global(.font-input-popover button:focus-visible),
2049
+ :global(.font-list-row:focus),
2050
+ :global(.font-list-row:focus-visible),
2051
+ :global(.font-input-popover [data-item]:focus),
2052
+ :global(.font-input-popover [data-item]:focus-visible) {
2053
+ outline: none;
2054
+ box-shadow: none;
2055
+ }
2056
+
2057
+ /* Clamp to the viewport so the Fonts header and list stay on screen */
1957
2058
  :global(.font-input-popover) {
1958
2059
  z-index: 50;
1959
2060
  will-change: transform;
1960
2061
  transform: translateZ(0);
1961
2062
  backface-visibility: hidden;
2063
+ max-width: calc(100vw - 16px) !important;
2064
+ overflow: hidden;
1962
2065
  }
1963
2066
 
1964
2067
  :global(.font-popover-content) {
@@ -1998,14 +2101,20 @@
1998
2101
  z-index: 10; /* Ensure it appears above other content */
1999
2102
  }
2000
2103
 
2001
- /* Prevent hover effect on selected items */
2002
- :global(.selected-item:hover) {
2003
- background-color: hsl(var(--sage-green)) !important;
2104
+ /* Selected row only — never leave white text on a white (deselected) row. */
2105
+ :global(.font-list-row.selected-item),
2106
+ :global([data-item].selected-item) {
2004
2107
  color: white !important;
2005
2108
  }
2006
-
2007
- /* Ensure text is white in selected items */
2008
- :global(.selected-item) {
2109
+ :global(.font-list-row:not(.selected-item)),
2110
+ :global([data-item]:not(.selected-item)) {
2111
+ color: hsl(var(--grey7)) !important;
2112
+ }
2113
+
2114
+ /* Prevent hover from keeping sage+white on a row that is no longer selected. */
2115
+ :global(.font-list-row.selected-item:hover),
2116
+ :global([data-item].selected-item:hover) {
2117
+ background-color: hsl(var(--sage-green)) !important;
2009
2118
  color: white !important;
2010
2119
  }
2011
2120
 
@@ -23,12 +23,14 @@ declare const FontInputController: $$__sveltets_2_IsomorphicComponent<{
23
23
  * @prop {any} openDropdowns - Store tracking open dropdown states
24
24
  * @prop {Function} toggleDropdown - Function to toggle dropdown state
25
25
  * @prop {Function} onFocus - Optional focus handler
26
- */ label: string;
26
+ */ label?: string;
27
+ class?: string;
28
+ style?: string;
27
29
  value?: {
28
30
  family: string;
29
31
  weight: string;
30
32
  category: string;
31
- } | null;
33
+ } | string | null;
32
34
  onChange?: ((newValue: {
33
35
  family: string;
34
36
  weight: string;