@hypit/hypit 0.1.5 → 0.1.6
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/examples/semantic-composition/README.md +2 -1
- package/examples/semantic-composition/chat.svml +1 -1
- package/examples/semantic-composition/hypit.runtime.json +1 -1
- package/package.json +4 -1
- package/packages/caption-fine/README.md +7 -0
- package/packages/cli/src/view.ts +4 -3
- package/packages/film/src/surface.ts +4 -1
- package/packages/runtime-local/README.md +17 -0
- package/packages/script/README.md +15 -3
- package/packages/script/src/manifest.ts +2 -1
- package/packages/script/src/parser.ts +45 -6
- package/packages/studio/LOCALIZATION.md +98 -0
- package/packages/studio/README.md +13 -0
- package/packages/studio/locales/en.json +197 -0
- package/packages/studio/locales/zh-CN.json +191 -0
- package/packages/studio/src/localization-node.ts +46 -0
- package/packages/studio/src/localization.ts +85 -0
- package/packages/studio/src/style.css +15 -3
- package/packages/studio/src/ui/artifact-name.ts +8 -6
- package/packages/studio/src/ui/artifact-preview.ts +5 -4
- package/packages/studio/src/ui/code.ts +17 -16
- package/packages/studio/src/ui/comments.ts +26 -25
- package/packages/studio/src/ui/dropdown.ts +68 -0
- package/packages/studio/src/ui/i18n.ts +109 -0
- package/packages/studio/src/ui/icons.ts +1 -0
- package/packages/studio/src/ui/library.ts +76 -65
- package/packages/studio/src/ui/main.ts +84 -131
- package/packages/studio/src/ui/scrub-preview.ts +8 -6
- package/packages/studio/src/ui/stage.ts +28 -27
- package/packages/studio/src/ui/timeline.ts +17 -14
- package/packages/studio/start.ts +39 -22
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t, uiLabel, uiAttribute, uiText, uiAttr, type Message } from "./i18n.js";
|
|
1
2
|
import { feedbackClock } from "../feedback.js";
|
|
2
3
|
import type { FeedbackComment, FeedbackMutation, FeedbackView } from "../feedback.js";
|
|
3
4
|
import { icon } from "./icons.js";
|
|
@@ -11,28 +12,28 @@ export function createComments(store: Store, stage: Stage) {
|
|
|
11
12
|
element.className = "comments-panel";
|
|
12
13
|
element.innerHTML = `
|
|
13
14
|
<div class="pane-heading comments-heading">
|
|
14
|
-
<div class="pane-title">${icon("comments")}<h2
|
|
15
|
-
<button type="button" class="comments-icon" data-refresh aria-label
|
|
15
|
+
<div class="pane-title">${icon("comments")}<h2>${uiLabel("app.comments")}</h2><span data-count></span></div>
|
|
16
|
+
<button type="button" class="comments-icon" data-refresh ${uiAttribute("aria-label", "comments.refresh-comments")} ${uiAttribute("title", "comments.refresh-comments")}>${icon("refresh")}</button>
|
|
16
17
|
</div>
|
|
17
|
-
<div class="comments-filters" role="group" aria-label
|
|
18
|
-
<button type="button" data-filter="all" aria-pressed="true"
|
|
19
|
-
<button type="button" data-filter="open" aria-pressed="false"
|
|
20
|
-
<button type="button" data-filter="done" aria-pressed="false"
|
|
18
|
+
<div class="comments-filters" role="group" ${uiAttribute("aria-label", "comments.filter-comments")}>
|
|
19
|
+
<button type="button" data-filter="all" aria-pressed="true">${uiLabel("comments.all")}</button>
|
|
20
|
+
<button type="button" data-filter="open" aria-pressed="false">${uiLabel("comments.open")}</button>
|
|
21
|
+
<button type="button" data-filter="done" aria-pressed="false">${uiLabel("comments.done")}</button>
|
|
21
22
|
</div>
|
|
22
23
|
<div class="comments-error" role="status" aria-live="polite" hidden></div>
|
|
23
|
-
<div class="comments-list" aria-label
|
|
24
|
+
<div class="comments-list" ${uiAttribute("aria-label", "app.comments")}></div>
|
|
24
25
|
<form class="comment-composer">
|
|
25
26
|
<div class="comment-anchor">
|
|
26
|
-
<button type="button" class="comment-time" data-current-time title
|
|
27
|
+
<button type="button" class="comment-time" data-current-time ${uiAttribute("title", "comments.use-current-frame")} ${uiAttribute("aria-label", "comments.use-current-frame")}>${icon("when")}<span>00:00.00</span></button>
|
|
27
28
|
</div>
|
|
28
29
|
<div class="comment-input">
|
|
29
|
-
<textarea rows="3" aria-label
|
|
30
|
+
<textarea rows="3" ${uiAttribute("aria-label", "comments.write-a-comment")} ${uiAttribute("placeholder", "comments.placeholder")} spellcheck="true" enterkeyhint="send"></textarea>
|
|
30
31
|
<div class="comment-input-actions">
|
|
31
32
|
<div class="comment-tools">
|
|
32
|
-
<button type="button" class="comments-icon" aria-label
|
|
33
|
-
<button type="button" class="comments-icon" aria-label
|
|
33
|
+
<button type="button" class="comments-icon" ${uiAttribute("aria-label", "comments.emoji-soon")} ${uiAttribute("title", "comments.emoji-soon")} disabled>${icon("smile")}</button>
|
|
34
|
+
<button type="button" class="comments-icon" ${uiAttribute("aria-label", "comments.draw-soon")} ${uiAttribute("title", "comments.draw-soon")} disabled>${icon("edit")}</button>
|
|
34
35
|
</div>
|
|
35
|
-
<button class="comment-submit" type="submit" aria-label
|
|
36
|
+
<button class="comment-submit" type="submit" ${uiAttribute("aria-label", "comments.send-comment")} ${uiAttribute("title", "comments.send-shortcut")} disabled>${icon("send")}</button>
|
|
36
37
|
</div>
|
|
37
38
|
</div>
|
|
38
39
|
</form>`;
|
|
@@ -68,10 +69,10 @@ export function createComments(store: Store, stage: Stage) {
|
|
|
68
69
|
submit.disabled = busy || view === undefined || anchor === undefined || input.value.trim().length === 0;
|
|
69
70
|
};
|
|
70
71
|
const capture = (): void => { stage.pause(); anchor = currentAnchor(); renderAnchor(); };
|
|
71
|
-
const button = (name: string, label:
|
|
72
|
+
const button = (name: string, label: Message, action: () => void): HTMLButtonElement => {
|
|
72
73
|
const node = document.createElement("button");
|
|
73
74
|
node.type = "button"; node.className = "comments-icon";
|
|
74
|
-
node.innerHTML = icon(name); node
|
|
75
|
+
node.innerHTML = icon(name); uiAttr(node, "title", label); uiAttr(node, "aria-label", label);
|
|
75
76
|
node.disabled = busy;
|
|
76
77
|
node.addEventListener("click", (event) => { event.stopPropagation(); action(); });
|
|
77
78
|
return node;
|
|
@@ -101,8 +102,8 @@ export function createComments(store: Store, stage: Stage) {
|
|
|
101
102
|
const empty = document.createElement("div");
|
|
102
103
|
empty.className = "comments-empty";
|
|
103
104
|
empty.innerHTML = `${icon(filter === "done" ? "check" : "comments")}<strong></strong><p></p>`;
|
|
104
|
-
empty.querySelector("strong")
|
|
105
|
-
empty.querySelector("p")
|
|
105
|
+
uiText(empty.querySelector("strong")!, view === undefined ? "comments.loading-comments" : filter === "done" ? "comments.no-completed-comments" : filter === "open" ? "comments.caught-up" : "comments.empty");
|
|
106
|
+
uiText(empty.querySelector("p")!, filter === "all" ? "comments.empty-hint" : "comments.filter-hint");
|
|
106
107
|
list.append(empty);
|
|
107
108
|
}
|
|
108
109
|
for (const { comment, number } of comments) {
|
|
@@ -114,31 +115,31 @@ export function createComments(store: Store, stage: Stage) {
|
|
|
114
115
|
const label = document.createElement("span"); label.className = "comment-number"; label.textContent = `#${number}`;
|
|
115
116
|
const time = document.createElement("span"); time.className = "comment-time"; time.textContent = feedbackClock(comment.at);
|
|
116
117
|
location.append(time);
|
|
117
|
-
location
|
|
118
|
+
uiAttr(location, "title", "comments.go-to-this-frame"); location.addEventListener("click", () => locate(comment));
|
|
118
119
|
row.addEventListener("click", (event) => {
|
|
119
120
|
if ((event.target as Element).closest("button, form") !== null || window.getSelection()?.isCollapsed === false) return;
|
|
120
121
|
locate(comment);
|
|
121
122
|
});
|
|
122
123
|
const actions = document.createElement("div"); actions.className = "comment-actions";
|
|
123
|
-
const resolve = button("check", comment.resolved ? "
|
|
124
|
+
const resolve = button("check", comment.resolved ? "comments.reopen-comment" : "comments.mark-complete", () => {
|
|
124
125
|
void mutate({ type: "replace", before: comment, comment: { ...comment, resolved: !comment.resolved } });
|
|
125
126
|
});
|
|
126
127
|
resolve.classList.toggle("is-done", comment.resolved === true);
|
|
127
128
|
resolve.setAttribute("aria-pressed", String(comment.resolved === true));
|
|
128
|
-
actions.append(label, resolve, button("edit", "
|
|
129
|
+
actions.append(label, resolve, button("edit", "comments.edit-comment", () => {
|
|
129
130
|
editing = { id: comment.id, before: comment, text: comment.text }; selected = comment.id; render();
|
|
130
131
|
list.querySelector<HTMLTextAreaElement>(".comment-edit textarea")?.focus();
|
|
131
|
-
}), button("trash", "
|
|
132
|
+
}), button("trash", "comments.delete-comment", () => { void mutate({ type: "delete", before: comment }); }));
|
|
132
133
|
head.append(location, actions); row.append(head);
|
|
133
134
|
if (editing?.id === comment.id) {
|
|
134
135
|
const edit = document.createElement("form"); edit.className = "comment-edit";
|
|
135
136
|
const text = document.createElement("textarea"); text.rows = 3; text.value = editing.text;
|
|
136
|
-
text
|
|
137
|
+
uiAttr(text, "aria-label", "comments.edit-comment-text");
|
|
137
138
|
text.addEventListener("input", () => { if (editing !== undefined) editing.text = text.value; });
|
|
138
139
|
const controls = document.createElement("div"); controls.className = "comment-edit-actions";
|
|
139
|
-
const cancel = document.createElement("button"); cancel.type = "button"; cancel
|
|
140
|
+
const cancel = document.createElement("button"); cancel.type = "button"; uiText(cancel, "common.cancel");
|
|
140
141
|
cancel.addEventListener("click", () => { editing = undefined; render(); });
|
|
141
|
-
const save = document.createElement("button"); save.type = "submit"; save
|
|
142
|
+
const save = document.createElement("button"); save.type = "submit"; uiText(save, "common.save"); save.disabled = busy;
|
|
142
143
|
controls.append(cancel, save); edit.append(text, controls);
|
|
143
144
|
edit.addEventListener("submit", (event) => {
|
|
144
145
|
event.preventDefault();
|
|
@@ -161,7 +162,7 @@ export function createComments(store: Store, stage: Stage) {
|
|
|
161
162
|
try {
|
|
162
163
|
const response = await fetch("/__studio/feedback");
|
|
163
164
|
const data = await response.json() as FeedbackView & { error?: string };
|
|
164
|
-
if (!response.ok) throw new Error(data.error ?? "
|
|
165
|
+
if (!response.ok) throw new Error(data.error ?? t("comments.read-failed"));
|
|
165
166
|
if (request !== fetchId) return;
|
|
166
167
|
if (JSON.stringify(view) !== JSON.stringify(data)) { view = data; render(); }
|
|
167
168
|
showError(); renderAnchor();
|
|
@@ -173,7 +174,7 @@ export function createComments(store: Store, stage: Stage) {
|
|
|
173
174
|
try {
|
|
174
175
|
const response = await fetch("/__studio/feedback", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(mutation) });
|
|
175
176
|
const data = await response.json() as FeedbackView & { error?: string };
|
|
176
|
-
if (!response.ok) throw new Error(data.error ?? "
|
|
177
|
+
if (!response.ok) throw new Error(data.error ?? t("comments.save-failed"));
|
|
177
178
|
++fetchId; view = data; busy = false; saved?.();
|
|
178
179
|
if (mutation.type === "delete" && selected === mutation.before.id) selected = undefined;
|
|
179
180
|
if (mutation.type === "delete" && editing?.id === mutation.before.id) editing = undefined;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Shared listbox interaction for Studio controls. Callers own the options and values. */
|
|
2
|
+
export function bindDropdown(control: HTMLElement, trigger: HTMLButtonElement, menu: HTMLElement, options: readonly HTMLButtonElement[]) {
|
|
3
|
+
const close = (restoreFocus = false): void => {
|
|
4
|
+
control.classList.remove("open", "open-up");
|
|
5
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
6
|
+
menu.hidden = true;
|
|
7
|
+
if (restoreFocus) trigger.focus();
|
|
8
|
+
};
|
|
9
|
+
const open = (focus: "selected" | "first" | "last" = "selected"): void => {
|
|
10
|
+
control.classList.add("open");
|
|
11
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
12
|
+
menu.hidden = false;
|
|
13
|
+
control.classList.remove("open-up");
|
|
14
|
+
const scroll = control.closest<HTMLElement>(".workspace-scroll");
|
|
15
|
+
if (scroll !== null) {
|
|
16
|
+
const menuBox = menu.getBoundingClientRect();
|
|
17
|
+
const scrollBox = scroll.getBoundingClientRect();
|
|
18
|
+
if (menuBox.bottom > scrollBox.bottom && trigger.getBoundingClientRect().top - menuBox.height >= scrollBox.top) {
|
|
19
|
+
control.classList.add("open-up");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const target = focus === "first" ? options[0]
|
|
23
|
+
: focus === "last" ? options.at(-1)
|
|
24
|
+
: options.find((option) => option.classList.contains("active")) ?? options[0];
|
|
25
|
+
target?.focus();
|
|
26
|
+
};
|
|
27
|
+
const moveOptionFocus = (offset: number): void => {
|
|
28
|
+
const current = options.indexOf(document.activeElement as HTMLButtonElement);
|
|
29
|
+
const next = current < 0 ? 0 : (current + offset + options.length) % options.length;
|
|
30
|
+
options[next]?.focus();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
trigger.addEventListener("click", () => {
|
|
34
|
+
if (control.classList.contains("open")) close(false);
|
|
35
|
+
else open();
|
|
36
|
+
});
|
|
37
|
+
trigger.addEventListener("keydown", (event) => {
|
|
38
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
event.stopPropagation();
|
|
41
|
+
open(event.key === "ArrowDown" ? "first" : "last");
|
|
42
|
+
} else if (event.key === "Escape" && control.classList.contains("open")) {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
close(false);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
menu.addEventListener("keydown", (event) => {
|
|
49
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
event.stopPropagation();
|
|
52
|
+
moveOptionFocus(event.key === "ArrowDown" ? 1 : -1);
|
|
53
|
+
} else if (event.key === "Home" || event.key === "End") {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
options[event.key === "Home" ? 0 : options.length - 1]?.focus();
|
|
57
|
+
} else if (event.key === "Escape") {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
event.stopPropagation();
|
|
60
|
+
close(true);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
control.addEventListener("focusout", (event) => {
|
|
64
|
+
if (event.relatedTarget instanceof Node && control.contains(event.relatedTarget)) return;
|
|
65
|
+
close(false);
|
|
66
|
+
});
|
|
67
|
+
return { close };
|
|
68
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { builtinLanguages, chooseLanguage, formatMessage, readLanguagePack } from "../localization.js";
|
|
2
|
+
import type { LanguagePack, Message, MessageValues } from "../localization.js";
|
|
3
|
+
import { bindDropdown } from "./dropdown.js";
|
|
4
|
+
import { icon } from "./icons.js";
|
|
5
|
+
export type { Message } from "../localization.js";
|
|
6
|
+
|
|
7
|
+
type Attribute = "title" | "aria-label" | "placeholder" | "alt";
|
|
8
|
+
const attributes: readonly Attribute[] = ["title", "aria-label", "placeholder", "alt"];
|
|
9
|
+
const storageKey = "hypit-studio.language";
|
|
10
|
+
let languages = builtinLanguages;
|
|
11
|
+
let language = chooseLanguage(languages, navigator.languages);
|
|
12
|
+
|
|
13
|
+
export async function initializeI18n(): Promise<void> {
|
|
14
|
+
const response = await fetch("/__studio/locales");
|
|
15
|
+
if (!response.ok) throw new Error(`Could not load Studio languages (${response.status}).`);
|
|
16
|
+
languages = (await response.json() as unknown[]).map(readLanguagePack);
|
|
17
|
+
let saved: string | null = null;
|
|
18
|
+
try { saved = localStorage.getItem(storageKey); } catch { /* Browser storage may be disabled. */ }
|
|
19
|
+
language = chooseLanguage(languages, [...(saved ? [saved] : []), ...navigator.languages]);
|
|
20
|
+
document.documentElement.lang = language.locale;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function t(message: Message, values: MessageValues = {}): string {
|
|
24
|
+
return formatMessage(language, message, values);
|
|
25
|
+
}
|
|
26
|
+
function escape(value: string): string {
|
|
27
|
+
return value.replace(/[&<>"']/gu, char => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]!);
|
|
28
|
+
}
|
|
29
|
+
export function uiLabel(message: Message): string {
|
|
30
|
+
return `<span data-ui-text="${message}" dir="${language.direction}">${escape(t(message))}</span>`;
|
|
31
|
+
}
|
|
32
|
+
export function uiAttribute(attribute: Attribute, message: Message): string {
|
|
33
|
+
return `${attribute}="${escape(t(message))}" data-ui-${attribute}="${message}"`;
|
|
34
|
+
}
|
|
35
|
+
export function uiText(node: Element, message: Message, values: MessageValues = {}): void {
|
|
36
|
+
node.setAttribute("data-ui-text", message);
|
|
37
|
+
node.setAttribute("data-ui-values", JSON.stringify(values));
|
|
38
|
+
node.setAttribute("dir", language.direction);
|
|
39
|
+
node.textContent = t(message, values);
|
|
40
|
+
}
|
|
41
|
+
/** Use when a node changes from UI text into a user-provided name or an empty value. */
|
|
42
|
+
export function userText(node: Element, value: string): void {
|
|
43
|
+
node.removeAttribute("data-ui-text");
|
|
44
|
+
node.removeAttribute("data-ui-values");
|
|
45
|
+
node.setAttribute("dir", "auto");
|
|
46
|
+
node.textContent = value;
|
|
47
|
+
}
|
|
48
|
+
export function uiAttr(node: Element, attribute: Attribute, message: Message, values: MessageValues = {}): void {
|
|
49
|
+
node.setAttribute(`data-ui-${attribute}`, message);
|
|
50
|
+
node.setAttribute(`data-ui-${attribute}-values`, JSON.stringify(values));
|
|
51
|
+
node.setAttribute(attribute, t(message, values));
|
|
52
|
+
}
|
|
53
|
+
export function uiDate(node: Element, timestamp: number): void {
|
|
54
|
+
node.setAttribute("data-ui-date", String(timestamp));
|
|
55
|
+
node.textContent = new Intl.DateTimeFormat(language.locale, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }).format(timestamp);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function selectLanguage(pack: LanguagePack): void {
|
|
59
|
+
language = pack;
|
|
60
|
+
try { localStorage.setItem(storageKey, pack.locale); } catch { /* Tab-local choice still applies. */ }
|
|
61
|
+
document.documentElement.lang = pack.locale;
|
|
62
|
+
// Update explicitly marked chrome in place, without rebuilding editors or the player.
|
|
63
|
+
for (const node of Array.from(document.querySelectorAll("[data-ui-text]"))) {
|
|
64
|
+
uiText(node, node.getAttribute("data-ui-text") as Message, JSON.parse(node.getAttribute("data-ui-values") ?? "{}") as MessageValues);
|
|
65
|
+
}
|
|
66
|
+
for (const attribute of attributes) {
|
|
67
|
+
for (const node of Array.from(document.querySelectorAll(`[data-ui-${attribute}]`))) {
|
|
68
|
+
uiAttr(node, attribute, node.getAttribute(`data-ui-${attribute}`) as Message, JSON.parse(node.getAttribute(`data-ui-${attribute}-values`) ?? "{}") as MessageValues);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const node of Array.from(document.querySelectorAll("[data-ui-date]"))) uiDate(node, Number(node.getAttribute("data-ui-date")));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function languageMenu(): HTMLElement {
|
|
75
|
+
const control = document.createElement("div");
|
|
76
|
+
control.className = "parameter-select language-select";
|
|
77
|
+
const trigger = document.createElement("button");
|
|
78
|
+
trigger.type = "button"; trigger.className = "parameter-select-trigger language-toggle";
|
|
79
|
+
trigger.dataset.language = "";
|
|
80
|
+
trigger.innerHTML = icon("globe");
|
|
81
|
+
uiAttr(trigger, "title", "app.language"); uiAttr(trigger, "aria-label", "app.language");
|
|
82
|
+
trigger.setAttribute("aria-haspopup", "listbox"); trigger.setAttribute("aria-expanded", "false");
|
|
83
|
+
const menu = document.createElement("div");
|
|
84
|
+
menu.className = "parameter-select-menu"; menu.id = "studio-languages"; menu.hidden = true;
|
|
85
|
+
menu.setAttribute("role", "listbox"); uiAttr(menu, "aria-label", "app.language");
|
|
86
|
+
trigger.setAttribute("aria-controls", menu.id);
|
|
87
|
+
const options = languages.map(pack => {
|
|
88
|
+
const item = document.createElement("button");
|
|
89
|
+
item.type = "button"; item.className = "parameter-select-option";
|
|
90
|
+
item.lang = pack.locale; item.dir = pack.direction;
|
|
91
|
+
item.textContent = pack.name;
|
|
92
|
+
item.dataset.locale = pack.locale;
|
|
93
|
+
item.setAttribute("role", "option");
|
|
94
|
+
item.addEventListener("click", () => {
|
|
95
|
+
selectLanguage(pack); renderSelection(); close(true);
|
|
96
|
+
});
|
|
97
|
+
return item;
|
|
98
|
+
});
|
|
99
|
+
const renderSelection = (): void => {
|
|
100
|
+
for (const item of options) {
|
|
101
|
+
const selected = item.dataset.locale === language.locale;
|
|
102
|
+
item.classList.toggle("active", selected); item.setAttribute("aria-selected", String(selected));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
menu.append(...options); control.append(trigger, menu);
|
|
106
|
+
const { close } = bindDropdown(control, trigger, menu, options);
|
|
107
|
+
renderSelection();
|
|
108
|
+
return control;
|
|
109
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const paths: Readonly<Record<string, string>> = {
|
|
2
2
|
brand: '<g transform="translate(0 1.282) scale(.091603) translate(-50 -180)"><path fill="currentColor" stroke="none" d="M303.78,277.82c-5.98-8.63-15.83-13.77-26.32-13.77h-93.41c-12.28,0-24.92,7.44-29.6,18.8l-28.26,75.82c-4.6,11.25-4.54,24.05,2.22,34.16,6.79,10.15,19.36,16.15,31.57,16.15h85.98c14.12,0,26.9-8.88,31.84-22.1l29.69-79.57c3.67-9.84,2.29-20.86-3.69-29.48v-.02h-.02ZM252.02,377.25c-.94,2.52-3.39,4.22-6.08,4.22h-83.45c-4.34,0-6.68-2.8-7.49-3.99s-2.5-4.43-.84-8.44l23.85-66.24c2.29-6.35,8.08-10.87,14.82-11.22.33-.02.67-.02,1.02-.02h75.41s12.51,1.42,9.29,14.61l-26.53,71.09h0ZM106.85,361.73s-22.36-11.59-17.66-32.66l29.67-82.23c4.78-13.25,17.36-22.09,31.45-22.09h95.02c6.25,0,12.1,3.03,15.71,8.14l13.89,19.68h-112.97c-8.7,0-16.49,5.41-19.52,13.57l-35.59,95.6h0ZM73.3,323.27s-22.37-11.59-17.66-32.66l29.67-82.23c4.79-13.25,17.37-22.09,31.45-22.09h89.91c6.25,0,12.1,3.03,15.71,8.14l13.89,19.68h-107.84c-8.7,0-16.49,5.41-19.52,13.57l-35.59,95.6h-.02Z"/></g>',
|
|
3
|
+
globe: '<circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="4" ry="9"/><path d="M3 12h18"/>',
|
|
3
4
|
code: '<path d="M9 7 4 12l5 5M15 7l5 5-5 5M13 4l-2 16"/>',
|
|
4
5
|
edit: '<path d="m4 16-.8 4 4-.8L18.5 8.9 15.1 5.5 4 16Z"/><path d="m13.8 6.8 3.4 3.4"/>',
|
|
5
6
|
studio: '<rect x="3" y="4" width="18" height="16" rx="1.5"/><path d="M3 14h18M8 4v10M15 14v6"/>',
|