@jxsuite/studio 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/dist/studio.js +162163 -151534
- package/dist/studio.js.map +99 -27
- package/package.json +2 -2
- package/src/editor/insertion-helper.js +301 -0
- package/src/editor/slash-menu.js +111 -37
- package/src/markdown/md-convert.js +18 -16
- package/src/panels/activity-bar.js +22 -0
- package/src/panels/block-action-bar.js +436 -0
- package/src/panels/dnd.js +332 -0
- package/src/panels/editors.js +196 -0
- package/src/panels/elements-panel.js +148 -0
- package/src/panels/git-panel.js +280 -0
- package/src/panels/layers-panel.js +270 -0
- package/src/panels/left-panel.js +142 -0
- package/src/panels/properties-panel.js +1340 -0
- package/src/panels/right-panel.js +5 -3
- package/src/panels/shared.js +74 -0
- package/src/panels/style-inputs.js +176 -0
- package/src/panels/style-panel.js +651 -0
- package/src/panels/style-utils.js +193 -0
- package/src/panels/stylebook-layers-panel.js +103 -0
- package/src/panels/stylebook-panel.js +1052 -0
- package/src/platforms/devserver.js +113 -0
- package/src/state.js +7 -0
- package/src/studio.js +92 -4820
- package/src/ui/spectrum.js +4 -0
|
@@ -11,6 +11,8 @@ import { eventsSidebarTemplate } from "./events-panel.js";
|
|
|
11
11
|
import { isCustomElementDoc } from "./signals-panel.js";
|
|
12
12
|
import { ensureLitState } from "./shared.js";
|
|
13
13
|
import { isColorPopoverOpen } from "../ui/color-selector.js";
|
|
14
|
+
import { renderStylePanelTemplate } from "./style-panel.js";
|
|
15
|
+
import { renderPropertiesPanelTemplate } from "./properties-panel.js";
|
|
14
16
|
|
|
15
17
|
/** @type {any} */
|
|
16
18
|
let _ctx = null;
|
|
@@ -21,7 +23,7 @@ let _unsub = null;
|
|
|
21
23
|
/**
|
|
22
24
|
* Mount the right panel.
|
|
23
25
|
*
|
|
24
|
-
* @param {any} ctx — { propertiesSidebarTemplate,
|
|
26
|
+
* @param {any} ctx — { propertiesSidebarTemplate, getCanvasMode, renderCanvas,
|
|
25
27
|
* updateForcedPseudoPreview }
|
|
26
28
|
*/
|
|
27
29
|
export function mount(ctx) {
|
|
@@ -109,7 +111,7 @@ function rightPanelTemplate() {
|
|
|
109
111
|
/** @type {any} */
|
|
110
112
|
let bodyT = nothing;
|
|
111
113
|
if (tab === "properties") {
|
|
112
|
-
bodyT = _ctx.
|
|
114
|
+
bodyT = renderPropertiesPanelTemplate({ navigateToComponent: _ctx.navigateToComponent });
|
|
113
115
|
} else if (tab === "events") {
|
|
114
116
|
bodyT = eventsSidebarTemplate(S, {
|
|
115
117
|
isCustomElementDoc: () => isCustomElementDoc(S),
|
|
@@ -117,7 +119,7 @@ function rightPanelTemplate() {
|
|
|
117
119
|
});
|
|
118
120
|
} else if (tab === "style") {
|
|
119
121
|
try {
|
|
120
|
-
bodyT = _ctx.
|
|
122
|
+
bodyT = renderStylePanelTemplate({ getCanvasMode: _ctx.getCanvasMode });
|
|
121
123
|
} catch (/** @type {any} */ e) {
|
|
122
124
|
console.error("[renderStylePanelTemplate]", e);
|
|
123
125
|
}
|
package/src/panels/shared.js
CHANGED
|
@@ -39,3 +39,77 @@ export function ensureLitState(container) {
|
|
|
39
39
|
delete container["_$litPart$"];
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
export const unsafeTags = new Set(["script", "style", "link", "iframe", "object", "embed"]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate a sensible default Jx node for a given tag name.
|
|
47
|
+
*
|
|
48
|
+
* @param {any} tag
|
|
49
|
+
*/
|
|
50
|
+
export function defaultDef(tag) {
|
|
51
|
+
/** @type {any} */
|
|
52
|
+
const def = { tagName: tag };
|
|
53
|
+
if (/^h[1-6]$/.test(tag)) def.textContent = "Heading";
|
|
54
|
+
else if (tag === "p") def.textContent = "Paragraph text";
|
|
55
|
+
else if (
|
|
56
|
+
tag === "span" ||
|
|
57
|
+
tag === "strong" ||
|
|
58
|
+
tag === "em" ||
|
|
59
|
+
tag === "small" ||
|
|
60
|
+
tag === "mark" ||
|
|
61
|
+
tag === "code" ||
|
|
62
|
+
tag === "abbr" ||
|
|
63
|
+
tag === "q" ||
|
|
64
|
+
tag === "sub" ||
|
|
65
|
+
tag === "sup" ||
|
|
66
|
+
tag === "time"
|
|
67
|
+
)
|
|
68
|
+
def.textContent = "Text";
|
|
69
|
+
else if (tag === "a") {
|
|
70
|
+
def.textContent = "Link";
|
|
71
|
+
def.attributes = { href: "#" };
|
|
72
|
+
} else if (tag === "button") def.textContent = "Button";
|
|
73
|
+
else if (tag === "label") def.textContent = "Label";
|
|
74
|
+
else if (tag === "legend") def.textContent = "Legend";
|
|
75
|
+
else if (tag === "caption") def.textContent = "Caption";
|
|
76
|
+
else if (tag === "summary") def.textContent = "Summary";
|
|
77
|
+
else if (
|
|
78
|
+
tag === "li" ||
|
|
79
|
+
tag === "dt" ||
|
|
80
|
+
tag === "dd" ||
|
|
81
|
+
tag === "th" ||
|
|
82
|
+
tag === "td" ||
|
|
83
|
+
tag === "option"
|
|
84
|
+
)
|
|
85
|
+
def.textContent = "Item";
|
|
86
|
+
else if (tag === "blockquote") def.textContent = "Quote";
|
|
87
|
+
else if (tag === "pre") def.textContent = "Preformatted text";
|
|
88
|
+
else if (tag === "input") def.attributes = { type: "text", placeholder: "Enter text..." };
|
|
89
|
+
else if (tag === "img") def.attributes = { src: "", alt: "Image" };
|
|
90
|
+
else if (tag === "iframe") def.attributes = { src: "" };
|
|
91
|
+
else if (tag === "select") def.children = [{ tagName: "option", textContent: "Option 1" }];
|
|
92
|
+
else if (tag === "ul" || tag === "ol") def.children = [{ tagName: "li", textContent: "Item" }];
|
|
93
|
+
else if (tag === "dl")
|
|
94
|
+
def.children = [
|
|
95
|
+
{ tagName: "dt", textContent: "Term" },
|
|
96
|
+
{ tagName: "dd", textContent: "Definition" },
|
|
97
|
+
];
|
|
98
|
+
else if (tag === "table")
|
|
99
|
+
def.children = [
|
|
100
|
+
{
|
|
101
|
+
tagName: "thead",
|
|
102
|
+
children: [{ tagName: "tr", children: [{ tagName: "th", textContent: "Header" }] }],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
tagName: "tbody",
|
|
106
|
+
children: [{ tagName: "tr", children: [{ tagName: "td", textContent: "Cell" }] }],
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
else if (tag === "details")
|
|
110
|
+
def.children = [
|
|
111
|
+
{ tagName: "summary", textContent: "Summary" },
|
|
112
|
+
{ tagName: "p", textContent: "Detail content" },
|
|
113
|
+
];
|
|
114
|
+
return def;
|
|
115
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/** Style input widgets — keyword, select, combobox, and font renderers for the style panel. */
|
|
2
|
+
|
|
3
|
+
import { html } from "lit-html";
|
|
4
|
+
import { live } from "lit-html/directives/live.js";
|
|
5
|
+
import { getState, update, updateStyle, debouncedStyleCommit } from "../store.js";
|
|
6
|
+
import { widgetForType as _widgetForType } from "../ui/widgets.js";
|
|
7
|
+
import { kebabToLabel, friendlyNameToVar, varDisplayName } from "../utils/studio-utils.js";
|
|
8
|
+
import {
|
|
9
|
+
TYPO_PREVIEW_PROPS,
|
|
10
|
+
currentFontFamily,
|
|
11
|
+
getFontVars,
|
|
12
|
+
getCssInitialMap,
|
|
13
|
+
camelToKebab,
|
|
14
|
+
} from "./style-utils.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Dual-mode keyword input — shared by select (enum) and combobox (examples) widgets.
|
|
18
|
+
*
|
|
19
|
+
* @param {any} options @param {any} prop @param {any} value @param {any} onChange
|
|
20
|
+
*/
|
|
21
|
+
export function renderKeywordInput(options, prop, value, onChange) {
|
|
22
|
+
const cssInitialMap = getCssInitialMap();
|
|
23
|
+
const isTypoPreview = TYPO_PREVIEW_PROPS.has(prop) || prop === "fontWeight";
|
|
24
|
+
const font = isTypoPreview ? currentFontFamily() : "";
|
|
25
|
+
const cssProp = isTypoPreview ? camelToKebab(prop) : "";
|
|
26
|
+
|
|
27
|
+
const comboOptions = options.map((/** @type {any} */ v) => {
|
|
28
|
+
const label = v.includes("-")
|
|
29
|
+
? kebabToLabel(v)
|
|
30
|
+
: v.replace(/^./, (/** @type {any} */ c) => c.toUpperCase());
|
|
31
|
+
const style = isTypoPreview ? `${cssProp}: ${v};${font ? ` font-family: ${font}` : ""}` : "";
|
|
32
|
+
return { value: v, label, style };
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return html`<jx-value-selector
|
|
36
|
+
size="s"
|
|
37
|
+
.value=${value || ""}
|
|
38
|
+
placeholder=${cssInitialMap.get(prop) || ""}
|
|
39
|
+
.options=${comboOptions}
|
|
40
|
+
@change=${(/** @type {any} */ e) => onChange(e.target.value)}
|
|
41
|
+
@input=${debouncedStyleCommit(`kw:${prop}`, 400, (/** @type {any} */ e) =>
|
|
42
|
+
onChange(e.target.value),
|
|
43
|
+
)}
|
|
44
|
+
></jx-value-selector>`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @param {any} entry @param {any} prop @param {any} value @param {any} onChange */
|
|
48
|
+
export function renderSelectInput(entry, prop, value, onChange) {
|
|
49
|
+
return renderKeywordInput(entry.enum || [], prop, value, onChange);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {any} preset @param {any} onChange */
|
|
53
|
+
function handleFontPresetSelection(preset, onChange) {
|
|
54
|
+
const S = getState();
|
|
55
|
+
const varName = friendlyNameToVar(preset.title, "--font-");
|
|
56
|
+
if (!S.document?.style?.[varName]) {
|
|
57
|
+
update(updateStyle(S, [], varName, preset.value));
|
|
58
|
+
}
|
|
59
|
+
onChange(`var(${varName})`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @param {any} val @param {any} presets @param {any} onChange */
|
|
63
|
+
function handleFontSelection(val, presets, onChange) {
|
|
64
|
+
if (!val) return;
|
|
65
|
+
if (val.startsWith("__preset__:")) {
|
|
66
|
+
const title = val.slice("__preset__:".length);
|
|
67
|
+
const preset = presets.find((/** @type {any} */ p) => p.title === title);
|
|
68
|
+
if (preset) handleFontPresetSelection(preset, onChange);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (val.startsWith("--")) {
|
|
72
|
+
onChange(`var(${val})`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const preset = presets.find((/** @type {any} */ p) => p.title === val);
|
|
76
|
+
if (preset) {
|
|
77
|
+
handleFontPresetSelection(preset, onChange);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const fontVars = getFontVars();
|
|
81
|
+
const matchedVar = fontVars.find(
|
|
82
|
+
(/** @type {any} */ fv) => varDisplayName(fv.name, "--font-") === val,
|
|
83
|
+
);
|
|
84
|
+
if (matchedVar) {
|
|
85
|
+
onChange(`var(${matchedVar.name})`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
onChange(val);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build font options array for jx-value-selector.
|
|
93
|
+
*
|
|
94
|
+
* @param {any[]} fontVars @param {any[]} presets
|
|
95
|
+
* @returns {{ value: string; label: string; style: string }[] | { divider: true }[]}
|
|
96
|
+
*/
|
|
97
|
+
export function buildFontOptions(fontVars, presets) {
|
|
98
|
+
/** @type {any[]} */
|
|
99
|
+
const opts = fontVars.map((/** @type {any} */ fv) => ({
|
|
100
|
+
value: fv.name,
|
|
101
|
+
label: varDisplayName(fv.name, "--font-"),
|
|
102
|
+
style: `font-family: ${fv.value}`,
|
|
103
|
+
}));
|
|
104
|
+
const unadded = presets.filter(
|
|
105
|
+
(/** @type {any} */ p) =>
|
|
106
|
+
!fontVars.some((/** @type {any} */ fv) => fv.name === friendlyNameToVar(p.title, "--font-")),
|
|
107
|
+
);
|
|
108
|
+
if (unadded.length > 0 && opts.length > 0) opts.push({ divider: true });
|
|
109
|
+
for (const p of unadded) {
|
|
110
|
+
opts.push({
|
|
111
|
+
value: "__preset__:" + p.title,
|
|
112
|
+
label: p.title,
|
|
113
|
+
style: `font-family: ${p.value}`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return opts;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @param {any} entry @param {any} prop @param {any} value @param {any} onChange */
|
|
120
|
+
export function renderComboboxInput(entry, prop, value, onChange) {
|
|
121
|
+
const cssInitialMap = getCssInitialMap();
|
|
122
|
+
const fontVars = prop === "fontFamily" ? getFontVars() : [];
|
|
123
|
+
const presets = entry.presets || [];
|
|
124
|
+
const examples = entry.examples || [];
|
|
125
|
+
|
|
126
|
+
if (prop === "fontFamily") {
|
|
127
|
+
const varMatch = typeof value === "string" && value.match(/^var\((--[^)]+)\)$/);
|
|
128
|
+
const comboValue = varMatch ? varMatch[1] : value || "";
|
|
129
|
+
const fontOptions = buildFontOptions(fontVars, presets);
|
|
130
|
+
return html`<jx-value-selector
|
|
131
|
+
size="s"
|
|
132
|
+
.value=${comboValue}
|
|
133
|
+
placeholder=${cssInitialMap.get("fontFamily") || ""}
|
|
134
|
+
.options=${fontOptions}
|
|
135
|
+
@change=${(/** @type {any} */ e) => handleFontSelection(e.target.value, presets, onChange)}
|
|
136
|
+
@input=${debouncedStyleCommit("combo:fontFamily", 400, (/** @type {any} */ e) =>
|
|
137
|
+
onChange(e.target.value),
|
|
138
|
+
)}
|
|
139
|
+
></jx-value-selector>`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (examples.length > 0) {
|
|
143
|
+
return renderKeywordInput(examples, prop, value, onChange);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return html`
|
|
147
|
+
<sp-textfield
|
|
148
|
+
size="s"
|
|
149
|
+
placeholder=${cssInitialMap.get(prop) || ""}
|
|
150
|
+
.value=${live(value || "")}
|
|
151
|
+
@input=${debouncedStyleCommit(`combo:${prop}`, 400, (/** @type {any} */ e) =>
|
|
152
|
+
onChange(e.target.value),
|
|
153
|
+
)}
|
|
154
|
+
></sp-textfield>
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Style-aware widgetForType — wraps the generic widget renderer with style-specific select/combobox
|
|
160
|
+
* inputs and CSS initial-value placeholders.
|
|
161
|
+
*/
|
|
162
|
+
export function widgetForType(
|
|
163
|
+
/** @type {any} */ type,
|
|
164
|
+
/** @type {any} */ entry,
|
|
165
|
+
/** @type {any} */ prop,
|
|
166
|
+
/** @type {any} */ value,
|
|
167
|
+
/** @type {any} */ onCommit,
|
|
168
|
+
/** @type {any} */ opts = {},
|
|
169
|
+
) {
|
|
170
|
+
const cssInitialMap = getCssInitialMap();
|
|
171
|
+
return _widgetForType(type, entry, prop, value, onCommit, {
|
|
172
|
+
placeholder: opts.placeholder || cssInitialMap.get(prop) || "",
|
|
173
|
+
renderSelect: renderSelectInput,
|
|
174
|
+
renderCombobox: renderComboboxInput,
|
|
175
|
+
});
|
|
176
|
+
}
|