@jxsuite/studio 0.19.0 → 0.21.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.
Files changed (99) hide show
  1. package/dist/studio.css +98 -98
  2. package/dist/studio.js +13476 -12330
  3. package/dist/studio.js.map +152 -148
  4. package/dist/workers/editor.worker.js +79 -79
  5. package/dist/workers/json.worker.js +109 -109
  6. package/dist/workers/ts.worker.js +82 -82
  7. package/package.json +19 -7
  8. package/src/browse/browse-modal.js +16 -13
  9. package/src/browse/browse.js +44 -35
  10. package/src/canvas/canvas-diff.js +24 -16
  11. package/src/canvas/canvas-helpers.js +24 -19
  12. package/src/canvas/canvas-live-render.js +113 -39
  13. package/src/canvas/canvas-render.js +119 -73
  14. package/src/canvas/canvas-utils.js +51 -21
  15. package/src/canvas/nested-site-style.js +50 -0
  16. package/src/editor/component-inline-edit.js +14 -13
  17. package/src/editor/content-inline-edit.js +22 -10
  18. package/src/editor/context-menu.js +28 -14
  19. package/src/editor/convert-targets.js +60 -0
  20. package/src/editor/convert-to-component.js +20 -19
  21. package/src/editor/convert-to-repeater.js +226 -0
  22. package/src/editor/inline-edit.js +69 -28
  23. package/src/editor/insertion-helper.js +16 -12
  24. package/src/editor/shortcuts.js +14 -4
  25. package/src/editor/slash-menu.js +40 -19
  26. package/src/files/components.js +1 -1
  27. package/src/files/file-ops.js +37 -16
  28. package/src/files/files.js +322 -68
  29. package/src/github/github-auth.js +122 -0
  30. package/src/github/github-publish.js +126 -0
  31. package/src/markdown/md-convert.js +172 -107
  32. package/src/new-project/new-project-modal.js +204 -0
  33. package/src/panels/activity-bar.js +27 -25
  34. package/src/panels/ai-panel.js +406 -0
  35. package/src/panels/block-action-bar.js +77 -28
  36. package/src/panels/canvas-dnd.js +8 -2
  37. package/src/panels/data-explorer.js +19 -13
  38. package/src/panels/dnd.js +144 -92
  39. package/src/panels/editors.js +55 -35
  40. package/src/panels/elements-panel.js +33 -19
  41. package/src/panels/events-panel.js +15 -10
  42. package/src/panels/git-panel.js +475 -99
  43. package/src/panels/head-panel.js +121 -81
  44. package/src/panels/imports-panel.js +94 -65
  45. package/src/panels/layers-panel.js +64 -43
  46. package/src/panels/left-panel.js +105 -48
  47. package/src/panels/overlays.js +1 -0
  48. package/src/panels/panel-events.js +45 -30
  49. package/src/panels/preview-render.js +23 -18
  50. package/src/panels/properties-panel.js +378 -257
  51. package/src/panels/pseudo-preview.js +2 -2
  52. package/src/panels/quick-search.js +11 -9
  53. package/src/panels/right-panel.js +42 -16
  54. package/src/panels/shared.js +4 -4
  55. package/src/panels/signals-panel.js +415 -166
  56. package/src/panels/statusbar.js +10 -6
  57. package/src/panels/style-inputs.js +67 -35
  58. package/src/panels/style-panel.js +239 -134
  59. package/src/panels/style-utils.js +34 -16
  60. package/src/panels/stylebook-layers-panel.js +12 -10
  61. package/src/panels/stylebook-panel.js +134 -66
  62. package/src/panels/tab-strip.js +5 -2
  63. package/src/panels/toolbar.js +333 -109
  64. package/src/panels/welcome-screen.js +121 -0
  65. package/src/platform.js +2 -4
  66. package/src/platforms/devserver.js +113 -7
  67. package/src/resize-edges.js +98 -0
  68. package/src/services/cem-export.js +12 -7
  69. package/src/services/code-services.js +30 -12
  70. package/src/settings/content-types-editor.js +8 -8
  71. package/src/settings/css-vars-editor.js +30 -24
  72. package/src/settings/defs-editor.js +12 -7
  73. package/src/settings/general-settings.js +85 -3
  74. package/src/settings/head-editor.js +30 -24
  75. package/src/settings/schema-field-ui.js +58 -39
  76. package/src/settings/settings-modal.js +12 -9
  77. package/src/site-context.js +41 -31
  78. package/src/state.js +70 -29
  79. package/src/store.js +36 -35
  80. package/src/studio.js +120 -90
  81. package/src/tabs/tab.js +67 -43
  82. package/src/tabs/transact.js +23 -13
  83. package/src/ui/button-group.js +24 -24
  84. package/src/ui/color-selector.js +28 -23
  85. package/src/ui/field-row.js +3 -3
  86. package/src/ui/icons.js +2 -2
  87. package/src/ui/layers.js +31 -1
  88. package/src/ui/media-picker.js +7 -7
  89. package/src/ui/spectrum.js +5 -1
  90. package/src/ui/unit-selector.js +18 -16
  91. package/src/ui/value-selector.js +21 -15
  92. package/src/ui/widgets.js +22 -19
  93. package/src/utils/canvas-media.js +5 -4
  94. package/src/utils/edit-display.js +32 -26
  95. package/src/utils/google-fonts.js +11 -11
  96. package/src/utils/inherited-style.js +9 -8
  97. package/src/utils/studio-utils.js +18 -18
  98. package/src/view.js +58 -1
  99. package/src/workspace/workspace.js +29 -7
@@ -0,0 +1,204 @@
1
+ /**
2
+ * New Project modal — guides the user through creating a new Jx project. Fields mirror the CLI
3
+ * scaffolder: name, description, url, adapter.
4
+ */
5
+
6
+ import { html } from "lit-html";
7
+ import { openModal } from "../ui/layers.js";
8
+ import { getPlatform } from "../platform.js";
9
+
10
+ /** @type {ReturnType<typeof openModal> | null} */
11
+ let _handle = null;
12
+
13
+ /**
14
+ * @type {{
15
+ * name: string;
16
+ * description: string;
17
+ * url: string;
18
+ * adapter: string;
19
+ * directory: string;
20
+ * }}
21
+ */
22
+ let _form = { name: "", description: "", url: "", adapter: "static", directory: "" };
23
+
24
+ /** @type {string} */
25
+ let _error = "";
26
+
27
+ /** @type {boolean} */
28
+ let _creating = false;
29
+
30
+ /** @type {((result: { root: string; config: object } | null) => void) | null} */
31
+ let _resolve = null;
32
+
33
+ /**
34
+ * Open the New Project modal. Returns a promise that resolves with the created project info (or
35
+ * null if cancelled).
36
+ *
37
+ * @returns {Promise<{ root: string; config: object } | null>}
38
+ */
39
+ export function openNewProjectModal() {
40
+ if (_handle) return Promise.resolve(null);
41
+ _form = { name: "", description: "", url: "", adapter: "static", directory: "" };
42
+ _error = "";
43
+ _creating = false;
44
+
45
+ return new Promise((resolve) => {
46
+ _resolve = resolve;
47
+ renderModal();
48
+ });
49
+ }
50
+
51
+ export function closeNewProjectModal() {
52
+ if (!_handle) return;
53
+ _handle.close();
54
+ _handle = null;
55
+ if (_resolve) {
56
+ _resolve(null);
57
+ _resolve = null;
58
+ }
59
+ }
60
+
61
+ function renderModal() {
62
+ const onInput =
63
+ (/** @type {"name" | "description" | "url" | "adapter" | "directory"} */ field) =>
64
+ (/** @type {Event} */ e) => {
65
+ _form[field] = /** @type {HTMLInputElement} */ (e.target).value;
66
+ if (field === "name" && !_form.directory) {
67
+ // Auto-derive directory slug from name while user hasn't manually typed one
68
+ _dirDerived = true;
69
+ }
70
+ if (_dirDerived && field === "name") {
71
+ _form.directory = _form.name
72
+ .toLowerCase()
73
+ .replace(/[^a-z0-9]+/g, "-")
74
+ .replace(/^-|-$/g, "");
75
+ }
76
+ if (field === "directory") _dirDerived = false;
77
+ renderModal();
78
+ };
79
+
80
+ const onAdapterChange = (/** @type {Event} */ e) => {
81
+ _form.adapter = /** @type {HTMLInputElement} */ (e.target).value;
82
+ renderModal();
83
+ };
84
+
85
+ const onSubmit = async () => {
86
+ if (!_form.name.trim()) {
87
+ _error = "Project name is required";
88
+ renderModal();
89
+ return;
90
+ }
91
+ if (!_form.directory.trim()) {
92
+ _form.directory = _form.name
93
+ .toLowerCase()
94
+ .replace(/[^a-z0-9]+/g, "-")
95
+ .replace(/^-|-$/g, "");
96
+ }
97
+
98
+ _creating = true;
99
+ _error = "";
100
+ renderModal();
101
+
102
+ try {
103
+ const platform = getPlatform();
104
+ const result = await platform.createProject(_form);
105
+ _creating = false;
106
+ if (_handle) {
107
+ _handle.close();
108
+ _handle = null;
109
+ }
110
+ if (_resolve) {
111
+ _resolve(result);
112
+ _resolve = null;
113
+ }
114
+ } catch (/** @type {unknown} */ e) {
115
+ _creating = false;
116
+ _error = /** @type {Error} */ (e).message;
117
+ renderModal();
118
+ }
119
+ };
120
+
121
+ const tpl = html`
122
+ <sp-underlay open @close=${closeNewProjectModal}></sp-underlay>
123
+ <div
124
+ class="new-project-modal"
125
+ @keydown=${(/** @type {KeyboardEvent} */ e) => {
126
+ if (e.key === "Escape") closeNewProjectModal();
127
+ }}
128
+ >
129
+ <div class="new-project-modal-header">
130
+ <h2 class="new-project-modal-title">New Project</h2>
131
+ <sp-action-button quiet size="s" @click=${closeNewProjectModal} title="Close">
132
+ <sp-icon-close slot="icon"></sp-icon-close>
133
+ </sp-action-button>
134
+ </div>
135
+ <div class="new-project-modal-body">
136
+ <label class="new-project-field">
137
+ <span class="new-project-label">Project Name *</span>
138
+ <sp-textfield
139
+ placeholder="My Site"
140
+ .value=${_form.name}
141
+ @input=${onInput("name")}
142
+ style="width: 100%"
143
+ ></sp-textfield>
144
+ </label>
145
+
146
+ <label class="new-project-field">
147
+ <span class="new-project-label">Directory</span>
148
+ <sp-textfield
149
+ placeholder="my-site"
150
+ .value=${_form.directory}
151
+ @input=${onInput("directory")}
152
+ style="width: 100%"
153
+ ></sp-textfield>
154
+ </label>
155
+
156
+ <label class="new-project-field">
157
+ <span class="new-project-label">Description</span>
158
+ <sp-textfield
159
+ placeholder="A short description of the site"
160
+ .value=${_form.description}
161
+ @input=${onInput("description")}
162
+ style="width: 100%"
163
+ ></sp-textfield>
164
+ </label>
165
+
166
+ <label class="new-project-field">
167
+ <span class="new-project-label">Production URL</span>
168
+ <sp-textfield
169
+ placeholder="https://example.com"
170
+ .value=${_form.url}
171
+ @input=${onInput("url")}
172
+ style="width: 100%"
173
+ ></sp-textfield>
174
+ </label>
175
+
176
+ <label class="new-project-field">
177
+ <span class="new-project-label">Deployment Adapter</span>
178
+ <sp-picker label="Adapter" .value=${_form.adapter} @change=${onAdapterChange}>
179
+ <sp-menu-item value="static">Static</sp-menu-item>
180
+ <sp-menu-item value="cloudflare-pages">Cloudflare Pages</sp-menu-item>
181
+ <sp-menu-item value="node">Node</sp-menu-item>
182
+ <sp-menu-item value="bun">Bun</sp-menu-item>
183
+ </sp-picker>
184
+ </label>
185
+
186
+ ${_error ? html`<div class="new-project-error">${_error}</div>` : ""}
187
+ </div>
188
+ <div class="new-project-modal-footer">
189
+ <sp-button variant="secondary" @click=${closeNewProjectModal}>Cancel</sp-button>
190
+ <sp-button variant="accent" ?disabled=${_creating} @click=${onSubmit}>
191
+ ${_creating ? "Creating…" : "Create Project"}
192
+ </sp-button>
193
+ </div>
194
+ </div>
195
+ `;
196
+
197
+ if (!_handle) {
198
+ _handle = openModal(tpl);
199
+ } else {
200
+ _handle.update(tpl);
201
+ }
202
+ }
203
+
204
+ let _dirDerived = true;
@@ -16,10 +16,11 @@ export function mount() {
16
16
  _scope.run(() => {
17
17
  effect(() => {
18
18
  const tab = activeTab.value;
19
- if (!tab) return;
20
- const gs = tab.session.ui.gitStatus;
21
- if (!gs && !tab.session.ui.gitLoading) {
22
- refreshGitStatus();
19
+ if (tab) {
20
+ const gs = tab.session.ui.gitStatus;
21
+ if (!gs && !tab.session.ui.gitLoading) {
22
+ refreshGitStatus();
23
+ }
23
24
  }
24
25
  renderActivityBar();
25
26
  });
@@ -31,7 +32,7 @@ export function unmount() {
31
32
  _scope = null;
32
33
  }
33
34
 
34
- const gitBranchIcon = (/** @type {any} */ s) => html`
35
+ const gitBranchIcon = (/** @type {string} */ s) => html`
35
36
  <svg
36
37
  slot="icon"
37
38
  xmlns="http://www.w3.org/2000/svg"
@@ -52,35 +53,37 @@ const gitBranchIcon = (/** @type {any} */ s) => html`
52
53
  `;
53
54
 
54
55
  /**
55
- * @param {any} tag
56
- * @param {any} size
56
+ * @param {string} tag
57
+ * @param {string} [size]
57
58
  */
58
59
  export function tabIcon(tag, size) {
59
- /** @type {Record<string, any>} */
60
+ /** @type {Record<string, (s: string) => import("lit-html").TemplateResult>} */
60
61
  const m = {
61
- "sp-icon-folder": (/** @type {any} */ s) =>
62
+ "sp-icon-folder": (/** @type {string} */ s) =>
62
63
  html`<sp-icon-folder slot="icon" size=${s}></sp-icon-folder>`,
63
- "sp-icon-layers": (/** @type {any} */ s) =>
64
+ "sp-icon-layers": (/** @type {string} */ s) =>
64
65
  html`<sp-icon-layers slot="icon" size=${s}></sp-icon-layers>`,
65
- "sp-icon-view-grid": (/** @type {any} */ s) =>
66
+ "sp-icon-view-grid": (/** @type {string} */ s) =>
66
67
  html`<sp-icon-view-grid slot="icon" size=${s}></sp-icon-view-grid>`,
67
- "sp-icon-brackets": (/** @type {any} */ s) =>
68
+ "sp-icon-brackets": (/** @type {string} */ s) =>
68
69
  html`<sp-icon-brackets slot="icon" size=${s}></sp-icon-brackets>`,
69
- "sp-icon-data": (/** @type {any} */ s) =>
70
+ "sp-icon-data": (/** @type {string} */ s) =>
70
71
  html`<sp-icon-data slot="icon" size=${s}></sp-icon-data>`,
71
- "sp-icon-properties": (/** @type {any} */ s) =>
72
+ "sp-icon-properties": (/** @type {string} */ s) =>
72
73
  html`<sp-icon-properties slot="icon" size=${s}></sp-icon-properties>`,
73
- "sp-icon-event": (/** @type {any} */ s) =>
74
+ "sp-icon-event": (/** @type {string} */ s) =>
74
75
  html`<sp-icon-event slot="icon" size=${s}></sp-icon-event>`,
75
- "sp-icon-brush": (/** @type {any} */ s) =>
76
+ "sp-icon-brush": (/** @type {string} */ s) =>
76
77
  html`<sp-icon-brush slot="icon" size=${s}></sp-icon-brush>`,
77
- "sp-icon-file-single-web-page": (/** @type {any} */ s) =>
78
+ "sp-icon-file-single-web-page": (/** @type {string} */ s) =>
78
79
  html`<sp-icon-file-single-web-page slot="icon" size=${s}></sp-icon-file-single-web-page>`,
79
- "sp-icon-view-all-tags": (/** @type {any} */ s) =>
80
+ "sp-icon-view-all-tags": (/** @type {string} */ s) =>
80
81
  html`<sp-icon-view-all-tags slot="icon" size=${s}></sp-icon-view-all-tags>`,
81
- "sp-icon-artboard": (/** @type {any} */ s) =>
82
+ "sp-icon-artboard": (/** @type {string} */ s) =>
82
83
  html`<sp-icon-artboard slot="icon" size=${s}></sp-icon-artboard>`,
83
- "sp-icon-box": (/** @type {any} */ s) =>
84
+ "sp-icon-chat": (/** @type {string} */ s) =>
85
+ html`<sp-icon-chat slot="icon" size=${s}></sp-icon-chat>`,
86
+ "sp-icon-box": (/** @type {string} */ s) =>
84
87
  html`<sp-icon-box slot="icon" size=${s}></sp-icon-box>`,
85
88
  "sp-icon-git-branch": gitBranchIcon,
86
89
  };
@@ -90,9 +93,8 @@ export function tabIcon(tag, size) {
90
93
 
91
94
  export function renderActivityBar() {
92
95
  const tab = activeTab.value;
93
- if (!tab) return;
94
96
  const leftTab = view.leftTab;
95
- const gitFileCount = /** @type {any} */ (tab?.session.ui.gitStatus)?.files?.length || 0;
97
+ const gitFileCount = tab?.session.ui.gitStatus?.files?.length || 0;
96
98
  const tabs = [
97
99
  { value: "files", icon: "sp-icon-folder", label: "Files" },
98
100
  { value: "layers", icon: "sp-icon-layers", label: "Layers" },
@@ -108,8 +110,8 @@ export function renderActivityBar() {
108
110
  selected=${view.leftPanelCollapsed ? "" : leftTab}
109
111
  direction="vertical"
110
112
  quiet
111
- @change=${(/** @type {any} */ e) => {
112
- const clicked = e.target.selected;
113
+ @change=${(/** @type {Event} */ e) => {
114
+ const clicked = /** @type {HTMLElement & { selected: string }} */ (e.target).selected;
113
115
  if (clicked === view.leftTab && !view.leftPanelCollapsed) {
114
116
  view.leftPanelCollapsed = true;
115
117
  applyPanelCollapse();
@@ -146,5 +148,5 @@ export function renderActivityBar() {
146
148
  </sp-action-button>
147
149
  </div>
148
150
  `;
149
- litRender(tpl, /** @type {any} */ (activityBar));
151
+ litRender(tpl, activityBar);
150
152
  }