@jxsuite/studio 0.28.2 → 0.28.4

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 (104) hide show
  1. package/dist/studio.js +86144 -83971
  2. package/dist/studio.js.map +229 -219
  3. package/package.json +5 -5
  4. package/src/browse/browse-modal.ts +9 -3
  5. package/src/browse/browse.ts +139 -82
  6. package/src/canvas/canvas-diff.ts +35 -18
  7. package/src/canvas/canvas-helpers.ts +55 -21
  8. package/src/canvas/canvas-live-render.ts +140 -89
  9. package/src/canvas/canvas-render.ts +127 -82
  10. package/src/canvas/canvas-utils.ts +68 -42
  11. package/src/canvas/nested-site-style.ts +15 -7
  12. package/src/editor/component-inline-edit.ts +90 -51
  13. package/src/editor/content-inline-edit.ts +75 -72
  14. package/src/editor/context-menu.ts +125 -71
  15. package/src/editor/convert-targets.ts +17 -13
  16. package/src/editor/convert-to-component.ts +51 -28
  17. package/src/editor/convert-to-repeater.ts +44 -19
  18. package/src/editor/inline-edit.ts +107 -52
  19. package/src/editor/inline-format.ts +128 -60
  20. package/src/editor/insertion-helper.ts +26 -14
  21. package/src/editor/shortcuts.ts +89 -43
  22. package/src/editor/slash-menu.ts +41 -26
  23. package/src/files/components.ts +9 -7
  24. package/src/files/file-ops.ts +53 -33
  25. package/src/files/files.ts +246 -143
  26. package/src/format/constraints.ts +25 -15
  27. package/src/format/format-host.ts +33 -10
  28. package/src/github/github-auth.ts +24 -14
  29. package/src/github/github-publish.ts +20 -14
  30. package/src/new-project/new-project-modal.ts +27 -18
  31. package/src/panels/activity-bar.ts +27 -26
  32. package/src/panels/ai-panel.ts +100 -44
  33. package/src/panels/block-action-bar.ts +84 -52
  34. package/src/panels/canvas-dnd.ts +47 -28
  35. package/src/panels/data-explorer.ts +24 -11
  36. package/src/panels/dnd.ts +148 -100
  37. package/src/panels/editors.ts +54 -37
  38. package/src/panels/elements-panel.ts +23 -13
  39. package/src/panels/events-panel.ts +48 -38
  40. package/src/panels/git-panel.ts +95 -60
  41. package/src/panels/head-panel.ts +138 -84
  42. package/src/panels/imports-panel.ts +66 -29
  43. package/src/panels/layers-panel.ts +80 -36
  44. package/src/panels/left-panel.ts +86 -53
  45. package/src/panels/overlays.ts +36 -19
  46. package/src/panels/panel-events.ts +48 -27
  47. package/src/panels/panel-scheduler.ts +41 -23
  48. package/src/panels/preview-render.ts +26 -26
  49. package/src/panels/properties-panel.ts +155 -118
  50. package/src/panels/pseudo-preview.ts +31 -15
  51. package/src/panels/quick-search.ts +28 -13
  52. package/src/panels/right-panel.ts +35 -24
  53. package/src/panels/shared.ts +46 -29
  54. package/src/panels/signals-panel.ts +303 -186
  55. package/src/panels/statusbar.ts +27 -12
  56. package/src/panels/style-inputs.ts +30 -24
  57. package/src/panels/style-panel.ts +130 -90
  58. package/src/panels/style-utils.ts +63 -23
  59. package/src/panels/stylebook-layers-panel.ts +27 -30
  60. package/src/panels/stylebook-panel.ts +140 -72
  61. package/src/panels/tab-strip.ts +21 -10
  62. package/src/panels/toolbar.ts +52 -40
  63. package/src/panels/welcome-screen.ts +1 -1
  64. package/src/platform.ts +2 -1
  65. package/src/platforms/devserver.ts +221 -105
  66. package/src/recent-projects.ts +15 -7
  67. package/src/resize-edges.ts +7 -5
  68. package/src/services/cem-export.ts +73 -57
  69. package/src/services/code-services.ts +52 -25
  70. package/src/services/monaco-setup.ts +5 -5
  71. package/src/settings/content-types-editor.ts +133 -62
  72. package/src/settings/css-vars-editor.ts +31 -14
  73. package/src/settings/defs-editor.ts +125 -63
  74. package/src/settings/general-settings.ts +11 -6
  75. package/src/settings/head-editor.ts +30 -15
  76. package/src/settings/schema-field-ui.ts +75 -35
  77. package/src/settings/settings-modal.ts +35 -17
  78. package/src/site-context.ts +80 -47
  79. package/src/state.ts +98 -65
  80. package/src/store.ts +42 -23
  81. package/src/studio.ts +165 -144
  82. package/src/tabs/tab.ts +51 -45
  83. package/src/tabs/transact.ts +230 -109
  84. package/src/types.ts +60 -51
  85. package/src/ui/button-group.ts +3 -2
  86. package/src/ui/color-selector.ts +33 -15
  87. package/src/ui/expression-editor.ts +139 -86
  88. package/src/ui/field-input.ts +35 -18
  89. package/src/ui/field-row.ts +1 -1
  90. package/src/ui/icons.ts +5 -6
  91. package/src/ui/layers.ts +33 -26
  92. package/src/ui/media-picker.ts +13 -7
  93. package/src/ui/panel-resize.ts +39 -20
  94. package/src/ui/spectrum.ts +4 -2
  95. package/src/ui/unit-selector.ts +20 -11
  96. package/src/ui/value-selector.ts +13 -7
  97. package/src/ui/widgets.ts +34 -18
  98. package/src/utils/canvas-media.ts +66 -33
  99. package/src/utils/edit-display.ts +43 -44
  100. package/src/utils/google-fonts.ts +10 -6
  101. package/src/utils/inherited-style.ts +22 -8
  102. package/src/utils/studio-utils.ts +88 -45
  103. package/src/view.ts +9 -5
  104. package/src/workspace/workspace.ts +49 -29
@@ -14,42 +14,52 @@ export interface NestingValidator {
14
14
  blockTags: ReadonlySet<string>;
15
15
  inlineTags: ReadonlySet<string>;
16
16
  allTags: ReadonlySet<string>;
17
- isVoid(tag: string): boolean;
18
- isTextOnly(tag: string): boolean;
17
+ isVoid: (tag: string) => boolean;
18
+ isTextOnly: (tag: string) => boolean;
19
19
  /** Whether childTag may appear inside parentTag ("_root" for the document root). */
20
- isValidChild(parentTag: string, childTag: string): boolean;
20
+ isValidChild: (parentTag: string, childTag: string) => boolean;
21
21
  }
22
22
 
23
23
  /** Build a nesting validator from a format's `$studio.elements` declaration. */
24
- export function createNestingValidator(elements: ElementsHints | undefined): NestingValidator {
25
- const blockTags = new Set(elements?.block ?? []);
26
- const inlineTags = new Set(elements?.inline ?? []);
24
+ export function createNestingValidator(elements?: ElementsHints): NestingValidator {
25
+ const blockTags = new Set(elements?.block);
26
+ const inlineTags = new Set(elements?.inline);
27
27
  const allTags = new Set([...blockTags, ...inlineTags]);
28
- const voidTags = new Set(elements?.void ?? []);
29
- const textOnly = new Set(elements?.textOnly ?? []);
28
+ const voidTags = new Set(elements?.void);
29
+ const textOnly = new Set(elements?.textOnly);
30
30
  const nesting = elements?.nesting ?? {};
31
31
 
32
32
  return {
33
+ allTags,
33
34
  blockTags,
34
35
  inlineTags,
35
- allTags,
36
- isVoid: (tag) => voidTags.has(tag),
37
36
  isTextOnly: (tag) => textOnly.has(tag),
38
37
  isValidChild(parentTag, childTag) {
39
38
  const rule = nesting[parentTag];
40
- if (!rule) return true; // unknown parents (directive components) allow anything
39
+ if (!rule) {
40
+ return true;
41
+ } // Unknown parents (directive components) allow anything
41
42
 
42
- if (rule.only) return rule.only.includes(childTag);
43
+ if (rule.only) {
44
+ return rule.only.includes(childTag);
45
+ }
43
46
 
44
47
  const isBlock = blockTags.has(childTag);
45
48
  const isInline = inlineTags.has(childTag);
46
49
  const isDirective = !allTags.has(childTag);
47
50
 
48
- if (isBlock && rule.block) return true;
49
- if (isInline && rule.inline) return true;
50
- if (isDirective && rule.directive) return true;
51
+ if (isBlock && rule.block) {
52
+ return true;
53
+ }
54
+ if (isInline && rule.inline) {
55
+ return true;
56
+ }
57
+ if (isDirective && rule.directive) {
58
+ return true;
59
+ }
51
60
 
52
61
  return false;
53
62
  },
63
+ isVoid: (tag) => voidTags.has(tag),
54
64
  };
55
65
  }
@@ -95,15 +95,30 @@ export function formatByExtension(ext: string, capability?: string): StudioForma
95
95
 
96
96
  /** Look up a format by its import name. */
97
97
  export function formatByName(name: string | null | undefined): StudioFormat | undefined {
98
- if (!name) return undefined;
98
+ if (!name) {
99
+ return undefined;
100
+ }
99
101
  return _formats.find((f) => f.name === name);
100
102
  }
101
103
 
104
+ /** Error for opening a non-JSON file when no imported format class claims its extension. */
105
+ export function noFormatError(path: string): Error {
106
+ return new Error(
107
+ `No format class imported for "${path}" — add one to project.json imports ` +
108
+ `(e.g. "Markdown": "@jxsuite/parser/Markdown.class.json") and make sure the ` +
109
+ `project's dependencies are installed`,
110
+ );
111
+ }
112
+
102
113
  /** The format claiming a file path's extension, if any. */
103
114
  export function formatForPath(path: string | null | undefined): StudioFormat | undefined {
104
- if (!path) return undefined;
115
+ if (!path) {
116
+ return undefined;
117
+ }
105
118
  const dot = path.lastIndexOf(".");
106
- if (dot === -1) return undefined;
119
+ if (dot === -1) {
120
+ return undefined;
121
+ }
107
122
  return formatByExtension(path.slice(dot));
108
123
  }
109
124
 
@@ -111,8 +126,12 @@ export function formatForPath(path: string | null | undefined): StudioFormat | u
111
126
  export function documentExtensions(kind?: string): string[] {
112
127
  const exts = new Set<string>();
113
128
  for (const f of _formats) {
114
- if (kind && !f.documentKinds.includes(kind)) continue;
115
- for (const e of f.extensions) exts.add(e);
129
+ if (kind && !f.documentKinds.includes(kind)) {
130
+ continue;
131
+ }
132
+ for (const e of f.extensions) {
133
+ exts.add(e);
134
+ }
116
135
  }
117
136
  exts.delete(".json");
118
137
  return [...exts];
@@ -140,10 +159,10 @@ export async function formatParse(
140
159
  options?: Record<string, unknown>,
141
160
  ): Promise<JxMutableNode> {
142
161
  return (await formatAction({
143
- format: name,
144
162
  action: "parse",
145
- source,
163
+ format: name,
146
164
  options,
165
+ source,
147
166
  })) as JxMutableNode;
148
167
  }
149
168
 
@@ -154,9 +173,9 @@ export async function formatSerialize(
154
173
  options?: Record<string, unknown>,
155
174
  ): Promise<string> {
156
175
  return (await formatAction({
157
- format: name,
158
176
  action: "serialize",
159
177
  doc,
178
+ format: name,
160
179
  options,
161
180
  })) as string;
162
181
  }
@@ -190,13 +209,17 @@ export function splitFormatDocument(format: StudioFormat | undefined, doc: JxMut
190
209
 
191
210
  // Content document — children form the root-level body; other keys are frontmatter
192
211
  const children = (doc.children as unknown[]) ?? [];
193
- if (children.length === 0) children.push({ tagName: "p", children: [] });
212
+ if (children.length === 0) {
213
+ children.push({ children: [], tagName: "p" });
214
+ }
194
215
 
195
216
  const documentKeys = new Set(["state", "imports"]);
196
217
  const contentDoc: Record<string, unknown> = { children };
197
218
  const frontmatter: Record<string, unknown> = {};
198
219
  for (const [key, value] of Object.entries(doc)) {
199
- if (key === "children") continue;
220
+ if (key === "children") {
221
+ continue;
222
+ }
200
223
  if (documentKeys.has(key)) {
201
224
  contentDoc[key] = value;
202
225
  } else {
@@ -28,18 +28,22 @@ export function clearGithubToken() {
28
28
  */
29
29
  export async function authenticateGithub() {
30
30
  const existing = getGithubToken();
31
- if (existing) return existing;
31
+ if (existing) {
32
+ return existing;
33
+ }
32
34
 
33
35
  const codeRes = await fetch("https://github.com/login/device/code", {
34
- method: "POST",
36
+ body: JSON.stringify({ client_id: CLIENT_ID, scope: "repo" }),
35
37
  headers: {
36
- "Content-Type": "application/json",
37
38
  Accept: "application/json",
39
+ "Content-Type": "application/json",
38
40
  },
39
- body: JSON.stringify({ client_id: CLIENT_ID, scope: "repo" }),
41
+ method: "POST",
40
42
  });
41
43
 
42
- if (!codeRes.ok) throw new Error("Failed to initiate GitHub device flow");
44
+ if (!codeRes.ok) {
45
+ throw new Error("Failed to initiate GitHub device flow");
46
+ }
43
47
  const codeData = await codeRes.json();
44
48
 
45
49
  const { device_code, user_code, verification_uri, interval = 5 } = codeData;
@@ -49,19 +53,21 @@ export async function authenticateGithub() {
49
53
  let pollTimer: ReturnType<typeof setTimeout> | null = null;
50
54
 
51
55
  const poll = async () => {
52
- if (cancelled) return;
56
+ if (cancelled) {
57
+ return;
58
+ }
53
59
  try {
54
60
  const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
55
- method: "POST",
56
- headers: {
57
- "Content-Type": "application/json",
58
- Accept: "application/json",
59
- },
60
61
  body: JSON.stringify({
61
62
  client_id: CLIENT_ID,
62
63
  device_code,
63
64
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
64
65
  }),
66
+ headers: {
67
+ Accept: "application/json",
68
+ "Content-Type": "application/json",
69
+ },
70
+ method: "POST",
65
71
  });
66
72
  const tokenData = await tokenRes.json();
67
73
 
@@ -81,7 +87,7 @@ export async function authenticateGithub() {
81
87
  return;
82
88
  }
83
89
 
84
- // expired_token or access_denied
90
+ // Expired_token or access_denied
85
91
  done(null);
86
92
  } catch {
87
93
  pollTimer = setTimeout(poll, interval * 1000);
@@ -97,12 +103,16 @@ export async function authenticateGithub() {
97
103
  cancel-label="Cancel"
98
104
  @cancel=${() => {
99
105
  cancelled = true;
100
- if (pollTimer) clearTimeout(pollTimer);
106
+ if (pollTimer) {
107
+ clearTimeout(pollTimer);
108
+ }
101
109
  done(null);
102
110
  }}
103
111
  @close=${() => {
104
112
  cancelled = true;
105
- if (pollTimer) clearTimeout(pollTimer);
113
+ if (pollTimer) {
114
+ clearTimeout(pollTimer);
115
+ }
106
116
  done(null);
107
117
  }}
108
118
  >
@@ -2,11 +2,11 @@
2
2
  /** Publish a local project to GitHub — creates a new repo and pushes. */
3
3
 
4
4
  import { html } from "lit-html";
5
+ import { errorMessage } from "@jxsuite/schema/parse";
5
6
  import { ref } from "lit-html/directives/ref.js";
6
7
  import { showDialog } from "../ui/layers";
7
8
  import { authenticateGithub } from "./github-auth";
8
9
  import { getPlatform } from "../platform";
9
- import { refreshGitStatus } from "../panels/git-panel";
10
10
  import { statusMessage } from "../panels/statusbar";
11
11
 
12
12
  /**
@@ -18,7 +18,9 @@ import { statusMessage } from "../panels/statusbar";
18
18
  */
19
19
  export async function publishToGithub({ projectName }: { projectName: string }) {
20
20
  const token = await authenticateGithub();
21
- if (!token) return false;
21
+ if (!token) {
22
+ return false;
23
+ }
22
24
 
23
25
  const repoOpts = await showDialog<{
24
26
  name: string;
@@ -37,9 +39,9 @@ export async function publishToGithub({ projectName }: { projectName: string })
37
39
  cancel-label="Cancel"
38
40
  @confirm=${() => {
39
41
  done({
40
- name: _nameInput?.value || projectName,
41
42
  description: _descInput?.value || "",
42
43
  isPrivate: _privateToggle?.checked ?? true,
44
+ name: _nameInput?.value || projectName,
43
45
  });
44
46
  }}
45
47
  @cancel=${() => done(null)}
@@ -81,23 +83,25 @@ export async function publishToGithub({ projectName }: { projectName: string })
81
83
  `;
82
84
  });
83
85
 
84
- if (!repoOpts) return false;
86
+ if (!repoOpts) {
87
+ return false;
88
+ }
85
89
 
86
90
  statusMessage("Creating GitHub repository…");
87
91
 
88
92
  const createRes = await fetch("https://api.github.com/user/repos", {
89
- method: "POST",
90
- headers: {
91
- Authorization: `Bearer ${token}`,
92
- Accept: "application/vnd.github+json",
93
- "Content-Type": "application/json",
94
- },
95
93
  body: JSON.stringify({
96
- name: repoOpts.name,
94
+ auto_init: false,
97
95
  description: repoOpts.description,
96
+ name: repoOpts.name,
98
97
  private: repoOpts.isPrivate,
99
- auto_init: false,
100
98
  }),
99
+ headers: {
100
+ Accept: "application/vnd.github+json",
101
+ Authorization: `Bearer ${token}`,
102
+ "Content-Type": "application/json",
103
+ },
104
+ method: "POST",
101
105
  });
102
106
 
103
107
  if (!createRes.ok) {
@@ -117,11 +121,13 @@ export async function publishToGithub({ projectName }: { projectName: string })
117
121
  statusMessage("Pushing to GitHub…");
118
122
  try {
119
123
  await platform.gitPush({ setUpstream: true });
120
- } catch (e) {
121
- statusMessage(`Push failed: ${(e as Error).message}`);
124
+ } catch (error) {
125
+ statusMessage(`Push failed: ${errorMessage(error)}`);
122
126
  return false;
123
127
  }
124
128
 
129
+ // Lazy import breaks the github-publish ↔ git-panel module cycle
130
+ const { refreshGitStatus } = await import("../panels/git-panel");
125
131
  await refreshGitStatus();
126
132
  statusMessage(`Published to GitHub: ${repo.html_url}`);
127
133
  return true;
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { html } from "lit-html";
8
+ import { errorMessage } from "@jxsuite/schema/parse";
8
9
  import { openModal } from "../ui/layers";
9
10
  import { getPlatform } from "../platform";
10
11
  import type { ProjectConfig } from "@jxsuite/schema/types";
@@ -21,16 +22,16 @@ let _handle: ReturnType<typeof openModal> | null = null;
21
22
  * }}
22
23
  */
23
24
  let _form = {
24
- name: "",
25
- description: "",
26
- url: "",
27
25
  adapter: "static",
26
+ description: "",
28
27
  directory: "",
28
+ name: "",
29
+ url: "",
29
30
  };
30
31
 
31
- let _error: string = "";
32
+ let _error = "";
32
33
 
33
- let _creating: boolean = false;
34
+ let _creating = false;
34
35
 
35
36
  /** @type {((result: { root: string; config: object } | null) => void) | null} */
36
37
  let _resolve: ((result: { root: string; config: ProjectConfig } | null) => void) | null = null;
@@ -45,13 +46,15 @@ export function openNewProjectModal(): Promise<{
45
46
  root: string;
46
47
  config: ProjectConfig;
47
48
  } | null> {
48
- if (_handle) return Promise.resolve(null);
49
+ if (_handle) {
50
+ return Promise.resolve(null);
51
+ }
49
52
  _form = {
50
- name: "",
51
- description: "",
52
- url: "",
53
53
  adapter: "static",
54
+ description: "",
54
55
  directory: "",
56
+ name: "",
57
+ url: "",
55
58
  };
56
59
  _error = "";
57
60
  _creating = false;
@@ -63,7 +66,9 @@ export function openNewProjectModal(): Promise<{
63
66
  }
64
67
 
65
68
  export function closeNewProjectModal() {
66
- if (!_handle) return;
69
+ if (!_handle) {
70
+ return;
71
+ }
67
72
  _handle.close();
68
73
  _handle = null;
69
74
  if (_resolve) {
@@ -83,10 +88,12 @@ function renderModal() {
83
88
  if (_dirDerived && field === "name") {
84
89
  _form.directory = _form.name
85
90
  .toLowerCase()
86
- .replace(/[^a-z0-9]+/g, "-")
87
- .replace(/^-|-$/g, "");
91
+ .replaceAll(/[^a-z0-9]+/g, "-")
92
+ .replaceAll(/^-|-$/g, "");
93
+ }
94
+ if (field === "directory") {
95
+ _dirDerived = false;
88
96
  }
89
- if (field === "directory") _dirDerived = false;
90
97
  renderModal();
91
98
  };
92
99
 
@@ -104,8 +111,8 @@ function renderModal() {
104
111
  if (!_form.directory.trim()) {
105
112
  _form.directory = _form.name
106
113
  .toLowerCase()
107
- .replace(/[^a-z0-9]+/g, "-")
108
- .replace(/^-|-$/g, "");
114
+ .replaceAll(/[^a-z0-9]+/g, "-")
115
+ .replaceAll(/^-|-$/g, "");
109
116
  }
110
117
 
111
118
  _creating = true;
@@ -124,9 +131,9 @@ function renderModal() {
124
131
  _resolve(result);
125
132
  _resolve = null;
126
133
  }
127
- } catch (e) {
134
+ } catch (error) {
128
135
  _creating = false;
129
- _error = (e as Error).message;
136
+ _error = errorMessage(error);
130
137
  renderModal();
131
138
  }
132
139
  };
@@ -136,7 +143,9 @@ function renderModal() {
136
143
  <div
137
144
  class="new-project-modal"
138
145
  @keydown=${(e: KeyboardEvent) => {
139
- if (e.key === "Escape") closeNewProjectModal();
146
+ if (e.key === "Escape") {
147
+ closeNewProjectModal();
148
+ }
140
149
  }}
141
150
  >
142
151
  <div class="new-project-modal-header">
@@ -5,11 +5,13 @@ import { html, render as litRender, nothing } from "lit-html";
5
5
  import { activityBar, renderOnly } from "../store";
6
6
  import { effect, effectScope } from "../reactivity";
7
7
  import { activeTab } from "../workspace/workspace";
8
- import { view, applyPanelCollapse } from "../view";
8
+ import { applyPanelCollapse, view } from "../view";
9
9
  import { openSettingsModal } from "../settings/settings-modal";
10
10
  import { refreshGitStatus } from "./git-panel";
11
+ import type { EffectScope } from "@vue/reactivity";
12
+ import type { TemplateResult } from "lit-html";
11
13
 
12
- let _scope: import("@vue/reactivity").EffectScope | null = null;
14
+ let _scope: EffectScope | null = null;
13
15
 
14
16
  export function mount() {
15
17
  _scope = effectScope();
@@ -57,27 +59,27 @@ const gitBranchIcon = (s: string) => html`
57
59
  * @param {string} [size]
58
60
  */
59
61
  export function tabIcon(tag: string, size?: string) {
60
- const m: Record<string, (s: string) => import("lit-html").TemplateResult> = {
61
- "sp-icon-folder": (s: string) => html`<sp-icon-folder slot="icon" size=${s}></sp-icon-folder>`,
62
- "sp-icon-layers": (s: string) => html`<sp-icon-layers slot="icon" size=${s}></sp-icon-layers>`,
63
- "sp-icon-view-grid": (s: string) =>
64
- html`<sp-icon-view-grid slot="icon" size=${s}></sp-icon-view-grid>`,
62
+ const m: Record<string, (s: string) => TemplateResult> = {
63
+ "sp-icon-artboard": (s: string) =>
64
+ html`<sp-icon-artboard slot="icon" size=${s}></sp-icon-artboard>`,
65
+ "sp-icon-box": (s: string) => html`<sp-icon-box slot="icon" size=${s}></sp-icon-box>`,
65
66
  "sp-icon-brackets": (s: string) =>
66
67
  html`<sp-icon-brackets slot="icon" size=${s}></sp-icon-brackets>`,
68
+ "sp-icon-brush": (s: string) => html`<sp-icon-brush slot="icon" size=${s}></sp-icon-brush>`,
69
+ "sp-icon-chat": (s: string) => html`<sp-icon-chat slot="icon" size=${s}></sp-icon-chat>`,
67
70
  "sp-icon-data": (s: string) => html`<sp-icon-data slot="icon" size=${s}></sp-icon-data>`,
68
- "sp-icon-properties": (s: string) =>
69
- html`<sp-icon-properties slot="icon" size=${s}></sp-icon-properties>`,
70
71
  "sp-icon-event": (s: string) => html`<sp-icon-event slot="icon" size=${s}></sp-icon-event>`,
71
- "sp-icon-brush": (s: string) => html`<sp-icon-brush slot="icon" size=${s}></sp-icon-brush>`,
72
72
  "sp-icon-file-single-web-page": (s: string) =>
73
73
  html`<sp-icon-file-single-web-page slot="icon" size=${s}></sp-icon-file-single-web-page>`,
74
+ "sp-icon-folder": (s: string) => html`<sp-icon-folder slot="icon" size=${s}></sp-icon-folder>`,
75
+ "sp-icon-git-branch": gitBranchIcon,
76
+ "sp-icon-layers": (s: string) => html`<sp-icon-layers slot="icon" size=${s}></sp-icon-layers>`,
77
+ "sp-icon-properties": (s: string) =>
78
+ html`<sp-icon-properties slot="icon" size=${s}></sp-icon-properties>`,
74
79
  "sp-icon-view-all-tags": (s: string) =>
75
80
  html`<sp-icon-view-all-tags slot="icon" size=${s}></sp-icon-view-all-tags>`,
76
- "sp-icon-artboard": (s: string) =>
77
- html`<sp-icon-artboard slot="icon" size=${s}></sp-icon-artboard>`,
78
- "sp-icon-chat": (s: string) => html`<sp-icon-chat slot="icon" size=${s}></sp-icon-chat>`,
79
- "sp-icon-box": (s: string) => html`<sp-icon-box slot="icon" size=${s}></sp-icon-box>`,
80
- "sp-icon-git-branch": gitBranchIcon,
81
+ "sp-icon-view-grid": (s: string) =>
82
+ html`<sp-icon-view-grid slot="icon" size=${s}></sp-icon-view-grid>`,
81
83
  };
82
84
  const fn = m[tag];
83
85
  return fn ? fn(size || "s") : nothing;
@@ -85,17 +87,17 @@ export function tabIcon(tag: string, size?: string) {
85
87
 
86
88
  export function renderActivityBar() {
87
89
  const tab = activeTab.value;
88
- const leftTab = view.leftTab;
90
+ const { leftTab } = view;
89
91
  const gitFileCount = tab?.session.ui.gitStatus?.files?.length || 0;
90
92
  const tabs = [
91
- { value: "files", icon: "sp-icon-folder", label: "Files" },
92
- { value: "layers", icon: "sp-icon-layers", label: "Layers" },
93
- { value: "imports", icon: "sp-icon-box", label: "Imports" },
94
- { value: "blocks", icon: "sp-icon-view-grid", label: "Elements" },
95
- { value: "state", icon: "sp-icon-brackets", label: "State" },
96
- { value: "data", icon: "sp-icon-data", label: "Data" },
97
- { value: "head", icon: "sp-icon-view-all-tags", label: "Document" },
98
- { value: "git", icon: "sp-icon-git-branch", label: "Source Control" },
93
+ { icon: "sp-icon-folder", label: "Files", value: "files" },
94
+ { icon: "sp-icon-layers", label: "Layers", value: "layers" },
95
+ { icon: "sp-icon-box", label: "Imports", value: "imports" },
96
+ { icon: "sp-icon-view-grid", label: "Elements", value: "blocks" },
97
+ { icon: "sp-icon-brackets", label: "State", value: "state" },
98
+ { icon: "sp-icon-data", label: "Data", value: "data" },
99
+ { icon: "sp-icon-view-all-tags", label: "Document", value: "head" },
100
+ { icon: "sp-icon-git-branch", label: "Source Control", value: "git" },
99
101
  ];
100
102
  const tpl = html`
101
103
  <sp-tabs
@@ -107,14 +109,13 @@ export function renderActivityBar() {
107
109
  if (clicked === view.leftTab && !view.leftPanelCollapsed) {
108
110
  view.leftPanelCollapsed = true;
109
111
  applyPanelCollapse();
110
- renderActivityBar();
111
112
  } else {
112
113
  view.leftTab = clicked;
113
114
  view.leftPanelCollapsed = false;
114
115
  applyPanelCollapse();
115
116
  renderOnly("leftPanel");
116
- renderActivityBar();
117
117
  }
118
+ renderActivityBar();
118
119
  }}
119
120
  >
120
121
  ${tabs.map(