@jxsuite/studio 0.0.1

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.
@@ -0,0 +1,83 @@
1
+ // packages/studio/icons.js
2
+ // Icon templates for style sidebar button groups.
3
+ // Uses Spectrum workflow icons where available; custom SVGs for flex-specific concepts.
4
+
5
+ import { html } from "lit";
6
+
7
+ // Helper for custom filled-rect icons (alignment/justify diagrams) where no Spectrum match exists
8
+ const _R = (/** @type {any} */ d) =>
9
+ html`<svg
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ width="16"
12
+ height="16"
13
+ viewBox="0 0 24 24"
14
+ fill="currentColor"
15
+ stroke="none"
16
+ >
17
+ ${d}
18
+ </svg>`;
19
+
20
+ // Helper for custom stroke icons
21
+ const _S = (/** @type {any} */ d) =>
22
+ html`<svg
23
+ xmlns="http://www.w3.org/2000/svg"
24
+ width="16"
25
+ height="16"
26
+ viewBox="0 0 24 24"
27
+ fill="none"
28
+ stroke="currentColor"
29
+ stroke-width="2"
30
+ stroke-linecap="round"
31
+ stroke-linejoin="round"
32
+ >
33
+ ${d}
34
+ </svg>`;
35
+
36
+ const icons = {
37
+ // ─── Arrows — flexDirection ───
38
+ "arrow-right": html`<sp-icon-arrow-right slot="icon"></sp-icon-arrow-right>`,
39
+ "arrow-left": html`<sp-icon-arrow-left slot="icon"></sp-icon-arrow-left>`,
40
+ "arrow-down": html`<sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>`,
41
+ "arrow-up": html`<sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>`,
42
+
43
+ // ─── Text align — textAlign ───
44
+ "text-align-left": html`<sp-icon-text-align-left slot="icon"></sp-icon-text-align-left>`,
45
+ "text-align-center": html`<sp-icon-text-align-center slot="icon"></sp-icon-text-align-center>`,
46
+ "text-align-right": html`<sp-icon-text-align-right slot="icon"></sp-icon-text-align-right>`,
47
+ "text-align-justify": html`<sp-icon-text-align-justify slot="icon"></sp-icon-text-align-justify>`,
48
+
49
+ // ─── flexWrap ───
50
+ "wrap-text": html`<sp-icon-flip-vertical slot="icon"></sp-icon-flip-vertical>`,
51
+
52
+ // ─── alignItems / alignSelf — vertical alignment within container ───
53
+ "align-start-v": html`<sp-icon-align-top slot="icon"></sp-icon-align-top>`,
54
+ "align-end-v": html`<sp-icon-align-bottom slot="icon"></sp-icon-align-bottom>`,
55
+ "align-center-v": html`<sp-icon-align-middle slot="icon"></sp-icon-align-middle>`,
56
+ "align-stretch-v": html`<sp-icon-distribute-vertically
57
+ slot="icon"
58
+ ></sp-icon-distribute-vertically>`,
59
+ "align-baseline": html`<sp-icon-text-baseline-shift slot="icon"></sp-icon-text-baseline-shift>`,
60
+
61
+ // ─── justifyContent — horizontal distribution within container ───
62
+ "justify-start": html`<sp-icon-align-left slot="icon"></sp-icon-align-left>`,
63
+ "justify-end": html`<sp-icon-align-right slot="icon"></sp-icon-align-right>`,
64
+ "justify-center": html`<sp-icon-align-center slot="icon"></sp-icon-align-center>`,
65
+ "justify-between": html`<sp-icon-distribute-space-horiz
66
+ slot="icon"
67
+ ></sp-icon-distribute-space-horiz>`,
68
+ "justify-around": html`<sp-icon-distribute-horizontally
69
+ slot="icon"
70
+ ></sp-icon-distribute-horizontally>`,
71
+ "justify-evenly": html`<sp-icon-distribute-horizontal-center
72
+ slot="icon"
73
+ ></sp-icon-distribute-horizontal-center>`,
74
+
75
+ // ─── display mode icons ───
76
+ "display-flex": html`<sp-icon-view-column slot="icon"></sp-icon-view-column>`,
77
+ "display-grid": html`<sp-icon-view-grid slot="icon"></sp-icon-view-grid>`,
78
+ "display-block": html`<sp-icon-box slot="icon"></sp-icon-box>`,
79
+ "display-inline": html`<sp-icon-remove slot="icon"></sp-icon-remove>`,
80
+ "display-none": html`<sp-icon-visibility-off slot="icon"></sp-icon-visibility-off>`,
81
+ };
82
+
83
+ export default icons;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Jx-styled-combobox — Dual-mode styled combobox custom element.
3
+ *
4
+ * Renders as sp-picker when the current value matches a predefined option, or as a textfield +
5
+ * dropdown overlay (manual combobox) when it doesn't. Both modes share identical styled menu items,
6
+ * ensuring visual consistency.
7
+ *
8
+ * Usage: html`<jx-styled-combobox size="s" .value=${"italic"} placeholder="normal" .options=${[{
9
+ * value: "italic", label: "Italic", style: "font-style: italic" }]} @change=${handler}
10
+ * @input=${handler}
11
+ *
12
+ * > </jx-styled-combobox>`
13
+ *
14
+ * Options format: { value: string, label: string, style?: string } — menu item { divider: true } —
15
+ * menu divider
16
+ */
17
+
18
+ import { LitElement, html } from "lit";
19
+ import { live } from "lit/directives/live.js";
20
+
21
+ /** @typedef {{ value: string; label: string; style?: string } | { divider: true }} ComboOption */
22
+
23
+ export class JxStyledCombobox extends LitElement {
24
+ static properties = {
25
+ value: { type: String },
26
+ placeholder: { type: String },
27
+ size: { type: String },
28
+ options: { attribute: false },
29
+ };
30
+
31
+ constructor() {
32
+ super();
33
+ /** @type {string} */ this.value = "";
34
+ /** @type {string} */ this.placeholder = "";
35
+ /** @type {string} */ this.size = "s";
36
+ /** @type {ComboOption[]} */ this.options = [];
37
+ /** @type {string} */
38
+ this._menuId = "jx-combo-" + Math.random().toString(36).slice(2, 8);
39
+ }
40
+
41
+ /** No shadow DOM — render directly into light DOM */
42
+ createRenderRoot() {
43
+ return this;
44
+ }
45
+
46
+ /** Check if current value matches a predefined option */
47
+ get _isPicker() {
48
+ return (
49
+ !!this.value &&
50
+ this.options.some((/** @type {any} */ o) => !o.divider && o.value === this.value)
51
+ );
52
+ }
53
+
54
+ /** Get the selected option's style string for the picker button preview */
55
+ get _selectedStyle() {
56
+ if (!this._isPicker) return "";
57
+ const opt = this.options.find((/** @type {any} */ o) => !o.divider && o.value === this.value);
58
+ return /** @type {any} */ (opt)?.style || "";
59
+ }
60
+
61
+ /** Render menu items from options array */
62
+ _renderMenuItems() {
63
+ return this.options.map((/** @type {any} */ opt) =>
64
+ opt.divider
65
+ ? html`<sp-menu-divider></sp-menu-divider>`
66
+ : html`<sp-menu-item value=${opt.value} style=${opt.style || ""}
67
+ >${opt.label}</sp-menu-item
68
+ >`,
69
+ );
70
+ }
71
+
72
+ /** Picker mode: sp-picker @change handler */
73
+ _handlePickerChange(/** @type {any} */ e) {
74
+ e.stopPropagation(); // prevent sp-picker's raw event from reaching consumer
75
+ this.value = e.target.value;
76
+ this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
77
+ }
78
+
79
+ /** Combobox mode: sp-menu @change handler */
80
+ _handleMenuChange(/** @type {any} */ e) {
81
+ e.stopPropagation(); // prevent sp-menu's raw event from reaching consumer
82
+ if (!e.target.value) return;
83
+ this.value = e.target.value;
84
+ this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
85
+ }
86
+
87
+ /** Combobox mode: textfield @input handler */
88
+ _handleInput(/** @type {any} */ e) {
89
+ e.stopPropagation(); // prevent sp-textfield's raw event from reaching consumer
90
+ this.value = e.target.value;
91
+ this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
92
+ }
93
+
94
+ /** Set popover min-width to match trigger width (replicates sp-picker behavior) */
95
+ _setPopoverWidth(/** @type {any} */ e) {
96
+ const group = this.querySelector(".jx-combobox-group");
97
+ const w = group ? /** @type {HTMLElement} */ (group).offsetWidth : 0;
98
+ const popover = e.target.querySelector("sp-popover");
99
+ if (popover && w) popover.style.minWidth = `${w}px`;
100
+ }
101
+
102
+ render() {
103
+ if (this._isPicker) {
104
+ return html`
105
+ <sp-picker
106
+ class="jx-combobox-picker"
107
+ size=${this.size}
108
+ style=${this._selectedStyle}
109
+ .value=${live(this.value)}
110
+ @change=${this._handlePickerChange}
111
+ >
112
+ ${this._renderMenuItems()}
113
+ </sp-picker>
114
+ `;
115
+ }
116
+
117
+ return html`
118
+ <div class="jx-combobox-group" id=${this._menuId}>
119
+ <sp-textfield
120
+ size=${this.size}
121
+ placeholder=${this.placeholder}
122
+ .value=${live(this.value || "")}
123
+ @input=${this._handleInput}
124
+ @click=${(/** @type {Event} */ e) => e.stopPropagation()}
125
+ ></sp-textfield>
126
+ <sp-picker-button size=${this.size}></sp-picker-button>
127
+ <sp-overlay
128
+ trigger="${this._menuId}@click"
129
+ placement="bottom-start"
130
+ type="auto"
131
+ @sp-opened=${this._setPopoverWidth}
132
+ >
133
+ <sp-popover class="jx-combobox-popover">
134
+ <sp-menu size=${this.size} @change=${this._handleMenuChange}>
135
+ ${this._renderMenuItems()}
136
+ </sp-menu>
137
+ </sp-popover>
138
+ </sp-overlay>
139
+ </div>
140
+ `;
141
+ }
142
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Spectrum.js — Explicit Spectrum Web Component registration
3
+ *
4
+ * Bun's bundler tree-shakes bare side-effect imports (`import "..."`) because the Spectrum
5
+ * `sp-*.js` entry files export nothing — they only call `customElements.define()` as a side effect.
6
+ * To prevent the bundler from dropping them, we import the class constructors and collect them into
7
+ * an exported array that the main module references.
8
+ */
9
+
10
+ import { Theme } from "@spectrum-web-components/theme/src/Theme.js";
11
+ import themeSpectrumCSS from "@spectrum-web-components/theme/src/theme.css.js";
12
+ import themeDarkCSS from "@spectrum-web-components/theme/src/theme-dark.css.js";
13
+ import scaleMediumCSS from "@spectrum-web-components/theme/src/scale-medium.css.js";
14
+ import { Tabs } from "@spectrum-web-components/tabs/src/Tabs.js";
15
+ import { Tab } from "@spectrum-web-components/tabs/src/Tab.js";
16
+ import { TabPanel } from "@spectrum-web-components/tabs/src/TabPanel.js";
17
+ import { ActionButton } from "@spectrum-web-components/action-button/src/ActionButton.js";
18
+ import { ActionGroup } from "@spectrum-web-components/action-group/src/ActionGroup.js";
19
+ import { Search } from "@spectrum-web-components/search/src/Search.js";
20
+ import { Popover } from "@spectrum-web-components/popover/src/Popover.js";
21
+ import { Menu } from "@spectrum-web-components/menu/src/Menu.js";
22
+ import { MenuItem } from "@spectrum-web-components/menu/src/MenuItem.js";
23
+ import { MenuDivider } from "@spectrum-web-components/menu/src/MenuDivider.js";
24
+ import { MenuGroup } from "@spectrum-web-components/menu/src/MenuGroup.js";
25
+ import { Textfield } from "@spectrum-web-components/textfield/src/Textfield.js";
26
+ import { Swatch } from "@spectrum-web-components/swatch/src/Swatch.js";
27
+ import { SwatchGroup } from "@spectrum-web-components/swatch/src/SwatchGroup.js";
28
+ import { ColorArea } from "@spectrum-web-components/color-area/src/ColorArea.js";
29
+ import { ColorSlider } from "@spectrum-web-components/color-slider/src/ColorSlider.js";
30
+ import { ColorHandle } from "@spectrum-web-components/color-handle/src/ColorHandle.js";
31
+ import { NumberField } from "@spectrum-web-components/number-field/src/NumberField.js";
32
+ import { Picker } from "@spectrum-web-components/picker/src/Picker.js";
33
+ import { Combobox } from "@spectrum-web-components/combobox/src/Combobox.js";
34
+ import { FieldLabel } from "@spectrum-web-components/field-label/src/FieldLabel.js";
35
+ import { Checkbox } from "@spectrum-web-components/checkbox/src/Checkbox.js";
36
+ import { Switch as SpSwitch } from "@spectrum-web-components/switch/src/Switch.js";
37
+ import { Divider } from "@spectrum-web-components/divider/src/Divider.js";
38
+ import { Tooltip } from "@spectrum-web-components/tooltip/src/Tooltip.js";
39
+ import { Overlay } from "@spectrum-web-components/overlay/src/Overlay.js";
40
+ import { PickerButton } from "@spectrum-web-components/picker-button/src/PickerButton.js";
41
+ import { Accordion } from "@spectrum-web-components/accordion/src/Accordion.js";
42
+ import { AccordionItem } from "@spectrum-web-components/accordion/src/AccordionItem.js";
43
+ import { ActionBar } from "@spectrum-web-components/action-bar/src/ActionBar.js";
44
+
45
+ // Icons
46
+ import { IconFolder } from "@spectrum-web-components/icons-workflow/src/elements/IconFolder.js";
47
+ import { IconFolderOpen } from "@spectrum-web-components/icons-workflow/src/elements/IconFolderOpen.js";
48
+ import { IconDocument } from "@spectrum-web-components/icons-workflow/src/elements/IconDocument.js";
49
+ import { IconFileCode } from "@spectrum-web-components/icons-workflow/src/elements/IconFileCode.js";
50
+ import { IconFileTxt } from "@spectrum-web-components/icons-workflow/src/elements/IconFileTxt.js";
51
+ import { IconImage } from "@spectrum-web-components/icons-workflow/src/elements/IconImage.js";
52
+ import { IconRefresh } from "@spectrum-web-components/icons-workflow/src/elements/IconRefresh.js";
53
+ import { IconAdd } from "@spectrum-web-components/icons-workflow/src/elements/IconAdd.js";
54
+ import { IconLayers } from "@spectrum-web-components/icons-workflow/src/elements/IconLayers.js";
55
+ import { IconViewGrid } from "@spectrum-web-components/icons-workflow/src/elements/IconViewGrid.js";
56
+ import { IconBrackets } from "@spectrum-web-components/icons-workflow/src/elements/IconBrackets.js";
57
+ import { IconData } from "@spectrum-web-components/icons-workflow/src/elements/IconData.js";
58
+ import { IconChevronDown } from "@spectrum-web-components/icons-workflow/src/elements/IconChevronDown.js";
59
+ import { IconDelete } from "@spectrum-web-components/icons-workflow/src/elements/IconDelete.js";
60
+ import { IconClose } from "@spectrum-web-components/icons-workflow/src/elements/IconClose.js";
61
+ import { IconChevronRight } from "@spectrum-web-components/icons-workflow/src/elements/IconChevronRight.js";
62
+ import { IconEdit } from "@spectrum-web-components/icons-workflow/src/elements/IconEdit.js";
63
+ import { IconSaveFloppy } from "@spectrum-web-components/icons-workflow/src/elements/IconSaveFloppy.js";
64
+ import { IconUndo } from "@spectrum-web-components/icons-workflow/src/elements/IconUndo.js";
65
+ import { IconRedo } from "@spectrum-web-components/icons-workflow/src/elements/IconRedo.js";
66
+ import { IconDuplicate } from "@spectrum-web-components/icons-workflow/src/elements/IconDuplicate.js";
67
+ import { IconCopy } from "@spectrum-web-components/icons-workflow/src/elements/IconCopy.js";
68
+ import { IconExport } from "@spectrum-web-components/icons-workflow/src/elements/IconExport.js";
69
+ import { IconPreview } from "@spectrum-web-components/icons-workflow/src/elements/IconPreview.js";
70
+ import { IconCode } from "@spectrum-web-components/icons-workflow/src/elements/IconCode.js";
71
+ import { IconBrush } from "@spectrum-web-components/icons-workflow/src/elements/IconBrush.js";
72
+ import { IconBack } from "@spectrum-web-components/icons-workflow/src/elements/IconBack.js";
73
+ import { IconProperties } from "@spectrum-web-components/icons-workflow/src/elements/IconProperties.js";
74
+ import { IconEvent } from "@spectrum-web-components/icons-workflow/src/elements/IconEvent.js";
75
+
76
+ // Layout / alignment icons
77
+ import { IconArrowRight } from "@spectrum-web-components/icons-workflow/src/elements/IconArrowRight.js";
78
+ import { IconArrowLeft } from "@spectrum-web-components/icons-workflow/src/elements/IconArrowLeft.js";
79
+ import { IconArrowDown } from "@spectrum-web-components/icons-workflow/src/elements/IconArrowDown.js";
80
+ import { IconArrowUp } from "@spectrum-web-components/icons-workflow/src/elements/IconArrowUp.js";
81
+ import { IconTextAlignLeft } from "@spectrum-web-components/icons-workflow/src/elements/IconTextAlignLeft.js";
82
+ import { IconTextAlignCenter } from "@spectrum-web-components/icons-workflow/src/elements/IconTextAlignCenter.js";
83
+ import { IconTextAlignRight } from "@spectrum-web-components/icons-workflow/src/elements/IconTextAlignRight.js";
84
+ import { IconTextAlignJustify } from "@spectrum-web-components/icons-workflow/src/elements/IconTextAlignJustify.js";
85
+ import { IconAlignTop } from "@spectrum-web-components/icons-workflow/src/elements/IconAlignTop.js";
86
+ import { IconAlignBottom } from "@spectrum-web-components/icons-workflow/src/elements/IconAlignBottom.js";
87
+ import { IconAlignMiddle } from "@spectrum-web-components/icons-workflow/src/elements/IconAlignMiddle.js";
88
+ import { IconAlignLeft } from "@spectrum-web-components/icons-workflow/src/elements/IconAlignLeft.js";
89
+ import { IconAlignRight } from "@spectrum-web-components/icons-workflow/src/elements/IconAlignRight.js";
90
+ import { IconAlignCenter } from "@spectrum-web-components/icons-workflow/src/elements/IconAlignCenter.js";
91
+ import { IconDistributeSpaceHoriz } from "@spectrum-web-components/icons-workflow/src/elements/IconDistributeSpaceHoriz.js";
92
+ import { IconDistributeSpaceVert } from "@spectrum-web-components/icons-workflow/src/elements/IconDistributeSpaceVert.js";
93
+ import { IconDistributeHorizontally } from "@spectrum-web-components/icons-workflow/src/elements/IconDistributeHorizontally.js";
94
+ import { IconDistributeVertically } from "@spectrum-web-components/icons-workflow/src/elements/IconDistributeVertically.js";
95
+ import { IconDistributeBottomEdge } from "@spectrum-web-components/icons-workflow/src/elements/IconDistributeBottomEdge.js";
96
+ import { IconDistributeTopEdge } from "@spectrum-web-components/icons-workflow/src/elements/IconDistributeTopEdge.js";
97
+ import { IconDistributeHorizontalCenter } from "@spectrum-web-components/icons-workflow/src/elements/IconDistributeHorizontalCenter.js";
98
+ import { IconTextBaselineShift } from "@spectrum-web-components/icons-workflow/src/elements/IconTextBaselineShift.js";
99
+ import { IconFlipVertical } from "@spectrum-web-components/icons-workflow/src/elements/IconFlipVertical.js";
100
+ import { IconRemove } from "@spectrum-web-components/icons-workflow/src/elements/IconRemove.js";
101
+ import { IconViewColumn } from "@spectrum-web-components/icons-workflow/src/elements/IconViewColumn.js";
102
+ import { IconBox } from "@spectrum-web-components/icons-workflow/src/elements/IconBox.js";
103
+ import { IconVisibility } from "@spectrum-web-components/icons-workflow/src/elements/IconVisibility.js";
104
+ import { IconVisibilityOff } from "@spectrum-web-components/icons-workflow/src/elements/IconVisibilityOff.js";
105
+ import { IconArtboard } from "@spectrum-web-components/icons-workflow/src/elements/IconArtboard.js";
106
+
107
+ // Inline formatting icons
108
+ import { IconTextBold } from "@spectrum-web-components/icons-workflow/src/elements/IconTextBold.js";
109
+ import { IconTextItalic } from "@spectrum-web-components/icons-workflow/src/elements/IconTextItalic.js";
110
+ import { IconTextUnderline } from "@spectrum-web-components/icons-workflow/src/elements/IconTextUnderline.js";
111
+ import { IconTextStrikethrough } from "@spectrum-web-components/icons-workflow/src/elements/IconTextStrikethrough.js";
112
+ import { IconTextSuperscript } from "@spectrum-web-components/icons-workflow/src/elements/IconTextSuperscript.js";
113
+ import { IconTextSubscript } from "@spectrum-web-components/icons-workflow/src/elements/IconTextSubscript.js";
114
+ import { IconLink } from "@spectrum-web-components/icons-workflow/src/elements/IconLink.js";
115
+
116
+ // Custom studio components
117
+ import { JxStyledCombobox } from "./jx-styled-combobox.js";
118
+
119
+ // UI icons (used internally by Spectrum components like accordion, picker, combobox)
120
+ import { IconChevron100 } from "@spectrum-web-components/icons-ui/src/elements/IconChevron100.js";
121
+
122
+ // Register all components. Using defineElement from Spectrum's base package
123
+ // ensures duplicate registration is handled gracefully.
124
+ import { defineElement } from "@spectrum-web-components/base/src/define-element.js";
125
+
126
+ const components = [
127
+ ["sp-theme", Theme],
128
+ ["sp-tabs", Tabs],
129
+ ["sp-tab", Tab],
130
+ ["sp-tab-panel", TabPanel],
131
+ ["sp-action-button", ActionButton],
132
+ ["sp-action-group", ActionGroup],
133
+ ["sp-search", Search],
134
+ ["sp-popover", Popover],
135
+ ["sp-menu", Menu],
136
+ ["sp-menu-item", MenuItem],
137
+ ["sp-menu-divider", MenuDivider],
138
+ ["sp-menu-group", MenuGroup],
139
+ ["sp-textfield", Textfield],
140
+ ["sp-swatch", Swatch],
141
+ ["sp-swatch-group", SwatchGroup],
142
+ ["sp-color-area", ColorArea],
143
+ ["sp-color-slider", ColorSlider],
144
+ ["sp-color-handle", ColorHandle],
145
+ ["sp-number-field", NumberField],
146
+ ["sp-picker", Picker],
147
+ ["sp-combobox", Combobox],
148
+ ["sp-field-label", FieldLabel],
149
+ ["sp-checkbox", Checkbox],
150
+ ["sp-switch", SpSwitch],
151
+ ["sp-divider", Divider],
152
+ ["sp-tooltip", Tooltip],
153
+ ["sp-overlay", Overlay],
154
+ ["sp-picker-button", PickerButton],
155
+ ["sp-accordion", Accordion],
156
+ ["sp-accordion-item", AccordionItem],
157
+ ["sp-action-bar", ActionBar],
158
+ ["sp-icon-folder", IconFolder],
159
+ ["sp-icon-folder-open", IconFolderOpen],
160
+ ["sp-icon-document", IconDocument],
161
+ ["sp-icon-file-code", IconFileCode],
162
+ ["sp-icon-file-txt", IconFileTxt],
163
+ ["sp-icon-image", IconImage],
164
+ ["sp-icon-refresh", IconRefresh],
165
+ ["sp-icon-add", IconAdd],
166
+ ["sp-icon-layers", IconLayers],
167
+ ["sp-icon-view-grid", IconViewGrid],
168
+ ["sp-icon-brackets", IconBrackets],
169
+ ["sp-icon-data", IconData],
170
+ ["sp-icon-chevron-down", IconChevronDown],
171
+ ["sp-icon-chevron-right", IconChevronRight],
172
+ ["sp-icon-delete", IconDelete],
173
+ ["sp-icon-close", IconClose],
174
+ ["sp-icon-edit", IconEdit],
175
+ ["sp-icon-save-floppy", IconSaveFloppy],
176
+ ["sp-icon-undo", IconUndo],
177
+ ["sp-icon-redo", IconRedo],
178
+ ["sp-icon-duplicate", IconDuplicate],
179
+ ["sp-icon-copy", IconCopy],
180
+ ["sp-icon-export", IconExport],
181
+ ["sp-icon-preview", IconPreview],
182
+ ["sp-icon-code", IconCode],
183
+ ["sp-icon-brush", IconBrush],
184
+ ["sp-icon-back", IconBack],
185
+ ["sp-icon-properties", IconProperties],
186
+ ["sp-icon-event", IconEvent],
187
+ ["sp-icon-arrow-right", IconArrowRight],
188
+ ["sp-icon-arrow-left", IconArrowLeft],
189
+ ["sp-icon-arrow-down", IconArrowDown],
190
+ ["sp-icon-arrow-up", IconArrowUp],
191
+ ["sp-icon-text-align-left", IconTextAlignLeft],
192
+ ["sp-icon-text-align-center", IconTextAlignCenter],
193
+ ["sp-icon-text-align-right", IconTextAlignRight],
194
+ ["sp-icon-text-align-justify", IconTextAlignJustify],
195
+ ["sp-icon-align-top", IconAlignTop],
196
+ ["sp-icon-align-bottom", IconAlignBottom],
197
+ ["sp-icon-align-middle", IconAlignMiddle],
198
+ ["sp-icon-align-left", IconAlignLeft],
199
+ ["sp-icon-align-right", IconAlignRight],
200
+ ["sp-icon-align-center", IconAlignCenter],
201
+ ["sp-icon-distribute-space-horiz", IconDistributeSpaceHoriz],
202
+ ["sp-icon-distribute-space-vert", IconDistributeSpaceVert],
203
+ ["sp-icon-distribute-horizontally", IconDistributeHorizontally],
204
+ ["sp-icon-distribute-vertically", IconDistributeVertically],
205
+ ["sp-icon-distribute-bottom-edge", IconDistributeBottomEdge],
206
+ ["sp-icon-distribute-top-edge", IconDistributeTopEdge],
207
+ ["sp-icon-distribute-horizontal-center", IconDistributeHorizontalCenter],
208
+ ["sp-icon-text-baseline-shift", IconTextBaselineShift],
209
+ ["sp-icon-flip-vertical", IconFlipVertical],
210
+ ["sp-icon-remove", IconRemove],
211
+ ["sp-icon-view-column", IconViewColumn],
212
+ ["sp-icon-box", IconBox],
213
+ ["sp-icon-visibility", IconVisibility],
214
+ ["sp-icon-visibility-off", IconVisibilityOff],
215
+ ["sp-icon-artboard", IconArtboard],
216
+ ["sp-icon-text-bold", IconTextBold],
217
+ ["sp-icon-text-italic", IconTextItalic],
218
+ ["sp-icon-text-underline", IconTextUnderline],
219
+ ["sp-icon-text-strikethrough", IconTextStrikethrough],
220
+ ["sp-icon-text-superscript", IconTextSuperscript],
221
+ ["sp-icon-text-subscript", IconTextSubscript],
222
+ ["sp-icon-link", IconLink],
223
+ // UI icons (internal component chrome)
224
+ ["sp-icon-chevron100", IconChevron100],
225
+ // Custom studio components
226
+ ["jx-styled-combobox", JxStyledCombobox],
227
+ ];
228
+
229
+ for (const [tag, ctor] of /** @type {[string, CustomElementConstructor][]} */ (components)) {
230
+ if (!customElements.get(tag)) defineElement(tag, /** @type {any} */ (ctor));
231
+ }
232
+
233
+ // Register theme fragments (these are also side-effect-only in the original modules)
234
+ Theme.registerThemeFragment("spectrum", "system", themeSpectrumCSS);
235
+ Theme.registerThemeFragment("dark", "color", themeDarkCSS);
236
+ Theme.registerThemeFragment("medium", "scale", scaleMediumCSS);
237
+
238
+ export { components };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Studio-utils.js — Pure utility functions extracted from studio.js
3
+ *
4
+ * These are all side-effect-free functions used by style/properties/events panels.
5
+ */
6
+
7
+ /**
8
+ * CamelCase → kebab-case for inline style attributes
9
+ *
10
+ * @param {string} str
11
+ * @returns {string}
12
+ */
13
+ export function camelToKebab(str) {
14
+ return str.replace(/[A-Z]/g, (/** @type {string} */ c) => "-" + c.toLowerCase());
15
+ }
16
+
17
+ /**
18
+ * Convert camelCase property name to "Title Case" label (e.g. "backgroundColor" → "Background
19
+ * Color")
20
+ *
21
+ * @param {string} prop
22
+ * @returns {string}
23
+ */
24
+ export function camelToLabel(prop) {
25
+ return prop
26
+ .replace(/([A-Z])/g, " $1")
27
+ .replace(/^./, (/** @type {string} */ c) => c.toUpperCase());
28
+ }
29
+
30
+ /**
31
+ * Convert a kebab-case CSS value to Title Case for picker display (e.g. "border-box" → "Border
32
+ * Box")
33
+ *
34
+ * @param {string} val
35
+ * @returns {string}
36
+ */
37
+ export function kebabToLabel(val) {
38
+ return val.replace(
39
+ /(^|-)(\w)/g,
40
+ (/** @type {string} */ _, /** @type {string} */ sep, /** @type {string} */ c) =>
41
+ (sep ? " " : "") + c.toUpperCase(),
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Get display label from metadata entry or prop name
47
+ *
48
+ * @param {any} entry
49
+ * @param {string} prop
50
+ * @returns {string}
51
+ */
52
+ export function propLabel(entry, prop) {
53
+ return entry?.$label || camelToLabel(prop);
54
+ }
55
+
56
+ /**
57
+ * Label for HTML attributes — handles kebab-case (aria-label → "Aria Label")
58
+ *
59
+ * @param {any} entry
60
+ * @param {string} attr
61
+ * @returns {string}
62
+ */
63
+ export function attrLabel(entry, attr) {
64
+ if (entry?.$label) return entry.$label;
65
+ if (attr.includes("-"))
66
+ return attr.replace(
67
+ /(^|-)(\w)/g,
68
+ (/** @type {string} */ _, /** @type {string} */ sep, /** @type {string} */ c) =>
69
+ (sep ? " " : "") + c.toUpperCase(),
70
+ );
71
+ return camelToLabel(attr);
72
+ }
73
+
74
+ /**
75
+ * Abbreviate a CSS value for button-group display
76
+ *
77
+ * @param {string} val
78
+ * @returns {string}
79
+ */
80
+ export function abbreviateValue(val) {
81
+ /** @type {Record<string, string>} */
82
+ const map = {
83
+ inline: "inl",
84
+ "inline-block": "i-blk",
85
+ "inline-flex": "i-flx",
86
+ "inline-grid": "i-grd",
87
+ contents: "cnt",
88
+ "flow-root": "flow",
89
+ nowrap: "no-wr",
90
+ "wrap-reverse": "wr-rev",
91
+ "flex-start": "start",
92
+ "flex-end": "end",
93
+ "space-between": "betw",
94
+ "space-around": "arnd",
95
+ "space-evenly": "even",
96
+ stretch: "str",
97
+ baseline: "base",
98
+ normal: "norm",
99
+ "row-reverse": "row-r",
100
+ "column-reverse": "col-r",
101
+ column: "col",
102
+ };
103
+ return map[val] || val;
104
+ }
105
+
106
+ /**
107
+ * Determine input widget type from a css-meta entry
108
+ *
109
+ * @param {any} entry
110
+ * @returns {string}
111
+ */
112
+ export function inferInputType(entry) {
113
+ if (entry.$shorthand === true) return "shorthand";
114
+ if (entry.$input === "button-group") return "button-group";
115
+ if (entry.format === "color") return "color";
116
+ if (entry.$units !== undefined) return "number-unit";
117
+ if (entry.type === "number") return "number";
118
+ if (Array.isArray(entry.enum)) return "select";
119
+ if (Array.isArray(entry.examples) || Array.isArray(entry.presets)) return "combobox";
120
+ return "text";
121
+ }
122
+
123
+ /**
124
+ * Convert a human-readable name to a CSS variable name. E.g. "Geometric Humanist" →
125
+ * "--font-geometric-humanist"
126
+ *
127
+ * @param {string} name
128
+ * @param {string} prefix - E.g. "--font-"
129
+ * @returns {string}
130
+ */
131
+ export function friendlyNameToVar(name, prefix) {
132
+ const slug = name
133
+ .trim()
134
+ .toLowerCase()
135
+ .replace(/[^a-z0-9\s-]/g, "")
136
+ .replace(/\s+/g, "-")
137
+ .replace(/-+/g, "-")
138
+ .replace(/^-|-$/g, "");
139
+ if (!slug) return "";
140
+ return `${prefix}${slug}`;
141
+ }
142
+
143
+ /**
144
+ * Convert a CSS variable name back to a display name. E.g. "--font-geometric-humanist" with prefix
145
+ * "--font-" → "Geometric Humanist"
146
+ *
147
+ * @param {string} varName
148
+ * @param {string} prefix
149
+ * @returns {string}
150
+ */
151
+ export function varDisplayName(varName, prefix) {
152
+ return (
153
+ varName
154
+ .replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "")
155
+ .replace(/^--/, "")
156
+ .replace(/-/g, " ")
157
+ .replace(/\b\w/g, (/** @type {any} */ c) => c.toUpperCase()) || varName
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Parse a CEM type.text string into a structured descriptor.
163
+ *
164
+ * @param {string | undefined | null} typeText
165
+ * @returns {{ kind: "combobox"; options: string[] }
166
+ * | { kind: "boolean" }
167
+ * | { kind: "number" }
168
+ * | { kind: "text" }}
169
+ */
170
+ export function parseCemType(typeText) {
171
+ if (!typeText) return { kind: "text" };
172
+ const t = typeText
173
+ .trim()
174
+ .replace(/\s*\|\s*undefined\b/g, "")
175
+ .trim();
176
+ if (t === "boolean") return { kind: "boolean" };
177
+ if (t === "number") return { kind: "number" };
178
+ // Detect enum: "'a' | 'b' | 'c'" — pipe-separated quoted literals
179
+ const enumMatch = t.match(/^'[^']*'(\s*\|\s*'[^']*')+$/);
180
+ if (enumMatch) {
181
+ const options = [...t.matchAll(/'([^']*)'/g)].map((m) => m[1]);
182
+ return { kind: "combobox", options };
183
+ }
184
+ return { kind: "text" };
185
+ }