@motion-proto/live-tokens 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist-plugin/index.cjs +854 -226
- package/dist-plugin/index.d.cts +2 -1
- package/dist-plugin/index.d.ts +2 -1
- package/dist-plugin/index.js +852 -225
- package/package.json +26 -40
- package/src/{styles → app}/site.css +1 -1
- package/src/{component-editor → editor/component-editor}/BadgeEditor.svelte +8 -82
- package/src/{component-editor → editor/component-editor}/CalloutEditor.svelte +4 -4
- package/src/{component-editor → editor/component-editor}/CardEditor.svelte +28 -76
- package/src/{component-editor → editor/component-editor}/CollapsibleSectionEditor.svelte +37 -30
- package/src/{component-editor → editor/component-editor}/CornerBadgeEditor.svelte +31 -93
- package/src/{component-editor → editor/component-editor}/DialogEditor.svelte +60 -57
- package/src/editor/component-editor/ImageEditor.svelte +30 -0
- package/src/{component-editor → editor/component-editor}/InlineEditActionsEditor.svelte +6 -4
- package/src/editor/component-editor/MenuSelectEditor.svelte +160 -0
- package/src/{component-editor → editor/component-editor}/NotificationEditor.svelte +67 -38
- package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
- package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
- package/src/editor/component-editor/SectionDividerEditor.svelte +565 -0
- package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +29 -21
- package/src/{component-editor → editor/component-editor}/TabBarEditor.svelte +9 -14
- package/src/{component-editor → editor/component-editor}/TableEditor.svelte +9 -18
- package/src/{component-editor → editor/component-editor}/TooltipEditor.svelte +11 -47
- package/src/editor/component-editor/editors.d.ts +10 -0
- package/src/{component-editor → editor/component-editor}/registry.ts +28 -18
- package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +54 -15
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +151 -424
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileMenu.svelte +18 -170
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentsTab.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/CopyFromMenu.svelte +44 -4
- package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkageChart.svelte +6 -6
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkedBlock.svelte +6 -12
- package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
- package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +85 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +345 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +17 -12
- package/src/{component-editor → editor/component-editor}/scaffolding/TypeEditor.svelte +13 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +858 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +1 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/editorContext.ts +19 -9
- package/src/{component-editor → editor/component-editor}/scaffolding/linkedBlock.ts +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/types.ts +25 -0
- package/src/{lib → editor/core/components}/componentConfigKeys.ts +8 -0
- package/src/{lib → editor/core/components}/componentConfigService.ts +3 -3
- package/src/{lib → editor/core/components}/componentPersist.ts +11 -9
- package/src/editor/core/flashStatus.ts +30 -0
- package/src/{lib → editor/core/fonts}/fontLoader.ts +2 -2
- package/src/{lib → editor/core/fonts}/fontMigration.ts +4 -4
- package/src/{lib → editor/core/fonts}/fontParse.ts +1 -1
- package/src/editor/core/manifests/manifestService.ts +171 -0
- package/src/editor/core/palettes/familySwap.ts +99 -0
- package/src/{lib → editor/core/palettes}/paletteDerivation.ts +71 -2
- package/src/{lib → editor/core/palettes}/tokenRegistry.ts +9 -6
- package/src/editor/core/productionPulse.ts +37 -0
- package/src/{lib → editor/core/routing}/router.ts +1 -1
- package/src/{lib/files/versionedFileResource.ts → editor/core/storage/files/versionedFileResourceClient.ts} +8 -1
- package/src/{lib → editor/core/store}/editorCore.ts +24 -8
- package/src/{lib → editor/core/store}/editorPersistence.ts +3 -3
- package/src/{lib → editor/core/store}/editorRenderer.ts +2 -2
- package/src/{lib → editor/core/store}/editorStore.ts +222 -28
- package/src/{lib → editor/core/store}/editorTypes.ts +56 -13
- package/src/editor/core/store/gradientSource.ts +192 -0
- package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
- package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
- package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
- package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
- package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
- package/src/{lib → editor/core/themes}/migrations/index.ts +10 -0
- package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/components.ts +20 -6
- package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/gradients.ts +89 -14
- package/src/{lib → editor/core/themes}/slices/overlays.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/palettes.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/shadows.ts +3 -3
- package/src/{lib → editor/core/themes}/themeInit.ts +8 -8
- package/src/{lib → editor/core/themes}/themeService.ts +6 -6
- package/src/{lib → editor/core/themes}/themeTypes.ts +67 -8
- package/src/editor/index.ts +69 -0
- package/src/{lib → editor/overlay}/ColumnsOverlay.svelte +0 -1
- package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +80 -129
- package/src/{lib → editor/overlay}/columnsOverlay.ts +2 -2
- package/src/{pages → editor/pages}/ComponentEditorPage.svelte +12 -12
- package/src/{pages → editor/pages}/Editor.svelte +4 -4
- package/src/{pages → editor/pages}/EditorShell.svelte +18 -36
- package/src/{styles → editor/styles}/ui-editor.css +43 -22
- package/src/{styles → editor/styles}/ui-form-controls.css +23 -24
- package/src/{ui → editor/ui}/BezierCurveEditor.svelte +119 -68
- package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
- package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +7 -6
- package/src/editor/ui/FileLoadList.svelte +367 -0
- package/src/editor/ui/FilePill.svelte +80 -0
- package/src/editor/ui/FontStackEditor.svelte +499 -0
- package/src/editor/ui/GradientEditor.svelte +690 -0
- package/src/{ui → editor/ui}/GradientStopPicker.svelte +12 -4
- package/src/editor/ui/ManifestFileManager.svelte +438 -0
- package/src/{ui → editor/ui}/PaletteEditor.svelte +180 -673
- package/src/editor/ui/ProjectFontsSection.svelte +638 -0
- package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
- package/src/{ui → editor/ui}/TextTab.svelte +3 -3
- package/src/editor/ui/ThemeFileManager.svelte +783 -0
- package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
- package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -7
- package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +4 -1
- package/src/editor/ui/UIInfoPopover.svelte +243 -0
- package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
- package/src/{ui → editor/ui}/UILineHeightSelector.svelte +5 -5
- package/src/{ui → editor/ui}/UILinkToggle.svelte +2 -2
- package/src/{ui → editor/ui}/UIPaddingSelector.svelte +6 -6
- package/src/{ui → editor/ui}/UIPaletteSelector.svelte +57 -30
- package/src/editor/ui/UIPillButton.svelte +168 -0
- package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
- package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
- package/src/editor/ui/UISegmentedControl.svelte +114 -0
- package/src/editor/ui/UISquareButton.svelte +172 -0
- package/src/{ui → editor/ui}/UITokenSelector.svelte +14 -11
- package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
- package/src/{ui → editor/ui}/VariablesTab.svelte +46 -17
- package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
- package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +24 -47
- package/src/{ui → editor/ui}/palette/PaletteBase.svelte +11 -8
- package/src/{ui → editor/ui}/palette/paletteEditorState.ts +1 -1
- package/src/editor/ui/palette/paletteMath.ts +275 -0
- package/src/{ui → editor/ui}/sections/ColumnsSection.svelte +137 -18
- package/src/{ui → editor/ui}/sections/GradientsSection.svelte +8 -8
- package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +18 -18
- package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +23 -23
- package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
- package/src/{components → system/components}/Badge.svelte +0 -36
- package/src/{components → system/components}/Button.svelte +2 -2
- package/src/{components → system/components}/Card.svelte +34 -60
- package/src/{components → system/components}/CollapsibleSection.svelte +25 -2
- package/src/{components → system/components}/CornerBadge.svelte +8 -24
- package/src/{components → system/components}/Dialog.svelte +1 -1
- package/src/system/components/FloatingTokenTags.css +275 -0
- package/src/system/components/FloatingTokenTags.svelte +543 -0
- package/src/{components → system/components}/InlineEditActions.svelte +6 -4
- package/src/system/components/MenuSelect.svelte +229 -0
- package/src/{components → system/components}/Notification.svelte +8 -1
- package/src/{components → system/components}/ProgressBar.svelte +29 -11
- package/src/system/components/SectionDivider.svelte +560 -0
- package/src/{components → system/components}/SegmentedControl.svelte +49 -43
- package/src/{components → system/components}/TabBar.svelte +81 -65
- package/src/{components → system/components}/Table.svelte +17 -3
- package/src/{components → system/components}/Tooltip.svelte +6 -4
- package/src/system/styles/CONVENTIONS.md +178 -0
- package/src/system/styles/fonts.css +20 -0
- package/src/system/styles/tokens.css +601 -0
- package/src/system/styles/tokens.generated.css +544 -0
- package/src/component-editor/ImageEditor.svelte +0 -74
- package/src/component-editor/SectionDividerEditor.svelte +0 -265
- package/src/component-editor/scaffolding/DividerEditor.svelte +0 -94
- package/src/component-editor/scaffolding/GradientCard.svelte +0 -296
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +0 -62
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +0 -37
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +0 -61
- package/src/component-editor/scaffolding/StateBlock.svelte +0 -132
- package/src/component-editor/scaffolding/VariantGroup.svelte +0 -310
- package/src/components/SectionDivider.svelte +0 -483
- package/src/data/google-fonts.json +0 -75
- package/src/lib/index.ts +0 -68
- package/src/lib/presetService.ts +0 -214
- package/src/lib/productionPulse.ts +0 -32
- package/src/styles/fonts.css +0 -30
- package/src/styles/tokens.css +0 -1324
- package/src/ui/FontStackEditor.svelte +0 -361
- package/src/ui/GradientEditor.svelte +0 -470
- package/src/ui/PresetFileManager.svelte +0 -1116
- package/src/ui/ProjectFontsSection.svelte +0 -645
- package/src/ui/ThemeFileManager.svelte +0 -1020
- package/src/ui/UnsavedComponentsDialog.svelte +0 -315
- /package/src/{component-editor → editor/component-editor}/index.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/DemoHeader.svelte +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSectionType.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSources.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/defaultSections.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/siblings.ts +0 -0
- /package/src/{lib → editor/core}/cssVarSync.ts +0 -0
- /package/src/{lib → editor/core/palettes}/oklch.ts +0 -0
- /package/src/{lib → editor/core/routing}/navLinkTypes.ts +0 -0
- /package/src/{lib → editor/core/routing}/parentRouteStore.ts +0 -0
- /package/src/{lib → editor/core/storage}/storage.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfig.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfigStore.ts +0 -0
- /package/src/{lib → editor/core/store}/editorKeybindings.ts +0 -0
- /package/src/{lib → editor/core/store}/editorViewStore.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-10-sectiondivider-gradient-stops.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-13-primary-to-brand.ts +0 -0
- /package/src/{lib → editor/core/themes}/parsers/globalRootBlock.ts +0 -0
- /package/src/{lib → editor/core/themes}/slices/domainVars.ts +0 -0
- /package/src/{lib → editor/overlay}/overlayState.ts +0 -0
- /package/src/{pages → editor/pages}/ComponentEditorPage.svelte.d.ts +0 -0
- /package/src/{pages → editor/pages}/Editor.svelte.d.ts +0 -0
- /package/src/{ui → editor/ui}/Toggle.svelte +0 -0
- /package/src/{ui → editor/ui}/UIDialog.svelte +0 -0
- /package/src/{ui → editor/ui}/UIFontWeightSelector.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionItem.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionList.svelte +0 -0
- /package/src/{ui → editor/ui}/UIRadioGroup.svelte +0 -0
- /package/src/{lib → editor/ui}/copyPopover.ts +0 -0
- /package/src/{ui → editor/ui}/curveEngine.ts +0 -0
- /package/src/{ui → editor/ui}/index.ts +0 -0
- /package/src/{ui → editor/ui}/keepInViewport.ts +0 -0
- /package/src/{ui → editor/ui}/palette/ScaleCurveEditor.svelte +0 -0
- /package/src/{lib → editor/ui}/scrollSection.ts +0 -0
- /package/src/{ui → editor/ui}/sections/tokenScales.ts +0 -0
- /package/src/{ui → editor/ui}/variantScales.ts +0 -0
- /package/src/{assets → system/assets}/newspaper.webp +0 -0
- /package/src/{assets → system/assets}/offering.webp +0 -0
- /package/src/{components → system/components}/Callout.svelte +0 -0
- /package/src/{components → system/components}/Image.svelte +0 -0
- /package/src/{components → system/components}/RadioButton.svelte +0 -0
- /package/src/{components → system/components}/types.ts +0 -0
- /package/src/{styles → system/styles}/_padding.scss +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin.woff2 +0 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type AnchorSide = 'top' | 'right' | 'bottom' | 'left';
|
|
3
|
+
|
|
4
|
+
export type TagControl = 'surface' | 'radius' | 'border-color' | 'border-width' | 'font-family';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Where the kite string ties to the box. `inside` anchors are % of the
|
|
8
|
+
* box's footprint; edge anchors are `pos` 0..100 along the named side.
|
|
9
|
+
*/
|
|
10
|
+
export type Anchor =
|
|
11
|
+
| { side: AnchorSide; pos: number }
|
|
12
|
+
| { side: 'inside'; x: number; y: number };
|
|
13
|
+
|
|
14
|
+
export interface FloatingTag {
|
|
15
|
+
icon?: string;
|
|
16
|
+
/** Initial value; typically a CSS-variable name. */
|
|
17
|
+
label: string;
|
|
18
|
+
/** Tag center, in % of the stage. */
|
|
19
|
+
top: number;
|
|
20
|
+
left: number;
|
|
21
|
+
/** Bob delay, seconds. Negative values offset the start. */
|
|
22
|
+
delay?: number;
|
|
23
|
+
/** Static tilt, degrees. */
|
|
24
|
+
rotate?: number;
|
|
25
|
+
anchor: Anchor;
|
|
26
|
+
/** Visual property of the central box this tag drives. */
|
|
27
|
+
controls?: TagControl;
|
|
28
|
+
/** `top` chip sits above the box and opens down; `bottom` mirrors. */
|
|
29
|
+
placement?: 'top' | 'bottom';
|
|
30
|
+
/** `right-cap` pivots the tilt around the chip's right cap. */
|
|
31
|
+
pivot?: 'center' | 'right-cap';
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import MenuSelect from './MenuSelect.svelte';
|
|
37
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
38
|
+
// `.ftt-tag` is hand-rolled (not Badge) so editing badge-* tokens doesn't
|
|
39
|
+
// repaint the playground. The dropdown uses MenuSelect on purpose.
|
|
40
|
+
import './FloatingTokenTags.css';
|
|
41
|
+
|
|
42
|
+
interface Props {
|
|
43
|
+
tags?: FloatingTag[];
|
|
44
|
+
duration?: number;
|
|
45
|
+
distance?: number;
|
|
46
|
+
boxSize?: { w: number; h: number };
|
|
47
|
+
autoplay?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const controlLabels: Record<TagControl, string> = {
|
|
51
|
+
'surface': 'Surface color',
|
|
52
|
+
'radius': 'Corner radius',
|
|
53
|
+
'border-color': 'Border color',
|
|
54
|
+
'border-width': 'Border width',
|
|
55
|
+
'font-family': 'Font family',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Surfaces use the `-high` step and borders the `-strong` step so the box
|
|
59
|
+
// reads as the figure against the dark canvas. Picks span the hue wheel;
|
|
60
|
+
// `special` is too close to the background to pull weight here.
|
|
61
|
+
const valueOptions: Record<TagControl, string[]> = {
|
|
62
|
+
'surface': ['--surface-brand-high', '--surface-accent-high', '--surface-success-high', '--surface-info-high'],
|
|
63
|
+
'radius': ['--radius-none', '--radius-lg', '--radius-2xl', '--radius-full'],
|
|
64
|
+
'border-color': ['--border-brand-strong', '--border-accent-strong','--border-success-strong','--border-info-strong'],
|
|
65
|
+
'border-width': ['--border-width-1', '--border-width-2', '--border-width-3', '--border-width-5'],
|
|
66
|
+
'font-family': ['--font-display', '--font-sans', '--font-serif', '--font-mono'],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const defaultTags: FloatingTag[] = [
|
|
70
|
+
{
|
|
71
|
+
icon: 'fas fa-fill-drip',
|
|
72
|
+
label: '--surface-brand-high',
|
|
73
|
+
top: 15, left: 20, delay: 0, rotate: 3,
|
|
74
|
+
anchor: { side: 'inside', x: 10, y: 40 },
|
|
75
|
+
controls: 'surface',
|
|
76
|
+
placement: 'top',
|
|
77
|
+
pivot: 'right-cap',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
icon: 'fas fa-font',
|
|
81
|
+
label: '--font-display',
|
|
82
|
+
top: 5, left: 45, delay: -0.9, rotate: 2,
|
|
83
|
+
anchor: { side: 'inside', x: 78, y: 28 },
|
|
84
|
+
controls: 'font-family',
|
|
85
|
+
placement: 'top',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
icon: 'fa-solid fa-bezier-curve',
|
|
89
|
+
label: '--radius-2xl',
|
|
90
|
+
top: 23, left: 75, delay: -1.8, rotate: 2,
|
|
91
|
+
anchor: { side: 'top', pos: 100 },
|
|
92
|
+
controls: 'radius',
|
|
93
|
+
placement: 'top',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
icon: 'fas fa-paint-roller',
|
|
97
|
+
label: '--border-brand-strong',
|
|
98
|
+
top: 79, left: 72, delay: -3.6, rotate: -2,
|
|
99
|
+
anchor: { side: 'bottom', pos: 75 },
|
|
100
|
+
controls: 'border-color',
|
|
101
|
+
placement: 'top',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
icon: 'fas fa-grip-lines',
|
|
105
|
+
label: '--border-width-3',
|
|
106
|
+
top: 79, left: 28, delay: -5.2, rotate: -4,
|
|
107
|
+
anchor: { side: 'bottom', pos: 25 },
|
|
108
|
+
controls: 'border-width',
|
|
109
|
+
placement: 'top',
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
let {
|
|
114
|
+
tags = defaultTags,
|
|
115
|
+
duration = 7,
|
|
116
|
+
distance = 8,
|
|
117
|
+
boxSize = { w: 14, h: 11 },
|
|
118
|
+
autoplay = true,
|
|
119
|
+
}: Props = $props();
|
|
120
|
+
|
|
121
|
+
// Two override layers, deliberately desynchronised: `overrides` commits at
|
|
122
|
+
// selection (drives the tag label); `boxOverrides` commits at impact
|
|
123
|
+
// (drives the box's style) so the box swaps in sync with the bloop.
|
|
124
|
+
const defaultLabels = $derived(tags.map(t => t.label));
|
|
125
|
+
let overrides = $state<Record<number, string>>({});
|
|
126
|
+
let boxOverrides = $state<Record<number, string>>({});
|
|
127
|
+
const currentValues = $derived(defaultLabels.map((d, i) => overrides[i] ?? d));
|
|
128
|
+
const boxValues = $derived(defaultLabels.map((d, i) => boxOverrides[i] ?? d));
|
|
129
|
+
|
|
130
|
+
let openIdx = $state<number | null>(null);
|
|
131
|
+
let strobeIdx = $state<number | null>(null);
|
|
132
|
+
let flashingIdx = $state<number | null>(null);
|
|
133
|
+
let bloopActive = $state(false);
|
|
134
|
+
|
|
135
|
+
let dragOverrides = $state<Record<number, { top: number; left: number }>>({});
|
|
136
|
+
let draggingIdx = $state<number | null>(null);
|
|
137
|
+
const DRAG_THRESHOLD_PX = 4;
|
|
138
|
+
const dragStart = { px: 0, py: 0, tagIdx: -1, moved: false };
|
|
139
|
+
|
|
140
|
+
function tagTop(i: number): number { return dragOverrides[i]?.top ?? tags[i].top; }
|
|
141
|
+
function tagLeft(i: number): number { return dragOverrides[i]?.left ?? tags[i].left; }
|
|
142
|
+
|
|
143
|
+
type BallState = { startedAt: number; duration: number; pendingValue: string };
|
|
144
|
+
const ballStates = new SvelteMap<number, BallState>();
|
|
145
|
+
// `$state` so `bind:this={ballEls[i]}` is a reactive binding target.
|
|
146
|
+
const ballEls: HTMLSpanElement[] = $state([]);
|
|
147
|
+
|
|
148
|
+
let stageEl: HTMLDivElement | undefined = $state();
|
|
149
|
+
let boxEl: HTMLDivElement | undefined = $state();
|
|
150
|
+
const tagEls: HTMLSpanElement[] = $state([]);
|
|
151
|
+
const lineEls: SVGLineElement[] = $state([]);
|
|
152
|
+
const knotEls: HTMLSpanElement[] = $state([]);
|
|
153
|
+
|
|
154
|
+
function anchorPoint(
|
|
155
|
+
anchor: Anchor,
|
|
156
|
+
cx = 50,
|
|
157
|
+
cy = 50,
|
|
158
|
+
w = boxSize.w,
|
|
159
|
+
h = boxSize.h,
|
|
160
|
+
): { x: number; y: number } {
|
|
161
|
+
const halfW = w / 2, halfH = h / 2;
|
|
162
|
+
if (anchor.side === 'inside') {
|
|
163
|
+
return {
|
|
164
|
+
x: cx - halfW + (anchor.x / 100) * w,
|
|
165
|
+
y: cy - halfH + (anchor.y / 100) * h,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const t = anchor.pos / 100;
|
|
169
|
+
switch (anchor.side) {
|
|
170
|
+
case 'top': return { x: cx - halfW + t * w, y: cy - halfH };
|
|
171
|
+
case 'bottom': return { x: cx - halfW + t * w, y: cy + halfH };
|
|
172
|
+
case 'left': return { x: cx - halfW, y: cy - halfH + t * h };
|
|
173
|
+
case 'right': return { x: cx + halfW, y: cy - halfH + t * h };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Reads `boxValues` (not `currentValues`) so the box repaints at impact.
|
|
178
|
+
function resolveControl(name: TagControl): string | undefined {
|
|
179
|
+
const idx = tags.findIndex(t => t.controls === name);
|
|
180
|
+
return idx >= 0 ? boxValues[idx] : undefined;
|
|
181
|
+
}
|
|
182
|
+
const surfaceVar = $derived(resolveControl('surface'));
|
|
183
|
+
const radiusVar = $derived(resolveControl('radius'));
|
|
184
|
+
const borderColorVar = $derived(resolveControl('border-color'));
|
|
185
|
+
const borderWidthVar = $derived(resolveControl('border-width'));
|
|
186
|
+
const fontFamilyVar = $derived(resolveControl('font-family'));
|
|
187
|
+
|
|
188
|
+
function asVar(name: string | undefined): string | undefined {
|
|
189
|
+
return name ? `var(${name})` : undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Kite strings and energy balls are recomputed each frame from the box's
|
|
193
|
+
// measured rect so intrinsic sizing drives anchor placement.
|
|
194
|
+
function syncFrame() {
|
|
195
|
+
if (!stageEl) return;
|
|
196
|
+
const stageRect = stageEl.getBoundingClientRect();
|
|
197
|
+
if (stageRect.width === 0 || stageRect.height === 0) return;
|
|
198
|
+
|
|
199
|
+
let boxCx = 50, boxCy = 50;
|
|
200
|
+
let boxW = boxSize.w, boxH = boxSize.h;
|
|
201
|
+
if (boxEl) {
|
|
202
|
+
const br = boxEl.getBoundingClientRect();
|
|
203
|
+
if (br.width > 0 && br.height > 0) {
|
|
204
|
+
boxCx = ((br.left + br.width / 2) - stageRect.left) / stageRect.width * 100;
|
|
205
|
+
boxCy = ((br.top + br.height / 2) - stageRect.top) / stageRect.height * 100;
|
|
206
|
+
boxW = (br.width / stageRect.width) * 100;
|
|
207
|
+
boxH = (br.height / stageRect.height) * 100;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const now = performance.now();
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < tags.length; i++) {
|
|
214
|
+
const tagEl = tagEls[i];
|
|
215
|
+
const lineEl = lineEls[i];
|
|
216
|
+
if (!tagEl || !lineEl) continue;
|
|
217
|
+
|
|
218
|
+
// Tag endpoint: center of the pill's rounded cap on the box-facing side.
|
|
219
|
+
const pill = tagEl.querySelector('.ftt-tag') as HTMLElement | null;
|
|
220
|
+
const target = (pill ?? tagEl) as HTMLElement;
|
|
221
|
+
const r = target.getBoundingClientRect();
|
|
222
|
+
|
|
223
|
+
let radius = 0;
|
|
224
|
+
if (pill) {
|
|
225
|
+
const cs = getComputedStyle(pill);
|
|
226
|
+
const raw = parseFloat(cs.borderTopLeftRadius || '0') || 0;
|
|
227
|
+
radius = Math.min(raw, r.height / 2);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const onRight = tags[i].left > 50;
|
|
231
|
+
const px = onRight ? r.left + radius : r.right - radius;
|
|
232
|
+
const py = r.top + r.height / 2;
|
|
233
|
+
|
|
234
|
+
const x1 = (px - stageRect.left) / stageRect.width * 100;
|
|
235
|
+
const y1 = (py - stageRect.top) / stageRect.height * 100;
|
|
236
|
+
|
|
237
|
+
lineEl.setAttribute('x1', x1.toFixed(3));
|
|
238
|
+
lineEl.setAttribute('y1', y1.toFixed(3));
|
|
239
|
+
|
|
240
|
+
const a = anchorPoint(tags[i].anchor, boxCx, boxCy, boxW, boxH);
|
|
241
|
+
lineEl.setAttribute('x2', a.x.toFixed(3));
|
|
242
|
+
lineEl.setAttribute('y2', a.y.toFixed(3));
|
|
243
|
+
const knotEl = knotEls[i];
|
|
244
|
+
if (knotEl) {
|
|
245
|
+
knotEl.style.left = `${a.x.toFixed(3)}%`;
|
|
246
|
+
knotEl.style.top = `${a.y.toFixed(3)}%`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const ballEl = ballEls[i];
|
|
250
|
+
const state = ballStates.get(i);
|
|
251
|
+
if (!ballEl) continue;
|
|
252
|
+
if (!state) {
|
|
253
|
+
ballEl.style.opacity = '0';
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const elapsed = now - state.startedAt;
|
|
257
|
+
const t = Math.min(1, elapsed / state.duration);
|
|
258
|
+
if (t >= 1) {
|
|
259
|
+
// Commit at impact so the box's appearance changes with the bloop.
|
|
260
|
+
boxOverrides = { ...boxOverrides, [i]: state.pendingValue };
|
|
261
|
+
ballStates.delete(i);
|
|
262
|
+
ballEl.style.opacity = '0';
|
|
263
|
+
triggerBloop();
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const x2 = parseFloat(lineEl.getAttribute('x2') || '0');
|
|
267
|
+
const y2 = parseFloat(lineEl.getAttribute('y2') || '0');
|
|
268
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
269
|
+
const bx = x1 + (x2 - x1) * eased;
|
|
270
|
+
const by = y1 + (y2 - y1) * eased;
|
|
271
|
+
ballEl.style.left = `${bx.toFixed(3)}%`;
|
|
272
|
+
ballEl.style.top = `${by.toFixed(3)}%`;
|
|
273
|
+
ballEl.style.opacity = '1';
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
$effect(() => {
|
|
278
|
+
let rafId = 0;
|
|
279
|
+
const loop = () => {
|
|
280
|
+
syncFrame();
|
|
281
|
+
rafId = requestAnimationFrame(loop);
|
|
282
|
+
};
|
|
283
|
+
rafId = requestAnimationFrame(loop);
|
|
284
|
+
return () => cancelAnimationFrame(rafId);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
function pickValue(i: number, value: string) {
|
|
288
|
+
openIdx = null;
|
|
289
|
+
strobeIdx = null;
|
|
290
|
+
overrides = { ...overrides, [i]: value };
|
|
291
|
+
flashingIdx = i;
|
|
292
|
+
window.setTimeout(() => {
|
|
293
|
+
if (flashingIdx === i) flashingIdx = null;
|
|
294
|
+
}, 500);
|
|
295
|
+
ballStates.set(i, { startedAt: performance.now(), duration: 520, pendingValue: value });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function triggerBloop() {
|
|
299
|
+
bloopActive = true;
|
|
300
|
+
window.setTimeout(() => { bloopActive = false; }, 480);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Any click pauses auto-cycle for at least this long.
|
|
304
|
+
const USER_HOLD_MS = 4000;
|
|
305
|
+
let lastUserActionAt = 0;
|
|
306
|
+
function noteUserAction() {
|
|
307
|
+
lastUserActionAt = performance.now();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function clickTag(i: number) {
|
|
311
|
+
if (!tags[i].controls) return;
|
|
312
|
+
noteUserAction();
|
|
313
|
+
openIdx = openIdx === i ? null : i;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function userPick(i: number, value: string) {
|
|
317
|
+
// The matching dropdown item is also rendered disabled; this is a safety net.
|
|
318
|
+
if (currentValues[i] === value) return;
|
|
319
|
+
noteUserAction();
|
|
320
|
+
pickValue(i, value);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function onTagPointerDown(i: number, e: PointerEvent) {
|
|
324
|
+
dragStart.px = e.clientX;
|
|
325
|
+
dragStart.py = e.clientY;
|
|
326
|
+
dragStart.tagIdx = i;
|
|
327
|
+
dragStart.moved = false;
|
|
328
|
+
(e.currentTarget as Element).setPointerCapture(e.pointerId);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function onTagPointerMove(i: number, e: PointerEvent) {
|
|
332
|
+
if (dragStart.tagIdx !== i || !stageEl) return;
|
|
333
|
+
const dx = e.clientX - dragStart.px;
|
|
334
|
+
const dy = e.clientY - dragStart.py;
|
|
335
|
+
if (!dragStart.moved && Math.hypot(dx, dy) > DRAG_THRESHOLD_PX) {
|
|
336
|
+
dragStart.moved = true;
|
|
337
|
+
draggingIdx = i;
|
|
338
|
+
openIdx = null;
|
|
339
|
+
noteUserAction();
|
|
340
|
+
}
|
|
341
|
+
if (dragStart.moved) {
|
|
342
|
+
const r = stageEl.getBoundingClientRect();
|
|
343
|
+
const x = ((e.clientX - r.left) / r.width) * 100;
|
|
344
|
+
const y = ((e.clientY - r.top) / r.height) * 100;
|
|
345
|
+
dragOverrides = { ...dragOverrides, [i]: { top: y, left: x } };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function onTagPointerUp(i: number, e: PointerEvent) {
|
|
350
|
+
if (dragStart.tagIdx !== i) return;
|
|
351
|
+
(e.currentTarget as Element).releasePointerCapture(e.pointerId);
|
|
352
|
+
if (dragStart.moved) {
|
|
353
|
+
const p = dragOverrides[i];
|
|
354
|
+
// eslint-disable-next-line no-console
|
|
355
|
+
console.log(
|
|
356
|
+
`[${i}] ${tags[i].controls ?? '?'}: top: ${p.top.toFixed(1)}, left: ${p.left.toFixed(1)},`,
|
|
357
|
+
);
|
|
358
|
+
} else {
|
|
359
|
+
clickTag(i);
|
|
360
|
+
}
|
|
361
|
+
draggingIdx = null;
|
|
362
|
+
dragStart.tagIdx = -1;
|
|
363
|
+
dragStart.moved = false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let autoAlive = false;
|
|
367
|
+
let lastAutoTagIdx: number | null = null;
|
|
368
|
+
const sleep = (ms: number) => new Promise(r => window.setTimeout(r, ms));
|
|
369
|
+
|
|
370
|
+
const STROBE_STEP_MS = 125;
|
|
371
|
+
const BREATH_MS = 250;
|
|
372
|
+
|
|
373
|
+
async function autoLoop() {
|
|
374
|
+
while (autoAlive) {
|
|
375
|
+
await sleep(2400 + Math.random() * 2400);
|
|
376
|
+
if (!autoAlive) break;
|
|
377
|
+
|
|
378
|
+
// Hold off while the user is interacting; re-check on each fresh click.
|
|
379
|
+
while (autoAlive) {
|
|
380
|
+
const sinceUser = performance.now() - lastUserActionAt;
|
|
381
|
+
if (sinceUser >= USER_HOLD_MS) break;
|
|
382
|
+
await sleep(USER_HOLD_MS - sinceUser);
|
|
383
|
+
}
|
|
384
|
+
if (!autoAlive) break;
|
|
385
|
+
|
|
386
|
+
if (openIdx !== null) continue;
|
|
387
|
+
|
|
388
|
+
// Never the same tag twice in a row.
|
|
389
|
+
const tagCandidates = tags
|
|
390
|
+
.map((_, idx) => idx)
|
|
391
|
+
.filter(idx => idx !== lastAutoTagIdx && tags[idx].controls);
|
|
392
|
+
if (tagCandidates.length === 0) continue;
|
|
393
|
+
const i = tagCandidates[Math.floor(Math.random() * tagCandidates.length)];
|
|
394
|
+
const tag = tags[i];
|
|
395
|
+
const opts = valueOptions[tag.controls!];
|
|
396
|
+
|
|
397
|
+
// Never the same token twice in a row.
|
|
398
|
+
const currentIdx = opts.indexOf(currentValues[i]);
|
|
399
|
+
const candidates = opts
|
|
400
|
+
.map((_, k) => k)
|
|
401
|
+
.filter(k => k !== currentIdx);
|
|
402
|
+
if (candidates.length === 0) continue;
|
|
403
|
+
const finalIdx = candidates[Math.floor(Math.random() * candidates.length)];
|
|
404
|
+
|
|
405
|
+
openIdx = i;
|
|
406
|
+
|
|
407
|
+
// Let the menu sit open before any highlight appears.
|
|
408
|
+
await sleep(BREATH_MS);
|
|
409
|
+
if (!autoAlive || openIdx !== i) continue;
|
|
410
|
+
|
|
411
|
+
// Step from the top down to the chosen item.
|
|
412
|
+
for (let k = 0; k <= finalIdx; k++) {
|
|
413
|
+
if (!autoAlive || openIdx !== i) break;
|
|
414
|
+
strobeIdx = k;
|
|
415
|
+
await sleep(STROBE_STEP_MS);
|
|
416
|
+
}
|
|
417
|
+
if (!autoAlive || openIdx !== i) continue;
|
|
418
|
+
|
|
419
|
+
// Blink twice on the selection.
|
|
420
|
+
for (let blink = 0; blink < 2; blink++) {
|
|
421
|
+
if (!autoAlive || openIdx !== i) break;
|
|
422
|
+
strobeIdx = null;
|
|
423
|
+
await sleep(STROBE_STEP_MS);
|
|
424
|
+
if (!autoAlive || openIdx !== i) break;
|
|
425
|
+
strobeIdx = finalIdx;
|
|
426
|
+
await sleep(STROBE_STEP_MS);
|
|
427
|
+
}
|
|
428
|
+
if (!autoAlive || openIdx !== i) continue;
|
|
429
|
+
|
|
430
|
+
pickValue(i, opts[finalIdx]);
|
|
431
|
+
lastAutoTagIdx = i;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
$effect(() => {
|
|
436
|
+
if (!autoplay) return;
|
|
437
|
+
autoAlive = true;
|
|
438
|
+
autoLoop();
|
|
439
|
+
return () => { autoAlive = false; };
|
|
440
|
+
});
|
|
441
|
+
</script>
|
|
442
|
+
|
|
443
|
+
<div
|
|
444
|
+
class="ftt-stage"
|
|
445
|
+
bind:this={stageEl}
|
|
446
|
+
style:--ftt-bob-duration="{duration}s"
|
|
447
|
+
style:--ftt-bob-distance="{-distance}px"
|
|
448
|
+
>
|
|
449
|
+
<svg class="ftt-strings" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
|
|
450
|
+
{#each tags as tag, i (i)}
|
|
451
|
+
{@const a = anchorPoint(tag.anchor)}
|
|
452
|
+
<line
|
|
453
|
+
bind:this={lineEls[i]}
|
|
454
|
+
x1={tag.left} y1={tag.top}
|
|
455
|
+
x2={a.x} y2={a.y}
|
|
456
|
+
class="ftt-string"
|
|
457
|
+
class:ftt-live={ballStates.has(i)}
|
|
458
|
+
/>
|
|
459
|
+
{/each}
|
|
460
|
+
</svg>
|
|
461
|
+
|
|
462
|
+
<!-- Box is intrinsically sized to content + padding; boxSize is a floor. -->
|
|
463
|
+
<div
|
|
464
|
+
bind:this={boxEl}
|
|
465
|
+
class="ftt-box"
|
|
466
|
+
class:ftt-bloop={bloopActive}
|
|
467
|
+
style:min-width="{boxSize.w}%"
|
|
468
|
+
style:min-height="{boxSize.h}%"
|
|
469
|
+
style:background={asVar(surfaceVar)}
|
|
470
|
+
style:border-radius={asVar(radiusVar)}
|
|
471
|
+
style:border-color={asVar(borderColorVar)}
|
|
472
|
+
style:border-width={asVar(borderWidthVar)}
|
|
473
|
+
>
|
|
474
|
+
<span
|
|
475
|
+
class="ftt-box-label"
|
|
476
|
+
style:font-family={asVar(fontFamilyVar)}
|
|
477
|
+
>I'm a button</span>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
{#each tags as tag, i (i)}
|
|
481
|
+
{@const a = anchorPoint(tag.anchor)}
|
|
482
|
+
<span
|
|
483
|
+
bind:this={knotEls[i]}
|
|
484
|
+
class="ftt-knot"
|
|
485
|
+
style:left="{a.x}%"
|
|
486
|
+
style:top="{a.y}%"
|
|
487
|
+
aria-hidden="true"
|
|
488
|
+
></span>
|
|
489
|
+
{/each}
|
|
490
|
+
|
|
491
|
+
{#each tags as _t, i (i)}
|
|
492
|
+
<span class="ftt-energy-ball" bind:this={ballEls[i]} aria-hidden="true"></span>
|
|
493
|
+
{/each}
|
|
494
|
+
|
|
495
|
+
{#each tags as tag, i (i)}
|
|
496
|
+
<span
|
|
497
|
+
bind:this={tagEls[i]}
|
|
498
|
+
class="ftt-float"
|
|
499
|
+
class:ftt-flash={flashingIdx === i}
|
|
500
|
+
class:ftt-open={openIdx === i}
|
|
501
|
+
class:ftt-dragging={draggingIdx === i}
|
|
502
|
+
data-placement={tag.placement ?? 'top'}
|
|
503
|
+
data-pivot={tag.pivot ?? 'center'}
|
|
504
|
+
style:top="{tagTop(i)}%"
|
|
505
|
+
style:left="{tagLeft(i)}%"
|
|
506
|
+
style:animation-delay="{tag.delay ?? 0}s"
|
|
507
|
+
style:--ftt-rot="{tag.rotate ?? 0}deg"
|
|
508
|
+
>
|
|
509
|
+
{#if tag.controls}
|
|
510
|
+
<span class="ftt-float-property">{controlLabels[tag.controls]}</span>
|
|
511
|
+
{/if}
|
|
512
|
+
<span class="ftt-chip-host">
|
|
513
|
+
<button
|
|
514
|
+
type="button"
|
|
515
|
+
class="ftt-tag-trigger"
|
|
516
|
+
onpointerdown={(e) => onTagPointerDown(i, e)}
|
|
517
|
+
onpointermove={(e) => onTagPointerMove(i, e)}
|
|
518
|
+
onpointerup={(e) => onTagPointerUp(i, e)}
|
|
519
|
+
aria-haspopup="listbox"
|
|
520
|
+
aria-expanded={openIdx === i}
|
|
521
|
+
>
|
|
522
|
+
<span class="ftt-tag">
|
|
523
|
+
{#if tag.icon}<span class="ftt-tag-icon"><i class={tag.icon}></i></span>{/if}
|
|
524
|
+
<span class="ftt-tag-label">{currentValues[i]}</span>
|
|
525
|
+
</span>
|
|
526
|
+
</button>
|
|
527
|
+
|
|
528
|
+
{#if openIdx === i && tag.controls}
|
|
529
|
+
{@const opts = valueOptions[tag.controls]}
|
|
530
|
+
{@const strobeValue = strobeIdx !== null ? opts[strobeIdx] : null}
|
|
531
|
+
<div class="ftt-dropdown-wrap">
|
|
532
|
+
<MenuSelect
|
|
533
|
+
items={opts.map((opt) => ({ value: opt, label: opt }))}
|
|
534
|
+
value={currentValues[i]}
|
|
535
|
+
forceHoverValue={strobeValue}
|
|
536
|
+
onchange={(v) => userPick(i, v)}
|
|
537
|
+
/>
|
|
538
|
+
</div>
|
|
539
|
+
{/if}
|
|
540
|
+
</span>
|
|
541
|
+
</span>
|
|
542
|
+
{/each}
|
|
543
|
+
</div>
|
|
@@ -47,6 +47,8 @@
|
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
<style lang="scss">
|
|
50
|
+
@use '../styles/padding' as *;
|
|
51
|
+
|
|
50
52
|
:global(:root) {
|
|
51
53
|
/* Save (default) */
|
|
52
54
|
--inlineeditactions-save-default-surface: var(--surface-success-low);
|
|
@@ -110,7 +112,7 @@
|
|
|
110
112
|
color: var(--inlineeditactions-save-default-text);
|
|
111
113
|
border: var(--inlineeditactions-save-default-border-width) solid var(--inlineeditactions-save-default-border);
|
|
112
114
|
border-radius: var(--inlineeditactions-save-default-radius);
|
|
113
|
-
|
|
115
|
+
@include themed-padding(--inlineeditactions-save-default-padding, $h: 2);
|
|
114
116
|
font-size: var(--inlineeditactions-save-default-icon-size);
|
|
115
117
|
|
|
116
118
|
&:hover:not(:disabled),
|
|
@@ -119,7 +121,7 @@
|
|
|
119
121
|
color: var(--inlineeditactions-save-hover-text);
|
|
120
122
|
border: var(--inlineeditactions-save-hover-border-width) solid var(--inlineeditactions-save-hover-border);
|
|
121
123
|
border-radius: var(--inlineeditactions-save-hover-radius);
|
|
122
|
-
|
|
124
|
+
@include themed-padding(--inlineeditactions-save-hover-padding, $h: 2);
|
|
123
125
|
font-size: var(--inlineeditactions-save-hover-icon-size);
|
|
124
126
|
}
|
|
125
127
|
}
|
|
@@ -129,7 +131,7 @@
|
|
|
129
131
|
color: var(--inlineeditactions-cancel-default-text);
|
|
130
132
|
border: var(--inlineeditactions-cancel-default-border-width) solid var(--inlineeditactions-cancel-default-border);
|
|
131
133
|
border-radius: var(--inlineeditactions-cancel-default-radius);
|
|
132
|
-
|
|
134
|
+
@include themed-padding(--inlineeditactions-cancel-default-padding, $h: 2);
|
|
133
135
|
font-size: var(--inlineeditactions-cancel-default-icon-size);
|
|
134
136
|
|
|
135
137
|
&:hover:not(:disabled),
|
|
@@ -138,7 +140,7 @@
|
|
|
138
140
|
color: var(--inlineeditactions-cancel-hover-text);
|
|
139
141
|
border: var(--inlineeditactions-cancel-hover-border-width) solid var(--inlineeditactions-cancel-hover-border);
|
|
140
142
|
border-radius: var(--inlineeditactions-cancel-hover-radius);
|
|
141
|
-
|
|
143
|
+
@include themed-padding(--inlineeditactions-cancel-hover-padding, $h: 2);
|
|
142
144
|
font-size: var(--inlineeditactions-cancel-hover-icon-size);
|
|
143
145
|
}
|
|
144
146
|
}
|