@johnmorrisdotca/kazoku-ui 0.14.0 → 0.15.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.
- package/CHANGELOG.md +20 -0
- package/README.md +21 -1
- package/dist/embed-panel/choices.d.ts +42 -0
- package/dist/embed-panel/choices.js +48 -0
- package/dist/embed-panel/define.d.ts +1 -0
- package/dist/embed-panel/define.js +9 -0
- package/dist/embed-panel/element.d.ts +84 -0
- package/dist/embed-panel/element.js +684 -0
- package/dist/embed-panel/index.d.ts +5 -0
- package/dist/embed-panel/index.js +6 -0
- package/dist/embed-panel/snippets.d.ts +94 -0
- package/dist/embed-panel/snippets.js +73 -0
- package/dist/embed-panel/styles.d.ts +1 -0
- package/dist/embed-panel/styles.js +43 -0
- package/dist/embed-panel/words.d.ts +43 -0
- package/dist/embed-panel/words.js +69 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +24 -4
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { DEFAULT_KINDS, DEFAULT_SIZE, DEFAULT_SIZES, filterItems, groupsOf, KIND_ADDRESS_KEY, nearestSize, parseKinds, parseSizes, SEARCH_FROM, SIZE_ADDRESS_KEY, type PickItem } from "./choices.ts";
|
|
2
|
+
export { attrText, EMBED_KINDS, embedSnippet, type EmbedKind, type SnippetSpec } from "./snippets.ts";
|
|
3
|
+
export { kindLabel, languageOf, WORDS, wordsFor, type Language, type PanelWords } from "./words.ts";
|
|
4
|
+
export declare const EVENT = "kazoku-embedchange";
|
|
5
|
+
export declare const TAG = "kazoku-embed-panel";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The embed panel without the element: the seven ways a page is made and their text, the sizes, the tab and picker choices, the words. Nothing here touches the DOM, so it imports on a server.
|
|
2
|
+
export { DEFAULT_KINDS, DEFAULT_SIZE, DEFAULT_SIZES, filterItems, groupsOf, KIND_ADDRESS_KEY, nearestSize, parseKinds, parseSizes, SEARCH_FROM, SIZE_ADDRESS_KEY } from "./choices.js";
|
|
3
|
+
export { attrText, EMBED_KINDS, embedSnippet } from "./snippets.js";
|
|
4
|
+
export { kindLabel, languageOf, WORDS, wordsFor } from "./words.js";
|
|
5
|
+
export const EVENT = "kazoku-embedchange";
|
|
6
|
+
export const TAG = "kazoku-embed-panel";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ways `embedSnippet` writes an element into a page, in the order the panel's tabs show them.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { EMBED_KINDS } from "@johnmorrisdotca/kazoku-ui/embed-panel";
|
|
7
|
+
*
|
|
8
|
+
* console.log(EMBED_KINDS.join(", "));
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare const EMBED_KINDS: readonly ["html", "module", "react", "vue", "angular", "svelte", "iframe"];
|
|
12
|
+
/**
|
|
13
|
+
* One of the ways in `EMBED_KINDS`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { EMBED_KINDS, type EmbedKind } from "@johnmorrisdotca/kazoku-ui/embed-panel";
|
|
18
|
+
*
|
|
19
|
+
* const kind: EmbedKind = EMBED_KINDS[2];
|
|
20
|
+
* console.log(kind);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export type EmbedKind = (typeof EMBED_KINDS)[number];
|
|
24
|
+
/**
|
|
25
|
+
* What `embedSnippet` needs to know about an element: where it comes from, what it is called and what it is given.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { embedSnippet, type SnippetSpec } from "@johnmorrisdotca/kazoku-ui/embed-panel";
|
|
30
|
+
*
|
|
31
|
+
* const spec: SnippetSpec = {
|
|
32
|
+
* package: "@scope/dial",
|
|
33
|
+
* cdn: "https://cdn.jsdelivr.net/npm/@scope/dial@1.0.0",
|
|
34
|
+
* tag: "scope-dial",
|
|
35
|
+
* element: "element",
|
|
36
|
+
* entries: [],
|
|
37
|
+
* attrs: [["value", "7"]],
|
|
38
|
+
* name: "dial",
|
|
39
|
+
* iframe: { src: "https://example.com/embed.html?value=7", title: "Dial", width: 320, height: 320 },
|
|
40
|
+
* };
|
|
41
|
+
* console.log(embedSnippet("html", spec));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export interface SnippetSpec {
|
|
45
|
+
/** The npm name, for the `import` lines: `@scope/name`. */
|
|
46
|
+
package: string;
|
|
47
|
+
/** The package on the CDN with its version: `https://cdn.jsdelivr.net/npm/@scope/name@1.2.3`. */
|
|
48
|
+
cdn: string;
|
|
49
|
+
/** The element's tag. */
|
|
50
|
+
tag: string;
|
|
51
|
+
/** The subpath (under the package name) that defines the tag; an empty text is the package's main entry. */
|
|
52
|
+
element: string;
|
|
53
|
+
/** More subpaths to load beside it (data, looks); none is fine. */
|
|
54
|
+
entries: readonly string[];
|
|
55
|
+
/** The attributes, in the order they are written. A value of `""` is written as a bare attribute. */
|
|
56
|
+
attrs: readonly (readonly [string, string])[];
|
|
57
|
+
/** The name the React, Angular and plain module examples use: `planet` gives `Planet`, `PlanetComponent`, `app-planet` and `#planet`. */
|
|
58
|
+
name: string;
|
|
59
|
+
/** The embed page as an iframe. */
|
|
60
|
+
iframe: {
|
|
61
|
+
src: string;
|
|
62
|
+
title: string;
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** The attributes as they are written in a tag: `body="mars" moons`. */
|
|
68
|
+
export declare function attrText(attrs: SnippetSpec["attrs"]): string;
|
|
69
|
+
/**
|
|
70
|
+
* The text that puts an element in a page, for one way of making the page: a script tag from the CDN, an ES module, a React component, a Vue single-file component, an Angular
|
|
71
|
+
* standalone component, a Svelte component, or an iframe.
|
|
72
|
+
*
|
|
73
|
+
* @param kind - The way the page is made.
|
|
74
|
+
* @param spec - The element and what it is given.
|
|
75
|
+
* @returns The text to paste into the page's source.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { embedSnippet } from "@johnmorrisdotca/kazoku-ui/embed-panel";
|
|
80
|
+
*
|
|
81
|
+
* const spec = {
|
|
82
|
+
* package: "@scope/dial",
|
|
83
|
+
* cdn: "https://cdn.jsdelivr.net/npm/@scope/dial@1.0.0",
|
|
84
|
+
* tag: "scope-dial",
|
|
85
|
+
* element: "element",
|
|
86
|
+
* entries: [],
|
|
87
|
+
* attrs: [["value", "7"]] as const,
|
|
88
|
+
* name: "dial",
|
|
89
|
+
* iframe: { src: "https://example.com/embed.html", title: "Dial", width: 320, height: 320 },
|
|
90
|
+
* };
|
|
91
|
+
* console.log(embedSnippet("react", spec));
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function embedSnippet(kind: EmbedKind, spec: SnippetSpec): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The text that puts an element in a page, for each way a page is made: a script tag from a CDN, an ES module, React, Vue, Angular, Svelte, or an iframe of the embed page.
|
|
2
|
+
// Pure: no DOM. A package gives the tag, the entry points and the attributes of its element once and gets all seven; the panel shows them as the reader changes a choice.
|
|
3
|
+
/**
|
|
4
|
+
* The ways `embedSnippet` writes an element into a page, in the order the panel's tabs show them.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { EMBED_KINDS } from "@johnmorrisdotca/kazoku-ui/embed-panel";
|
|
9
|
+
*
|
|
10
|
+
* console.log(EMBED_KINDS.join(", "));
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export const EMBED_KINDS = ["html", "module", "react", "vue", "angular", "svelte", "iframe"];
|
|
14
|
+
const quote = (value) => `"${value.replace(/&/g, "&").replace(/"/g, """)}"`;
|
|
15
|
+
const joinPath = (...parts) => parts.filter((part) => part !== "").join("/");
|
|
16
|
+
/** The attributes as they are written in a tag: `body="mars" moons`. */
|
|
17
|
+
export function attrText(attrs) {
|
|
18
|
+
return attrs.map(([key, value]) => (value === "" ? key : `${key}=${quote(value)}`)).join(" ");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The text that puts an element in a page, for one way of making the page: a script tag from the CDN, an ES module, a React component, a Vue single-file component, an Angular
|
|
22
|
+
* standalone component, a Svelte component, or an iframe.
|
|
23
|
+
*
|
|
24
|
+
* @param kind - The way the page is made.
|
|
25
|
+
* @param spec - The element and what it is given.
|
|
26
|
+
* @returns The text to paste into the page's source.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import { embedSnippet } from "@johnmorrisdotca/kazoku-ui/embed-panel";
|
|
31
|
+
*
|
|
32
|
+
* const spec = {
|
|
33
|
+
* package: "@scope/dial",
|
|
34
|
+
* cdn: "https://cdn.jsdelivr.net/npm/@scope/dial@1.0.0",
|
|
35
|
+
* tag: "scope-dial",
|
|
36
|
+
* element: "element",
|
|
37
|
+
* entries: [],
|
|
38
|
+
* attrs: [["value", "7"]] as const,
|
|
39
|
+
* name: "dial",
|
|
40
|
+
* iframe: { src: "https://example.com/embed.html", title: "Dial", width: 320, height: 320 },
|
|
41
|
+
* };
|
|
42
|
+
* console.log(embedSnippet("react", spec));
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function embedSnippet(kind, spec) {
|
|
46
|
+
const Name = spec.name.charAt(0).toUpperCase() + spec.name.slice(1);
|
|
47
|
+
const attrs = attrText(spec.attrs);
|
|
48
|
+
const subpaths = [spec.element, ...spec.entries];
|
|
49
|
+
if (kind === "iframe") {
|
|
50
|
+
const f = spec.iframe;
|
|
51
|
+
return `<iframe\n src=${quote(f.src)}\n title=${quote(f.title)}\n width="${f.width}"\n height="${f.height}"\n loading="lazy"\n style="border: 0"\n></iframe>`;
|
|
52
|
+
}
|
|
53
|
+
if (kind === "html") {
|
|
54
|
+
const scripts = subpaths.map((path) => `<script type="module" src="${joinPath(spec.cdn, path)}/+esm"></script>`);
|
|
55
|
+
return `${scripts.join("\n")}\n\n<${spec.tag} ${attrs}></${spec.tag}>`;
|
|
56
|
+
}
|
|
57
|
+
const imports = subpaths.map((path) => `import "${joinPath(spec.package, path)}";`);
|
|
58
|
+
if (kind === "module") {
|
|
59
|
+
const sets = spec.attrs.map(([key, value]) => `${spec.name}.setAttribute(${JSON.stringify(key)}, ${JSON.stringify(value)});`);
|
|
60
|
+
return `${imports.join("\n")}\n\nconst ${spec.name} = document.createElement("${spec.tag}");\n${sets.join("\n")}\ndocument.querySelector("#${spec.name}").append(${spec.name});`;
|
|
61
|
+
}
|
|
62
|
+
const dynamic = subpaths.map((path) => `void import("${joinPath(spec.package, path)}");`);
|
|
63
|
+
if (kind === "react") {
|
|
64
|
+
return `import { useEffect } from "react";\n\nexport function ${Name}() {\n useEffect(() => {\n // The element needs a browser, so it is imported in an effect, never at the top of the file.\n${dynamic.map((l) => ` ${l}`).join("\n")}\n }, []);\n return <${spec.tag} ${attrs} />;\n}`;
|
|
65
|
+
}
|
|
66
|
+
if (kind === "vue") {
|
|
67
|
+
return `<script setup lang="ts">\nimport { onMounted } from "vue";\n\nonMounted(() => {\n${dynamic.map((l) => ` ${l}`).join("\n")}\n});\n</script>\n\n<template>\n <${spec.tag} ${attrs}></${spec.tag}>\n</template>`;
|
|
68
|
+
}
|
|
69
|
+
if (kind === "angular") {
|
|
70
|
+
return `import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from "@angular/core";\n\n@Component({\n selector: "app-${spec.name}",\n standalone: true,\n schemas: [CUSTOM_ELEMENTS_SCHEMA],\n template: \`<${spec.tag} ${attrs}></${spec.tag}>\`,\n})\nexport class ${Name}Component implements OnInit {\n ngOnInit(): void {\n // The element needs a browser: ngOnInit also runs when a server renders.\n if (typeof window !== "undefined") {\n${dynamic.map((l) => ` ${l}`).join("\n")}\n }\n }\n}`;
|
|
71
|
+
}
|
|
72
|
+
return `<script lang="ts">\n import { onMount } from "svelte";\n\n onMount(() => {\n${dynamic.map((l) => ` ${l}`).join("\n")}\n });\n</script>\n\n<${spec.tag} ${attrs}></${spec.tag}>`;
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const STYLES = "\n:host {\n --_ink: var(--kazoku-ink, #1b1b1f);\n --_surface: var(--kazoku-surface, #ffffff);\n --_line: var(--kazoku-line, #c9ccd3);\n --_accent: var(--kazoku-accent, #2457d6);\n display: inline-block;\n box-sizing: border-box;\n color: var(--_ink);\n font: 600 0.9rem/1.2 system-ui, -apple-system, \"Hiragino Sans\", \"Noto Sans JP\", \"Segoe UI\", sans-serif;\n color-scheme: light dark;\n}\n@media (prefers-color-scheme: dark) {\n :host(:not([theme=\"light\"]):not([data-scheme=\"light\"])) {\n --_ink: var(--kazoku-ink, #f1f2f6);\n --_surface: var(--kazoku-surface, #1c1d22);\n --_line: var(--kazoku-line, #4a4d58);\n --_accent: var(--kazoku-accent, #8fb0ff);\n }\n}\n:host([theme=\"dark\"]), :host([data-scheme=\"dark\"]:not([theme=\"light\"])) {\n --_ink: var(--kazoku-ink, #f1f2f6);\n --_surface: var(--kazoku-surface, #1c1d22);\n --_line: var(--kazoku-line, #4a4d58);\n --_accent: var(--kazoku-accent, #8fb0ff);\n color-scheme: dark;\n}\n:host([theme=\"light\"]) { color-scheme: light; }\n[hidden] { display: none !important; }\n.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n.group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px; }\nbutton { -webkit-appearance: none; appearance: none; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 48px; min-height: 48px; margin: 0; padding: 0 14px; border: 1px solid var(--_line); border-radius: 24px; background: var(--_surface); color: var(--_ink); font: inherit; cursor: pointer; white-space: nowrap; }\nbutton:hover:not(:disabled) { border-color: var(--_ink); }\nbutton:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }\nbutton[aria-pressed=\"true\"] { background: var(--_ink); color: var(--_surface); border-color: var(--_ink); }\nbutton:disabled { opacity: 0.4; cursor: default; }\nsvg { width: 18px; height: 18px; fill: currentColor; flex: none; }\n\n:host { display: block; box-sizing: border-box; width: 100%; justify-self: stretch; container-type: inline-size; --_preview-bg: var(--kazoku-preview-bg, var(--_surface)); --_preview-w: var(--kazoku-preview-width, 260px); }\n:host([hidden]) { display: none; }\n.panel { display: grid; gap: 12px; min-width: 0; }\n.title { margin: 0; font-size: 1.25rem; font-weight: 700; line-height: 1.25; }\n.row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }\n.lbl { font-weight: 600; }\n.pick { display: grid; gap: 8px; min-width: 0; }\n.builtin { display: grid; gap: 8px; min-width: 0; }\n.chips { display: flex; flex-wrap: wrap; gap: 4px; }\n.search { -webkit-appearance: none; appearance: none; box-sizing: border-box; width: 100%; max-width: 320px; min-height: 48px; margin: 0; padding: 0 16px; border: 1px solid var(--_line); border-radius: 24px; background: var(--_surface); color: var(--_ink); font: inherit; }\n.search:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }\n.search::placeholder { color: var(--_ink); opacity: 0.6; }\n.items { display: flex; flex-wrap: wrap; gap: 4px; max-height: 164px; overflow: auto; padding: 2px; }\n.items button { white-space: normal; text-align: center; }\n.none { margin: 0; font-weight: 400; opacity: 0.75; }\n.tabs { display: flex; gap: 4px; overflow-x: auto; padding: 2px; margin: -2px; }\n.tabs button { flex: 0 0 auto; }\n.tabs button[aria-selected=\"true\"] { background: var(--_ink); color: var(--_surface); border-color: var(--_ink); }\n.grid { display: grid; grid-template-columns: minmax(0, var(--_preview-w)) minmax(0, 1fr); gap: 16px; align-items: start; min-width: 0; }\n:host([no-preview]) .grid { grid-template-columns: minmax(0, 1fr); }\n@container (max-width: 640px) { .grid { grid-template-columns: minmax(0, 1fr); } }\n.preview { box-sizing: border-box; display: grid; place-items: center; min-width: 0; min-height: 160px; padding: 8px; overflow: hidden; border: 1px solid var(--_line); border-radius: 12px; background: var(--_preview-bg); }\n.frame { position: relative; overflow: hidden; max-width: 100%; }\n.fit { display: grid; place-items: center; transform-origin: 0 0; }\n.fit > * { max-width: none; }\n.fit iframe { border: 0; display: block; }\n.codecol { display: grid; gap: 10px; min-width: 0; }\n.note { margin: 0; font-weight: 400; font-size: 0.85rem; }\n.codebox { box-sizing: border-box; margin: 0; padding: 12px; overflow: auto; max-height: 340px; border: 1px solid var(--_line); border-radius: 12px; background: var(--_surface); color: var(--_ink); font: 400 0.8rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; tab-size: 2; white-space: pre; }\n.codebox:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }\n.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }\na.btn { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 48px; min-width: 48px; padding: 0 14px; border: 1px solid var(--_line); border-radius: 24px; background: var(--_surface); color: var(--_ink); font: inherit; text-decoration: none; white-space: nowrap; }\na.btn:hover { border-color: var(--_ink); }\na.btn:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }\nbutton.primary { background: var(--_accent); border-color: var(--_accent); color: var(--_surface); }\nbutton.primary:hover:not(:disabled) { border-color: var(--_ink); }\n.status { font-weight: 400; font-size: 0.85rem; min-height: 1.2em; }\n.credits { margin: 0; font-weight: 400; font-size: 0.85rem; opacity: 0.85; }\n";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BASE_STYLES } from "../shared/base.js";
|
|
2
|
+
// The panel: a picker, the size buttons, the tabs, then the picture beside the code. It stacks when its own box is narrow (a container query on the host, so it
|
|
3
|
+
// follows the box it sits in, not the window). The picture sits in a box it always fits: the element inside is scaled down to the box, never cut off.
|
|
4
|
+
export const STYLES = `${BASE_STYLES}
|
|
5
|
+
:host { display: block; box-sizing: border-box; width: 100%; justify-self: stretch; container-type: inline-size; --_preview-bg: var(--kazoku-preview-bg, var(--_surface)); --_preview-w: var(--kazoku-preview-width, 260px); }
|
|
6
|
+
:host([hidden]) { display: none; }
|
|
7
|
+
.panel { display: grid; gap: 12px; min-width: 0; }
|
|
8
|
+
.title { margin: 0; font-size: 1.25rem; font-weight: 700; line-height: 1.25; }
|
|
9
|
+
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; }
|
|
10
|
+
.lbl { font-weight: 600; }
|
|
11
|
+
.pick { display: grid; gap: 8px; min-width: 0; }
|
|
12
|
+
.builtin { display: grid; gap: 8px; min-width: 0; }
|
|
13
|
+
.chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
14
|
+
.search { -webkit-appearance: none; appearance: none; box-sizing: border-box; width: 100%; max-width: 320px; min-height: 48px; margin: 0; padding: 0 16px; border: 1px solid var(--_line); border-radius: 24px; background: var(--_surface); color: var(--_ink); font: inherit; }
|
|
15
|
+
.search:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }
|
|
16
|
+
.search::placeholder { color: var(--_ink); opacity: 0.6; }
|
|
17
|
+
.items { display: flex; flex-wrap: wrap; gap: 4px; max-height: 164px; overflow: auto; padding: 2px; }
|
|
18
|
+
.items button { white-space: normal; text-align: center; }
|
|
19
|
+
.none { margin: 0; font-weight: 400; opacity: 0.75; }
|
|
20
|
+
.tabs { display: flex; gap: 4px; overflow-x: auto; padding: 2px; margin: -2px; }
|
|
21
|
+
.tabs button { flex: 0 0 auto; }
|
|
22
|
+
.tabs button[aria-selected="true"] { background: var(--_ink); color: var(--_surface); border-color: var(--_ink); }
|
|
23
|
+
.grid { display: grid; grid-template-columns: minmax(0, var(--_preview-w)) minmax(0, 1fr); gap: 16px; align-items: start; min-width: 0; }
|
|
24
|
+
:host([no-preview]) .grid { grid-template-columns: minmax(0, 1fr); }
|
|
25
|
+
@container (max-width: 640px) { .grid { grid-template-columns: minmax(0, 1fr); } }
|
|
26
|
+
.preview { box-sizing: border-box; display: grid; place-items: center; min-width: 0; min-height: 160px; padding: 8px; overflow: hidden; border: 1px solid var(--_line); border-radius: 12px; background: var(--_preview-bg); }
|
|
27
|
+
.frame { position: relative; overflow: hidden; max-width: 100%; }
|
|
28
|
+
.fit { display: grid; place-items: center; transform-origin: 0 0; }
|
|
29
|
+
.fit > * { max-width: none; }
|
|
30
|
+
.fit iframe { border: 0; display: block; }
|
|
31
|
+
.codecol { display: grid; gap: 10px; min-width: 0; }
|
|
32
|
+
.note { margin: 0; font-weight: 400; font-size: 0.85rem; }
|
|
33
|
+
.codebox { box-sizing: border-box; margin: 0; padding: 12px; overflow: auto; max-height: 340px; border: 1px solid var(--_line); border-radius: 12px; background: var(--_surface); color: var(--_ink); font: 400 0.8rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; tab-size: 2; white-space: pre; }
|
|
34
|
+
.codebox:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }
|
|
35
|
+
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
36
|
+
a.btn { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 48px; min-width: 48px; padding: 0 14px; border: 1px solid var(--_line); border-radius: 24px; background: var(--_surface); color: var(--_ink); font: inherit; text-decoration: none; white-space: nowrap; }
|
|
37
|
+
a.btn:hover { border-color: var(--_ink); }
|
|
38
|
+
a.btn:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }
|
|
39
|
+
button.primary { background: var(--_accent); border-color: var(--_accent); color: var(--_surface); }
|
|
40
|
+
button.primary:hover:not(:disabled) { border-color: var(--_ink); }
|
|
41
|
+
.status { font-weight: 400; font-size: 0.85rem; min-height: 1.2em; }
|
|
42
|
+
.credits { margin: 0; font-weight: 400; font-size: 0.85rem; opacity: 0.85; }
|
|
43
|
+
`;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface PanelWords {
|
|
2
|
+
/** The panel's heading, when it shows one (`heading` attribute). */
|
|
3
|
+
title: string;
|
|
4
|
+
/** The name of the group of size buttons. */
|
|
5
|
+
size: string;
|
|
6
|
+
/** The name of the row of tabs. */
|
|
7
|
+
kinds: string;
|
|
8
|
+
/** The name of the picker. */
|
|
9
|
+
pick: string;
|
|
10
|
+
search: string;
|
|
11
|
+
all: string;
|
|
12
|
+
/** Said under the picker when the search finds nothing. */
|
|
13
|
+
none: string;
|
|
14
|
+
/** The name of the picture beside the code. */
|
|
15
|
+
preview: string;
|
|
16
|
+
/** The name of the code box. */
|
|
17
|
+
code: string;
|
|
18
|
+
copy: string;
|
|
19
|
+
copied: string;
|
|
20
|
+
copyFail: string;
|
|
21
|
+
open: string;
|
|
22
|
+
docs: string;
|
|
23
|
+
credits: string;
|
|
24
|
+
/** Tab names (`kind_<id>`). */
|
|
25
|
+
kind_html: string;
|
|
26
|
+
kind_module: string;
|
|
27
|
+
kind_react: string;
|
|
28
|
+
kind_vue: string;
|
|
29
|
+
kind_angular: string;
|
|
30
|
+
kind_svelte: string;
|
|
31
|
+
kind_iframe: string;
|
|
32
|
+
kind_image: string;
|
|
33
|
+
kind_element: string;
|
|
34
|
+
kind_url: string;
|
|
35
|
+
kind_svg: string;
|
|
36
|
+
}
|
|
37
|
+
export type Language = "en" | "ja";
|
|
38
|
+
export declare const WORDS: Record<Language, PanelWords>;
|
|
39
|
+
/** The language to use for a `lang` value such as "ja", "ja-JP" or "en-CA": Japanese for ja, English for anything else. */
|
|
40
|
+
export declare const languageOf: (lang: string | null | undefined) => Language;
|
|
41
|
+
export declare const wordsFor: (language: Language, overrides?: Partial<PanelWords>) => PanelWords;
|
|
42
|
+
/** What a tab says: the family's word for the id, else the page's own `labels`, else the id itself. */
|
|
43
|
+
export declare function kindLabel(id: string, language: Language, labels?: Readonly<Record<string, string>>, overrides?: Partial<PanelWords>): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const WORDS = {
|
|
2
|
+
en: {
|
|
3
|
+
title: "Put it in your page",
|
|
4
|
+
size: "Size",
|
|
5
|
+
kinds: "How your page is made",
|
|
6
|
+
pick: "What to put in",
|
|
7
|
+
search: "Search",
|
|
8
|
+
all: "All",
|
|
9
|
+
none: "Nothing matches",
|
|
10
|
+
preview: "Preview",
|
|
11
|
+
code: "Code",
|
|
12
|
+
copy: "Copy code",
|
|
13
|
+
copied: "Copied",
|
|
14
|
+
copyFail: "Select the code and copy it by hand.",
|
|
15
|
+
open: "Open the embed page",
|
|
16
|
+
docs: "Read the docs",
|
|
17
|
+
credits: "Credit",
|
|
18
|
+
kind_html: "HTML",
|
|
19
|
+
kind_module: "ES module",
|
|
20
|
+
kind_react: "React",
|
|
21
|
+
kind_vue: "Vue",
|
|
22
|
+
kind_angular: "Angular",
|
|
23
|
+
kind_svelte: "Svelte",
|
|
24
|
+
kind_iframe: "iframe",
|
|
25
|
+
kind_image: "Image",
|
|
26
|
+
kind_element: "Custom element",
|
|
27
|
+
kind_url: "iframe URL",
|
|
28
|
+
kind_svg: "SVG",
|
|
29
|
+
},
|
|
30
|
+
ja: {
|
|
31
|
+
title: "自分のページに入れる",
|
|
32
|
+
size: "大きさ",
|
|
33
|
+
kinds: "ページの作り方",
|
|
34
|
+
pick: "入れるもの",
|
|
35
|
+
search: "さがす",
|
|
36
|
+
all: "すべて",
|
|
37
|
+
none: "見つかりません",
|
|
38
|
+
preview: "プレビュー",
|
|
39
|
+
code: "コード",
|
|
40
|
+
copy: "コードをコピー",
|
|
41
|
+
copied: "コピーしました",
|
|
42
|
+
copyFail: "コードを選んで、手でコピーしてください。",
|
|
43
|
+
open: "埋め込みページを開く",
|
|
44
|
+
docs: "ドキュメントを読む",
|
|
45
|
+
credits: "クレジット",
|
|
46
|
+
kind_html: "HTML",
|
|
47
|
+
kind_module: "ESモジュール",
|
|
48
|
+
kind_react: "React",
|
|
49
|
+
kind_vue: "Vue",
|
|
50
|
+
kind_angular: "Angular",
|
|
51
|
+
kind_svelte: "Svelte",
|
|
52
|
+
kind_iframe: "iframe",
|
|
53
|
+
kind_image: "画像",
|
|
54
|
+
kind_element: "カスタム要素",
|
|
55
|
+
kind_url: "iframe URL",
|
|
56
|
+
kind_svg: "SVG",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
/** The language to use for a `lang` value such as "ja", "ja-JP" or "en-CA": Japanese for ja, English for anything else. */
|
|
60
|
+
export const languageOf = (lang) => (/^ja\b/i.test(lang ?? "") ? "ja" : "en");
|
|
61
|
+
export const wordsFor = (language, overrides = {}) => ({ ...WORDS[language], ...overrides });
|
|
62
|
+
/** What a tab says: the family's word for the id, else the page's own `labels`, else the id itself. */
|
|
63
|
+
export function kindLabel(id, language, labels = {}, overrides = {}) {
|
|
64
|
+
const mine = labels[id];
|
|
65
|
+
if (mine !== undefined && mine !== "")
|
|
66
|
+
return mine;
|
|
67
|
+
const words = wordsFor(language, overrides);
|
|
68
|
+
return words[`kind_${id}`] ?? id;
|
|
69
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** The subpaths of the parts, in the order they were added. */
|
|
2
|
-
export declare const PARTS: readonly ["view-controls", "theme-switch", "language-picker", "address-state", "save-image", "speed-buttons", "renderer", "testing"];
|
|
2
|
+
export declare const PARTS: readonly ["view-controls", "theme-switch", "language-picker", "address-state", "save-image", "speed-buttons", "embed-panel", "renderer", "testing"];
|
|
3
3
|
export type Part = (typeof PARTS)[number];
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Kazoku UI 家族: the family's shared interface parts, each its own subpath so a page loads only what it uses.
|
|
2
2
|
/** The subpaths of the parts, in the order they were added. */
|
|
3
|
-
export const PARTS = ["view-controls", "theme-switch", "language-picker", "address-state", "save-image", "speed-buttons", "renderer", "testing"];
|
|
3
|
+
export const PARTS = ["view-controls", "theme-switch", "language-picker", "address-state", "save-image", "speed-buttons", "embed-panel", "renderer", "testing"];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/kazoku-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Kazoku UI 家族, the family's shared interface parts as their own versioned package: view controls for any 3D or zoomable view (angle, tilt, auto-turn, zoom about the pointer, pan, touch, address state) a light, dark and auto theme switch, a language picker, choices kept in the address, a save-as-image button and one set of speed buttons (0.5×, 1×, 2× or your own steps), each a framework-free custom element and a plain controller. Zero dependencies; the shared 3D renderer takes three.js as an optional peer.",
|
|
3
|
+
"version": "0.15.1",
|
|
4
|
+
"description": "Kazoku UI 家族, the family's shared interface parts as their own versioned package: view controls for any 3D or zoomable view (angle, tilt, auto-turn, zoom about the pointer, pan, touch, address state) a light, dark and auto theme switch, a language picker, choices kept in the address, a save-as-image button and one set of speed buttons (0.5×, 1×, 2× or your own steps) and the \"Put it in your page\" embed panel (a compact picker, sizes, HTML, ES module, React, Vue, Angular, Svelte and iframe tabs, a picture that fits its box, Copy), each a framework-free custom element and a plain controller. Zero dependencies; the shared 3D renderer takes three.js as an optional peer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"view-controls",
|
|
7
7
|
"3d",
|
|
@@ -33,7 +33,11 @@
|
|
|
33
33
|
"renderer",
|
|
34
34
|
"speed-buttons",
|
|
35
35
|
"playback-speed",
|
|
36
|
-
"time-speed"
|
|
36
|
+
"time-speed",
|
|
37
|
+
"embed",
|
|
38
|
+
"embed-panel",
|
|
39
|
+
"snippets",
|
|
40
|
+
"iframe"
|
|
37
41
|
],
|
|
38
42
|
"homepage": "https://johnmorrisdotca.github.io/kazoku-ui/",
|
|
39
43
|
"repository": {
|
|
@@ -51,7 +55,8 @@
|
|
|
51
55
|
"./dist/theme-switch/define.js",
|
|
52
56
|
"./dist/language-picker/define.js",
|
|
53
57
|
"./dist/save-image/define.js",
|
|
54
|
-
"./dist/speed-buttons/define.js"
|
|
58
|
+
"./dist/speed-buttons/define.js",
|
|
59
|
+
"./dist/embed-panel/define.js"
|
|
55
60
|
],
|
|
56
61
|
"main": "./dist/index.js",
|
|
57
62
|
"module": "./dist/index.js",
|
|
@@ -142,6 +147,21 @@
|
|
|
142
147
|
"import": "./dist/speed-buttons/define.js",
|
|
143
148
|
"default": "./dist/speed-buttons/define.js"
|
|
144
149
|
},
|
|
150
|
+
"./embed-panel": {
|
|
151
|
+
"types": "./dist/embed-panel/index.d.ts",
|
|
152
|
+
"import": "./dist/embed-panel/index.js",
|
|
153
|
+
"default": "./dist/embed-panel/index.js"
|
|
154
|
+
},
|
|
155
|
+
"./embed-panel/element": {
|
|
156
|
+
"types": "./dist/embed-panel/element.d.ts",
|
|
157
|
+
"import": "./dist/embed-panel/element.js",
|
|
158
|
+
"default": "./dist/embed-panel/element.js"
|
|
159
|
+
},
|
|
160
|
+
"./embed-panel/define": {
|
|
161
|
+
"types": "./dist/embed-panel/define.d.ts",
|
|
162
|
+
"import": "./dist/embed-panel/define.js",
|
|
163
|
+
"default": "./dist/embed-panel/define.js"
|
|
164
|
+
},
|
|
145
165
|
"./renderer": {
|
|
146
166
|
"types": "./dist/renderer/index.d.ts",
|
|
147
167
|
"import": "./dist/renderer/index.js",
|