@povio/ui 5.0.0-rc.13 → 5.0.0-rc.14
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/README.md +8 -0
- package/dist/{AppCanvasPage-DtWEvs_Y.js → AppCanvasPage-vta1j5YQ.js} +15 -8
- package/dist/{CanvasComponentsFrame-CbgJJv-3.js → CanvasComponentsFrame-D6Mh_G3q.js} +1 -1
- package/dist/{ComponentGallery-COqEqqN6.js → ComponentGallery-CzKU899_.js} +1 -1
- package/dist/{UIConfigurator-DekPXjv9.js → UIConfigurator-CjAW1SJY.js} +398 -53
- package/dist/config/app-canvas-tanstack.js +1 -1
- package/dist/config/app-canvas.js +1 -1
- package/dist/config/ui-configurator-vite.d.ts +2 -0
- package/dist/config/ui-configurator-vite.js +234 -8
- package/dist/config/ui-configurator.js +1 -1
- package/dist/styles/configurator.css +202 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -322,3 +322,11 @@ Then open `/_canvas` directly. `enabled` accepts a boolean or a synchronous pred
|
|
|
322
322
|
The canvas supports named workspaces, enabled pages per workspace, and independent positions per breakpoint. The selected page's HTML layers expand directly beneath its row in the sidebar.
|
|
323
323
|
|
|
324
324
|
The existing `uiConfigurator` Vite plugin also saves canvas settings to `povio.canvas.json` in the app root. Configure an alternate file with `uiConfigurator({ canvasFile: "povio.canvas.json" })`, or disable this with `canvasFile: false`. No Git commands run: review and commit this file through the normal repository workflow. Conflicting writes from different sessions are rejected instead of silently overwriting settings. Without this development-server connection, settings stay in browser storage; export/import remains available. Theme versions from the Configure UI panel save separately to `povio.themes.json` (`themeVersionsFile` option).
|
|
325
|
+
|
|
326
|
+
### Fontsource font families
|
|
327
|
+
|
|
328
|
+
In Configure UI, open Typography → Font families → Browse Fontsource. Search or filter the catalog, select a family to preview it, then choose Heading, Body, or Add to available fonts and click **Add font to app**. In development, this installs the verified Fontsource package with the application's existing package manager, updates its package.json and lockfile, and adds CSS imports. Variable families with a weight axis are preferred; static families load their supported weights and styles. Existing compatible packages and imports are reused.
|
|
329
|
+
|
|
330
|
+
The selected role is a live theme-version change. Close keeps it in that version; **Apply theme** writes the selected version's settings to the repo. Adding a font makes its assets available to every version without changing other versions' role assignments. Package installation scripts are disabled, and no Git commit or push is performed. Catalog browsing and temporary CDN previews also work without the development installer.
|
|
331
|
+
|
|
332
|
+
The Vite plugin uses `src/styles/globals.css` by default. For another CSS entrypoint, configure `uiConfigurator({ fontCssFile: "src/app.css" })`; it must exist inside the application's Vite root. Set `fontCssFile: false` to disable font installation. Restart Vite after adding or changing the plugin configuration.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as createLucideIcon } from "./createLucideIcon-LcezOE4z.js";
|
|
2
|
-
import { _ as Moon, a as inspectElement, c as HighlightOverlay, d as useUIConfiguration, f as Trash, g as Pencil, h as Pin, l as InspectorDetails, m as Plus, p as Sun, r as useRepositoryStorage, s as getBoxHighlights, t as UIConfigurationModal } from "./UIConfigurator-
|
|
2
|
+
import { _ as Moon, a as inspectElement, c as HighlightOverlay, d as useUIConfiguration, f as Trash, g as Pencil, h as Pin, l as InspectorDetails, m as Plus, p as Sun, r as useRepositoryStorage, s as getBoxHighlights, t as UIConfigurationModal } from "./UIConfigurator-CjAW1SJY.js";
|
|
3
3
|
import { t as ChevronDown } from "./chevron-down-B4Pr8bD3.js";
|
|
4
4
|
import { t as ChevronRight } from "./chevron-right-3nKJR5Bb.js";
|
|
5
5
|
import { i as Search } from "./selectDesktop.cva-CHAMxhfO.js";
|
|
@@ -3030,18 +3030,25 @@ function startCanvasDocument(doc, options) {
|
|
|
3030
3030
|
doc.addEventListener("load", schedule, true);
|
|
3031
3031
|
doc.fonts?.ready.then(schedule);
|
|
3032
3032
|
const focusDescriptor = Object.getOwnPropertyDescriptor(win.HTMLElement.prototype, "focus");
|
|
3033
|
-
const elementFocus =
|
|
3033
|
+
const elementFocus = win.HTMLElement.prototype.focus;
|
|
3034
3034
|
const windowFocusDescriptor = Object.getOwnPropertyDescriptor(win, "focus");
|
|
3035
|
-
const windowFocus =
|
|
3035
|
+
const windowFocus = win.focus;
|
|
3036
3036
|
Object.defineProperty(win.HTMLElement.prototype, "focus", {
|
|
3037
|
-
|
|
3037
|
+
configurable: focusDescriptor.configurable,
|
|
3038
|
+
enumerable: focusDescriptor.enumerable,
|
|
3039
|
+
writable: true,
|
|
3038
3040
|
value(focusOptions) {
|
|
3039
3041
|
if (doc.hasFocus()) elementFocus.call(this, focusOptions);
|
|
3040
3042
|
}
|
|
3041
3043
|
});
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3044
|
+
Object.defineProperty(win, "focus", {
|
|
3045
|
+
configurable: windowFocusDescriptor?.configurable ?? true,
|
|
3046
|
+
enumerable: windowFocusDescriptor?.enumerable ?? true,
|
|
3047
|
+
writable: true,
|
|
3048
|
+
value() {
|
|
3049
|
+
if (doc.hasFocus()) windowFocus.call(win);
|
|
3050
|
+
}
|
|
3051
|
+
});
|
|
3045
3052
|
refresh();
|
|
3046
3053
|
return {
|
|
3047
3054
|
refresh,
|
|
@@ -3140,7 +3147,7 @@ function AppCanvasFrameBridge({ href }) {
|
|
|
3140
3147
|
const APP_CANVAS_PATH = "/_canvas";
|
|
3141
3148
|
/** Must match `componentsFrameParam`; kept inline so the canvas page does not load the gallery eagerly. */
|
|
3142
3149
|
const componentsParam = "povio-components";
|
|
3143
|
-
const CanvasComponentsFrame = lazy(() => import("./CanvasComponentsFrame-
|
|
3150
|
+
const CanvasComponentsFrame = lazy(() => import("./CanvasComponentsFrame-D6Mh_G3q.js"));
|
|
3144
3151
|
/** Ready-made route page. Mount beneath the app's UIConfigurationProvider. */
|
|
3145
3152
|
function AppCanvasPage(t0) {
|
|
3146
3153
|
const $ = c(24);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentArtboard } from "./ComponentGallery-
|
|
1
|
+
import { ComponentArtboard } from "./ComponentGallery-CzKU899_.js";
|
|
2
2
|
import { c } from "react/compiler-runtime";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/app-canvas/CanvasComponentsFrame.tsx
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as createLucideIcon } from "./createLucideIcon-LcezOE4z.js";
|
|
2
|
-
import { o as inspectTokens } from "./UIConfigurator-
|
|
2
|
+
import { o as inspectTokens } from "./UIConfigurator-CjAW1SJY.js";
|
|
3
3
|
import { n as ArrowLeft } from "./DropdownMenu-5jmtgLXd.js";
|
|
4
4
|
import { i as ArrowRight } from "./DropdownMenuDesktop-Cl9gZVq0.js";
|
|
5
5
|
import { n as Italic, r as Bold, t as Underline } from "./underline-66hmELxa.js";
|
|
@@ -731,7 +731,7 @@ function UIConfigurationProvider(t0) {
|
|
|
731
731
|
let t4;
|
|
732
732
|
if ($[4] !== isOpen || $[5] !== open) {
|
|
733
733
|
t4 = () => {
|
|
734
|
-
if (isOpen) setRevision(_temp5$
|
|
734
|
+
if (isOpen) setRevision(_temp5$3);
|
|
735
735
|
else open();
|
|
736
736
|
setFloating(true);
|
|
737
737
|
};
|
|
@@ -883,7 +883,7 @@ function UIConfigurationProvider(t0) {
|
|
|
883
883
|
} else t17 = $[49];
|
|
884
884
|
return t17;
|
|
885
885
|
}
|
|
886
|
-
function _temp5$
|
|
886
|
+
function _temp5$3(value_1) {
|
|
887
887
|
return value_1 + 1;
|
|
888
888
|
}
|
|
889
889
|
function _temp4$4(value_0) {
|
|
@@ -904,6 +904,360 @@ function useUIConfiguration() {
|
|
|
904
904
|
return context;
|
|
905
905
|
}
|
|
906
906
|
//#endregion
|
|
907
|
+
//#region src/configurator/theme-writer-protocol.ts
|
|
908
|
+
/** Shared by the browser configurator and the isolated Node Vite entry. */
|
|
909
|
+
const themeWriterMetaName = "povio-ui-theme-writer";
|
|
910
|
+
const themeWriterTokenHeader = "x-povio-ui-theme-token";
|
|
911
|
+
//#endregion
|
|
912
|
+
//#region src/configurator/theme-writer-client.ts
|
|
913
|
+
function getThemeWriter() {
|
|
914
|
+
if (typeof document === "undefined") return null;
|
|
915
|
+
const content = document.querySelector(`meta[name="${themeWriterMetaName}"]`)?.content;
|
|
916
|
+
if (!content) return null;
|
|
917
|
+
try {
|
|
918
|
+
const connection = JSON.parse(content);
|
|
919
|
+
if (typeof connection.endpoint === "string" && connection.endpoint.startsWith("/") && !connection.endpoint.startsWith("//") && typeof connection.token === "string" && typeof connection.file === "string") return connection;
|
|
920
|
+
} catch {}
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
async function saveThemeCss(connection, css) {
|
|
924
|
+
const response = await fetch(connection.endpoint, {
|
|
925
|
+
method: "POST",
|
|
926
|
+
headers: {
|
|
927
|
+
"Content-Type": "application/json",
|
|
928
|
+
[themeWriterTokenHeader]: connection.token
|
|
929
|
+
},
|
|
930
|
+
body: JSON.stringify({ css })
|
|
931
|
+
});
|
|
932
|
+
if (!response.ok) {
|
|
933
|
+
const result = await response.json().catch(() => null);
|
|
934
|
+
throw new Error(result?.error ?? `Theme save failed (${response.status}).`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
//#endregion
|
|
938
|
+
//#region src/configurator/fontsource-client.ts
|
|
939
|
+
const validId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
940
|
+
let catalog;
|
|
941
|
+
/** One metadata request per page, not one font download per search result. */
|
|
942
|
+
function loadFontsourceCatalog() {
|
|
943
|
+
catalog ??= fetch("https://api.fontsource.org/v1/fonts", { signal: AbortSignal.timeout(15e3) }).then(async (response) => {
|
|
944
|
+
if (!response.ok) throw new Error("Fontsource is unavailable. Try again.");
|
|
945
|
+
const data = await response.json();
|
|
946
|
+
if (!Array.isArray(data)) throw new Error("Fontsource returned an invalid catalog.");
|
|
947
|
+
return data.filter((font) => font && typeof font.id === "string" && font.id.length <= 100 && validId.test(font.id) && typeof font.family === "string" && typeof font.category === "string" && typeof font.variable === "boolean" && Array.isArray(font.subsets) && font.subsets.every((s) => typeof s === "string" && validId.test(s)) && typeof font.defSubset === "string" && font.subsets.includes(font.defSubset) && Array.isArray(font.weights) && font.weights.length > 0 && font.weights.every((w) => typeof w === "number" && w > 0 && w <= 1e3) && Array.isArray(font.styles) && (font.styles.includes("normal") || font.styles.includes("italic")) && font.type !== "icons").sort((a, b) => a.family.localeCompare(b.family));
|
|
948
|
+
}).catch((error) => {
|
|
949
|
+
catalog = void 0;
|
|
950
|
+
throw error;
|
|
951
|
+
});
|
|
952
|
+
return catalog;
|
|
953
|
+
}
|
|
954
|
+
function fontsourceWriter() {
|
|
955
|
+
const writer = getThemeWriter();
|
|
956
|
+
const fonts = writer?.fontsource;
|
|
957
|
+
return fonts && typeof fonts.endpoint === "string" && fonts.endpoint.startsWith("/") && !fonts.endpoint.startsWith("//") && typeof fonts.cssFile === "string" ? {
|
|
958
|
+
...fonts,
|
|
959
|
+
token: writer.token
|
|
960
|
+
} : null;
|
|
961
|
+
}
|
|
962
|
+
async function addFontsourceFamily(id) {
|
|
963
|
+
const writer = fontsourceWriter();
|
|
964
|
+
if (!writer) throw new Error("Adding fonts requires the Povio UI Vite plugin in a development server.");
|
|
965
|
+
const response = await fetch(writer.endpoint, {
|
|
966
|
+
method: "POST",
|
|
967
|
+
headers: {
|
|
968
|
+
"Content-Type": "application/json",
|
|
969
|
+
[themeWriterTokenHeader]: writer.token
|
|
970
|
+
},
|
|
971
|
+
body: JSON.stringify({ id })
|
|
972
|
+
});
|
|
973
|
+
const result = await response.json();
|
|
974
|
+
if (!response.ok) throw new Error(result.error ?? "Could not add this font to the app.");
|
|
975
|
+
if (typeof result.family !== "string" || typeof result.packageName !== "string" || !Array.isArray(result.imports)) throw new Error("The font installer returned an invalid response.");
|
|
976
|
+
return result;
|
|
977
|
+
}
|
|
978
|
+
/** The static preview is temporary; installed variable families use their full range. */
|
|
979
|
+
function fontsourcePreview(font) {
|
|
980
|
+
const subset = font.subsets.includes("latin") ? "latin" : font.defSubset;
|
|
981
|
+
const weight = font.weights.includes(400) ? 400 : font.weights[0];
|
|
982
|
+
const style = font.styles.includes("normal") ? "normal" : "italic";
|
|
983
|
+
return {
|
|
984
|
+
alias: `Povio Fontsource preview ${font.id}`,
|
|
985
|
+
weight,
|
|
986
|
+
style,
|
|
987
|
+
url: `https://cdn.jsdelivr.net/fontsource/fonts/${font.id}@latest/${subset}-${weight}-${style}.woff2`
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
//#endregion
|
|
991
|
+
//#region src/configurator/FontsourcePicker.tsx
|
|
992
|
+
function FontsourcePicker({ roles, onAdded }) {
|
|
993
|
+
const trigger = useRef(null);
|
|
994
|
+
const mounted = useRef(true);
|
|
995
|
+
useEffect(() => {
|
|
996
|
+
mounted.current = true;
|
|
997
|
+
return () => {
|
|
998
|
+
mounted.current = false;
|
|
999
|
+
};
|
|
1000
|
+
}, []);
|
|
1001
|
+
const [open, setOpen] = useState(false);
|
|
1002
|
+
const [fonts, setFonts] = useState([]);
|
|
1003
|
+
const [loading, setLoading] = useState(false);
|
|
1004
|
+
const [query, setQuery] = useState("");
|
|
1005
|
+
const [limit, setLimit] = useState(30);
|
|
1006
|
+
const [category, setCategory] = useState("");
|
|
1007
|
+
const [variableOnly, setVariableOnly] = useState(false);
|
|
1008
|
+
useEffect(() => setLimit(30), [
|
|
1009
|
+
query,
|
|
1010
|
+
category,
|
|
1011
|
+
variableOnly
|
|
1012
|
+
]);
|
|
1013
|
+
const [selected, setSelected] = useState(null);
|
|
1014
|
+
const [sample, setSample] = useState("The quick brown fox jumps over the lazy dog.");
|
|
1015
|
+
const [previewState, setPreviewState] = useState("loading");
|
|
1016
|
+
const [role, setRole] = useState(roles[0]?.token ?? "");
|
|
1017
|
+
const [adding, setAdding] = useState(false);
|
|
1018
|
+
const [error, setError] = useState("");
|
|
1019
|
+
const [notice, setNotice] = useState("");
|
|
1020
|
+
const [retry, setRetry] = useState(0);
|
|
1021
|
+
const writer = fontsourceWriter();
|
|
1022
|
+
useEffect(() => {
|
|
1023
|
+
if (!open) return;
|
|
1024
|
+
let active = true;
|
|
1025
|
+
setLoading(true);
|
|
1026
|
+
setError("");
|
|
1027
|
+
loadFontsourceCatalog().then((value) => {
|
|
1028
|
+
if (active) setFonts(value);
|
|
1029
|
+
}).catch((cause) => {
|
|
1030
|
+
if (active) setError(cause instanceof Error ? cause.message : "Could not load Fontsource.");
|
|
1031
|
+
}).finally(() => {
|
|
1032
|
+
if (active) setLoading(false);
|
|
1033
|
+
});
|
|
1034
|
+
return () => {
|
|
1035
|
+
active = false;
|
|
1036
|
+
};
|
|
1037
|
+
}, [open, retry]);
|
|
1038
|
+
useEffect(() => {
|
|
1039
|
+
if (!open || !selected) return;
|
|
1040
|
+
let active_0 = true;
|
|
1041
|
+
const doc = trigger.current?.ownerDocument ?? document;
|
|
1042
|
+
const preview = fontsourcePreview(selected);
|
|
1043
|
+
const face = new FontFace(preview.alias, `url("${preview.url}")`, {
|
|
1044
|
+
weight: String(preview.weight),
|
|
1045
|
+
style: preview.style
|
|
1046
|
+
});
|
|
1047
|
+
setPreviewState("loading");
|
|
1048
|
+
face.load().then((loaded) => {
|
|
1049
|
+
if (active_0) {
|
|
1050
|
+
doc.fonts.add(loaded);
|
|
1051
|
+
setPreviewState("ready");
|
|
1052
|
+
}
|
|
1053
|
+
}).catch(() => {
|
|
1054
|
+
if (active_0) setPreviewState("error");
|
|
1055
|
+
});
|
|
1056
|
+
return () => {
|
|
1057
|
+
active_0 = false;
|
|
1058
|
+
doc.fonts.delete(face);
|
|
1059
|
+
};
|
|
1060
|
+
}, [open, selected]);
|
|
1061
|
+
const filtered = useMemo(() => fonts.filter((font) => font.family.toLowerCase().includes(query.trim().toLowerCase()) && (!category || category === font.category) && (!variableOnly || font.variable)), [
|
|
1062
|
+
fonts,
|
|
1063
|
+
query,
|
|
1064
|
+
category,
|
|
1065
|
+
variableOnly
|
|
1066
|
+
]);
|
|
1067
|
+
const preview_0 = selected ? fontsourcePreview(selected) : null;
|
|
1068
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1069
|
+
className: "studio-fontsource",
|
|
1070
|
+
children: [
|
|
1071
|
+
/* @__PURE__ */ jsx("button", {
|
|
1072
|
+
ref: trigger,
|
|
1073
|
+
type: "button",
|
|
1074
|
+
className: "studio-gallery-back",
|
|
1075
|
+
onClick: () => {
|
|
1076
|
+
setOpen(true);
|
|
1077
|
+
setError("");
|
|
1078
|
+
},
|
|
1079
|
+
children: "Browse Fontsource"
|
|
1080
|
+
}),
|
|
1081
|
+
notice && /* @__PURE__ */ jsx("span", {
|
|
1082
|
+
className: "studio-fontsource-notice",
|
|
1083
|
+
role: "status",
|
|
1084
|
+
children: notice
|
|
1085
|
+
}),
|
|
1086
|
+
/* @__PURE__ */ jsx($542a13ca2fa5b484$export$5b6b19405a83ff9d, {
|
|
1087
|
+
triggerRef: trigger,
|
|
1088
|
+
placement: "bottom start",
|
|
1089
|
+
offset: 6,
|
|
1090
|
+
isOpen: open,
|
|
1091
|
+
onOpenChange: (value_0) => {
|
|
1092
|
+
if (!adding) setOpen(value_0);
|
|
1093
|
+
},
|
|
1094
|
+
isKeyboardDismissDisabled: adding,
|
|
1095
|
+
className: "studio-fontsource-popover",
|
|
1096
|
+
children: /* @__PURE__ */ jsxs($f2ff30fde7b014be$export$3ddf2d174ce01153, {
|
|
1097
|
+
"aria-label": "Fontsource font picker",
|
|
1098
|
+
children: [
|
|
1099
|
+
/* @__PURE__ */ jsxs("header", { children: [/* @__PURE__ */ jsx($2ec61d1d4f780267$export$a8a3e93435678ff9, {
|
|
1100
|
+
slot: "title",
|
|
1101
|
+
children: "Fontsource fonts"
|
|
1102
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1103
|
+
type: "button",
|
|
1104
|
+
"aria-label": "Close font picker",
|
|
1105
|
+
disabled: adding,
|
|
1106
|
+
onClick: () => setOpen(false),
|
|
1107
|
+
children: /* @__PURE__ */ jsx(X, { size: 13 })
|
|
1108
|
+
})] }),
|
|
1109
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1110
|
+
className: "studio-fontsource-search",
|
|
1111
|
+
children: [/* @__PURE__ */ jsx(Search, { size: 13 }), /* @__PURE__ */ jsx("input", {
|
|
1112
|
+
autoFocus: true,
|
|
1113
|
+
"aria-label": "Search Fontsource fonts",
|
|
1114
|
+
placeholder: "Find a font family…",
|
|
1115
|
+
value: query,
|
|
1116
|
+
onChange: (event) => setQuery(event.target.value)
|
|
1117
|
+
})]
|
|
1118
|
+
}),
|
|
1119
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1120
|
+
className: "studio-fontsource-filters",
|
|
1121
|
+
children: [/* @__PURE__ */ jsxs("select", {
|
|
1122
|
+
"aria-label": "Font category",
|
|
1123
|
+
value: category,
|
|
1124
|
+
onChange: (event_0) => setCategory(event_0.target.value),
|
|
1125
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
1126
|
+
value: "",
|
|
1127
|
+
children: "All categories"
|
|
1128
|
+
}), [...new Set(fonts.map((font_0) => font_0.category))].sort().map((name) => /* @__PURE__ */ jsx("option", {
|
|
1129
|
+
value: name,
|
|
1130
|
+
children: name
|
|
1131
|
+
}, name))]
|
|
1132
|
+
}), /* @__PURE__ */ jsxs("label", { children: [
|
|
1133
|
+
/* @__PURE__ */ jsx("input", {
|
|
1134
|
+
type: "checkbox",
|
|
1135
|
+
checked: variableOnly,
|
|
1136
|
+
onChange: (event_1) => setVariableOnly(event_1.target.checked)
|
|
1137
|
+
}),
|
|
1138
|
+
" ",
|
|
1139
|
+
"Variable only"
|
|
1140
|
+
] })]
|
|
1141
|
+
}),
|
|
1142
|
+
loading ? /* @__PURE__ */ jsx("p", {
|
|
1143
|
+
role: "status",
|
|
1144
|
+
children: "Loading font families…"
|
|
1145
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1146
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1147
|
+
className: "studio-fontsource-count",
|
|
1148
|
+
children: [
|
|
1149
|
+
filtered.length,
|
|
1150
|
+
" families",
|
|
1151
|
+
filtered.length > limit ? ` · showing ${limit}` : ""
|
|
1152
|
+
]
|
|
1153
|
+
}),
|
|
1154
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1155
|
+
className: "studio-fontsource-list",
|
|
1156
|
+
role: "group",
|
|
1157
|
+
"aria-label": "Fontsource families",
|
|
1158
|
+
children: [filtered.slice(0, limit).map((font_1) => /* @__PURE__ */ jsxs("button", {
|
|
1159
|
+
type: "button",
|
|
1160
|
+
disabled: adding,
|
|
1161
|
+
"aria-pressed": selected?.id === font_1.id,
|
|
1162
|
+
onClick: () => {
|
|
1163
|
+
setSelected(font_1);
|
|
1164
|
+
setError("");
|
|
1165
|
+
},
|
|
1166
|
+
children: [
|
|
1167
|
+
/* @__PURE__ */ jsx("span", { children: font_1.family }),
|
|
1168
|
+
/* @__PURE__ */ jsxs("small", { children: [font_1.category, font_1.variable ? " · variable" : ""] }),
|
|
1169
|
+
selected?.id === font_1.id && /* @__PURE__ */ jsx(Check, { size: 12 })
|
|
1170
|
+
]
|
|
1171
|
+
}, font_1.id)), !filtered.length && !error && /* @__PURE__ */ jsx("p", { children: "No matching font families." })]
|
|
1172
|
+
}),
|
|
1173
|
+
filtered.length > limit && /* @__PURE__ */ jsx("button", {
|
|
1174
|
+
type: "button",
|
|
1175
|
+
className: "studio-fontsource-more",
|
|
1176
|
+
onClick: () => setLimit((value_1) => value_1 + 30),
|
|
1177
|
+
children: "Show more fonts"
|
|
1178
|
+
})
|
|
1179
|
+
] }),
|
|
1180
|
+
selected && /* @__PURE__ */ jsxs("section", {
|
|
1181
|
+
className: "studio-fontsource-preview",
|
|
1182
|
+
"aria-label": `Preview ${selected.family}`,
|
|
1183
|
+
children: [
|
|
1184
|
+
/* @__PURE__ */ jsx("strong", { children: selected.family }),
|
|
1185
|
+
/* @__PURE__ */ jsx("input", {
|
|
1186
|
+
"aria-label": "Font preview text",
|
|
1187
|
+
value: sample,
|
|
1188
|
+
onChange: (event_2) => setSample(event_2.target.value)
|
|
1189
|
+
}),
|
|
1190
|
+
/* @__PURE__ */ jsx("p", {
|
|
1191
|
+
className: "studio-fontsource-sample",
|
|
1192
|
+
style: {
|
|
1193
|
+
fontFamily: previewState === "ready" ? `"${preview_0.alias}", sans-serif` : "inherit",
|
|
1194
|
+
fontWeight: preview_0?.weight,
|
|
1195
|
+
fontStyle: preview_0?.style
|
|
1196
|
+
},
|
|
1197
|
+
children: sample || "Aa Bb Cc 0123456789"
|
|
1198
|
+
}),
|
|
1199
|
+
/* @__PURE__ */ jsx("span", {
|
|
1200
|
+
role: "status",
|
|
1201
|
+
children: previewState === "loading" ? "Loading preview…" : previewState === "error" ? "Preview could not load. You can still add this family." : ""
|
|
1202
|
+
}),
|
|
1203
|
+
/* @__PURE__ */ jsxs("label", {
|
|
1204
|
+
className: "studio-fontsource-role",
|
|
1205
|
+
children: ["Use for", /* @__PURE__ */ jsxs("select", {
|
|
1206
|
+
"aria-label": "Use font for",
|
|
1207
|
+
value: role,
|
|
1208
|
+
disabled: adding,
|
|
1209
|
+
onChange: (event_3) => setRole(event_3.target.value),
|
|
1210
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
1211
|
+
value: "",
|
|
1212
|
+
children: "Add to available fonts"
|
|
1213
|
+
}), roles.map((item) => /* @__PURE__ */ jsx("option", {
|
|
1214
|
+
value: item.token,
|
|
1215
|
+
children: item.label
|
|
1216
|
+
}, item.token))]
|
|
1217
|
+
})]
|
|
1218
|
+
}),
|
|
1219
|
+
/* @__PURE__ */ jsx("button", {
|
|
1220
|
+
type: "button",
|
|
1221
|
+
className: "studio-primary",
|
|
1222
|
+
disabled: adding || !writer,
|
|
1223
|
+
onClick: async () => {
|
|
1224
|
+
setAdding(true);
|
|
1225
|
+
setError("");
|
|
1226
|
+
try {
|
|
1227
|
+
const font_2 = await addFontsourceFamily(selected.id);
|
|
1228
|
+
if (!mounted.current) return;
|
|
1229
|
+
onAdded(font_2, role || void 0);
|
|
1230
|
+
setNotice(`${font_2.family} added to the app.`);
|
|
1231
|
+
setOpen(false);
|
|
1232
|
+
} catch (cause_0) {
|
|
1233
|
+
setError(cause_0 instanceof Error ? cause_0.message : "Could not add font.");
|
|
1234
|
+
} finally {
|
|
1235
|
+
setAdding(false);
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
children: adding ? "Adding font…" : "Add font to app"
|
|
1239
|
+
})
|
|
1240
|
+
]
|
|
1241
|
+
}),
|
|
1242
|
+
error && /* @__PURE__ */ jsxs("p", {
|
|
1243
|
+
role: "alert",
|
|
1244
|
+
children: [error, !fonts.length && /* @__PURE__ */ jsx("button", {
|
|
1245
|
+
type: "button",
|
|
1246
|
+
onClick: () => setRetry((value_2) => value_2 + 1),
|
|
1247
|
+
children: "Retry"
|
|
1248
|
+
})]
|
|
1249
|
+
}),
|
|
1250
|
+
/* @__PURE__ */ jsx("p", {
|
|
1251
|
+
className: "studio-fontsource-help",
|
|
1252
|
+
children: writer ? `Adding installs the font package and updates ${writer.cssFile}. Your theme version keeps the Heading/Body choice; Apply theme writes it to the repo.` : "Preview fonts here. Adding them to your app requires the Povio UI Vite plugin in development."
|
|
1253
|
+
})
|
|
1254
|
+
]
|
|
1255
|
+
})
|
|
1256
|
+
})
|
|
1257
|
+
]
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
//#endregion
|
|
907
1261
|
//#region src/configurator/inspector/InspectorDetails.tsx
|
|
908
1262
|
/** A token as a design tool names a variable: `--text-large--line-height` → `text-large/line-height`. */
|
|
909
1263
|
const variableName = (token) => token.replace(/^--/, "").replace(/-base$/, "").replaceAll("--", "/");
|
|
@@ -1025,7 +1379,7 @@ function BoxModel(t0) {
|
|
|
1025
1379
|
let t3;
|
|
1026
1380
|
let t4;
|
|
1027
1381
|
if ($[0] !== box.border || $[1] !== box.content.height || $[2] !== box.content.width || $[3] !== box.gap.column || $[4] !== box.gap.row || $[5] !== box.margin || $[6] !== box.padding || $[7] !== box.position || $[8] !== box.radius || $[9] !== onHighlight || $[10] !== onSelect || $[11] !== snapshot.groups) {
|
|
1028
|
-
const properties = snapshot.groups.flatMap(_temp5$
|
|
1382
|
+
const properties = snapshot.groups.flatMap(_temp5$2);
|
|
1029
1383
|
const tokenFor = (label) => properties.find((property) => property.label === label)?.token;
|
|
1030
1384
|
const nonzero = _temp7$1;
|
|
1031
1385
|
let t5;
|
|
@@ -1204,7 +1558,7 @@ function _temp7$1(value_0) {
|
|
|
1204
1558
|
function _temp6$1(part) {
|
|
1205
1559
|
return parseFloat(part) !== 0 && Number.isFinite(parseFloat(part));
|
|
1206
1560
|
}
|
|
1207
|
-
function _temp5$
|
|
1561
|
+
function _temp5$2(group) {
|
|
1208
1562
|
return group.properties;
|
|
1209
1563
|
}
|
|
1210
1564
|
function _temp4$3(value) {
|
|
@@ -3784,7 +4138,7 @@ function CvaEditor(t0) {
|
|
|
3784
4138
|
children: [/* @__PURE__ */ jsx("option", {
|
|
3785
4139
|
value: "",
|
|
3786
4140
|
children: "None"
|
|
3787
|
-
}), Object.keys(options).map(_temp5)]
|
|
4141
|
+
}), Object.keys(options).map(_temp5$1)]
|
|
3788
4142
|
})]
|
|
3789
4143
|
}),
|
|
3790
4144
|
Object.entries(options).map((t20) => {
|
|
@@ -3886,7 +4240,7 @@ function _temp6(t0) {
|
|
|
3886
4240
|
const [key_3] = t0;
|
|
3887
4241
|
return key_3 !== "className";
|
|
3888
4242
|
}
|
|
3889
|
-
function _temp5(value_1) {
|
|
4243
|
+
function _temp5$1(value_1) {
|
|
3890
4244
|
return /* @__PURE__ */ jsx("option", {
|
|
3891
4245
|
value: value_1,
|
|
3892
4246
|
children: value_1
|
|
@@ -3908,37 +4262,6 @@ function _temp2$2(key) {
|
|
|
3908
4262
|
return key.split(".")[0];
|
|
3909
4263
|
}
|
|
3910
4264
|
//#endregion
|
|
3911
|
-
//#region src/configurator/theme-writer-protocol.ts
|
|
3912
|
-
/** Shared by the browser configurator and the isolated Node Vite entry. */
|
|
3913
|
-
const themeWriterMetaName = "povio-ui-theme-writer";
|
|
3914
|
-
const themeWriterTokenHeader = "x-povio-ui-theme-token";
|
|
3915
|
-
//#endregion
|
|
3916
|
-
//#region src/configurator/theme-writer-client.ts
|
|
3917
|
-
function getThemeWriter() {
|
|
3918
|
-
if (typeof document === "undefined") return null;
|
|
3919
|
-
const content = document.querySelector(`meta[name="${themeWriterMetaName}"]`)?.content;
|
|
3920
|
-
if (!content) return null;
|
|
3921
|
-
try {
|
|
3922
|
-
const connection = JSON.parse(content);
|
|
3923
|
-
if (typeof connection.endpoint === "string" && connection.endpoint.startsWith("/") && !connection.endpoint.startsWith("//") && typeof connection.token === "string" && typeof connection.file === "string") return connection;
|
|
3924
|
-
} catch {}
|
|
3925
|
-
return null;
|
|
3926
|
-
}
|
|
3927
|
-
async function saveThemeCss(connection, css) {
|
|
3928
|
-
const response = await fetch(connection.endpoint, {
|
|
3929
|
-
method: "POST",
|
|
3930
|
-
headers: {
|
|
3931
|
-
"Content-Type": "application/json",
|
|
3932
|
-
[themeWriterTokenHeader]: connection.token
|
|
3933
|
-
},
|
|
3934
|
-
body: JSON.stringify({ css })
|
|
3935
|
-
});
|
|
3936
|
-
if (!response.ok) {
|
|
3937
|
-
const result = await response.json().catch(() => null);
|
|
3938
|
-
throw new Error(result?.error ?? `Theme save failed (${response.status}).`);
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
//#endregion
|
|
3942
4265
|
//#region src/configurator/theme-versions.ts
|
|
3943
4266
|
const themeVersionsMetaName = "povio-ui-theme-versions";
|
|
3944
4267
|
function createThemeVersion(name, id, configuration) {
|
|
@@ -4893,13 +5216,13 @@ function useLoadedFonts() {
|
|
|
4893
5216
|
document.fonts.ready.then(() => {
|
|
4894
5217
|
if (!active) return;
|
|
4895
5218
|
const root = getComputedStyle(document.documentElement);
|
|
4896
|
-
const families = [...new Set([...document.fonts].map(_temp2))];
|
|
4897
|
-
setFonts(families.map((
|
|
4898
|
-
const base = `--font-${
|
|
4899
|
-
const value_0 = unquote(root.getPropertyValue(base)) ===
|
|
4900
|
-
const group = uploadedFamilies.has(
|
|
5219
|
+
const families = [...new Set([...document.fonts].map(_temp2))].filter(_temp3);
|
|
5220
|
+
setFonts(families.map((family_0) => {
|
|
5221
|
+
const base = `--font-${family_0.split(/\s+/)[0].toLowerCase()}-base`;
|
|
5222
|
+
const value_0 = unquote(root.getPropertyValue(base)) === family_0 ? `var(${base}), sans-serif` : `"${family_0}", sans-serif`;
|
|
5223
|
+
const group = uploadedFamilies.has(family_0) ? "Uploaded fonts" : "Loaded fonts";
|
|
4901
5224
|
return {
|
|
4902
|
-
name: `loaded:${
|
|
5225
|
+
name: `loaded:${family_0}`,
|
|
4903
5226
|
label: familyName(value_0),
|
|
4904
5227
|
value: value_0,
|
|
4905
5228
|
group
|
|
@@ -4928,6 +5251,9 @@ function useLoadedFonts() {
|
|
|
4928
5251
|
return t4;
|
|
4929
5252
|
}
|
|
4930
5253
|
/** Families added through the upload button in this page session. */
|
|
5254
|
+
function _temp3(family) {
|
|
5255
|
+
return !family.startsWith("Povio Fontsource preview ");
|
|
5256
|
+
}
|
|
4931
5257
|
function _temp2(face) {
|
|
4932
5258
|
return unquote(face.family);
|
|
4933
5259
|
}
|
|
@@ -5106,7 +5432,7 @@ function FontRoleCard(t0) {
|
|
|
5106
5432
|
} else t11 = $[21];
|
|
5107
5433
|
let t12;
|
|
5108
5434
|
if ($[22] !== options) {
|
|
5109
|
-
t12 = new Set(options.map(
|
|
5435
|
+
t12 = new Set(options.map(_temp4));
|
|
5110
5436
|
$[22] = options;
|
|
5111
5437
|
$[23] = t12;
|
|
5112
5438
|
} else t12 = $[23];
|
|
@@ -5120,7 +5446,7 @@ function FontRoleCard(t0) {
|
|
|
5120
5446
|
if ($[26] !== options || $[27] !== t13) {
|
|
5121
5447
|
t14 = t13.map((group_0) => /* @__PURE__ */ jsx("optgroup", {
|
|
5122
5448
|
label: group_0,
|
|
5123
|
-
children: options.filter((option_1) => option_1.group === group_0).map(
|
|
5449
|
+
children: options.filter((option_1) => option_1.group === group_0).map(_temp5)
|
|
5124
5450
|
}, group_0));
|
|
5125
5451
|
$[26] = options;
|
|
5126
5452
|
$[27] = t13;
|
|
@@ -5182,13 +5508,13 @@ function FontRoleCard(t0) {
|
|
|
5182
5508
|
} else t17 = $[45];
|
|
5183
5509
|
return t17;
|
|
5184
5510
|
}
|
|
5185
|
-
function
|
|
5511
|
+
function _temp5(option_2) {
|
|
5186
5512
|
return /* @__PURE__ */ jsx("option", {
|
|
5187
5513
|
value: option_2.name,
|
|
5188
5514
|
children: option_2.label
|
|
5189
5515
|
}, option_2.name);
|
|
5190
5516
|
}
|
|
5191
|
-
function
|
|
5517
|
+
function _temp4(t0) {
|
|
5192
5518
|
const { group } = t0;
|
|
5193
5519
|
return group;
|
|
5194
5520
|
}
|
|
@@ -5740,7 +6066,7 @@ function _temp$1(t0) {
|
|
|
5740
6066
|
}
|
|
5741
6067
|
//#endregion
|
|
5742
6068
|
//#region src/configurator/ComponentPreview.tsx
|
|
5743
|
-
const ComponentGallery = lazy(() => import("./ComponentGallery-
|
|
6069
|
+
const ComponentGallery = lazy(() => import("./ComponentGallery-CzKU899_.js"));
|
|
5744
6070
|
/** Every component, styled by a configuration; the configurator's preview and the App Canvas Components view. */
|
|
5745
6071
|
function ComponentPreview(t0) {
|
|
5746
6072
|
const $ = c(14);
|
|
@@ -6671,6 +6997,7 @@ function ConfigurationModalContent({ memory, windowBounds, poppedOut, floating,
|
|
|
6671
6997
|
const [mode, setMode] = useState(remembered.mode ?? "light");
|
|
6672
6998
|
const [sizeUnit, setSizeUnit] = useState(remembered.sizeUnit ?? "px");
|
|
6673
6999
|
const [loadedFonts, refreshLoadedFonts] = useLoadedFonts();
|
|
7000
|
+
const [fontsourceFamilies, setFontsourceFamilies] = useState([]);
|
|
6674
7001
|
const [editorWidth, setEditorWidth] = useState(remembered.editorWidth);
|
|
6675
7002
|
const [confirmingReset, setConfirmingReset] = useState(false);
|
|
6676
7003
|
const [nothingToReset, setNothingToReset] = useState(false);
|
|
@@ -7037,6 +7364,7 @@ function ConfigurationModalContent({ memory, windowBounds, poppedOut, floating,
|
|
|
7037
7364
|
const options = [
|
|
7038
7365
|
...themeFonts,
|
|
7039
7366
|
...loadedFonts.filter((font) => !themeFonts.some(({ value: value_11 }) => value_11 === font.value)),
|
|
7367
|
+
...fontsourceFamilies.filter((font_0) => !loadedFonts.some((loaded) => loaded.label === font_0.label)),
|
|
7040
7368
|
...systemFonts
|
|
7041
7369
|
];
|
|
7042
7370
|
const roleLabels = {
|
|
@@ -7059,6 +7387,23 @@ function ConfigurationModalContent({ memory, windowBounds, poppedOut, floating,
|
|
|
7059
7387
|
highlighted: highlightedToken === name_24
|
|
7060
7388
|
}, `${revision}-${resetRevision}-${name_24}`))
|
|
7061
7389
|
}),
|
|
7390
|
+
/* @__PURE__ */ jsx(FontsourcePicker, {
|
|
7391
|
+
roles: roles.map(([token_0]) => ({
|
|
7392
|
+
token: token_0,
|
|
7393
|
+
label: roleLabels[token_0] ?? labelFor(token_0).replace(/^font /, "")
|
|
7394
|
+
})),
|
|
7395
|
+
onAdded: (font_1, role) => {
|
|
7396
|
+
const value_13 = `${JSON.stringify(font_1.family)}, sans-serif`;
|
|
7397
|
+
setFontsourceFamilies((current_12) => [...current_12.filter((item) => item.name !== `fontsource:${font_1.packageName}`), {
|
|
7398
|
+
name: `fontsource:${font_1.packageName}`,
|
|
7399
|
+
label: font_1.family,
|
|
7400
|
+
value: value_13,
|
|
7401
|
+
group: "Fontsource fonts"
|
|
7402
|
+
}]);
|
|
7403
|
+
refreshLoadedFonts();
|
|
7404
|
+
if (role) setToken(role, value_13);
|
|
7405
|
+
}
|
|
7406
|
+
}),
|
|
7062
7407
|
/* @__PURE__ */ jsx(FontUpload, { onUpload: refreshLoadedFonts }),
|
|
7063
7408
|
!!families.length && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("hr", { className: "studio-font-divider" }), families.map((entry_2) => renderToken(entry_2))] })
|
|
7064
7409
|
]
|
|
@@ -7242,8 +7587,8 @@ function ConfigurationModalContent({ memory, windowBounds, poppedOut, floating,
|
|
|
7242
7587
|
role: "tab",
|
|
7243
7588
|
"aria-selected": !searching && tab_0 === activeSectionTab,
|
|
7244
7589
|
disabled: searching,
|
|
7245
|
-
onClick: () => setSectionTabs((
|
|
7246
|
-
...
|
|
7590
|
+
onClick: () => setSectionTabs((current_13) => ({
|
|
7591
|
+
...current_13,
|
|
7247
7592
|
[section]: tab_0
|
|
7248
7593
|
})),
|
|
7249
7594
|
children: [tab_0, groupChanged([...groupedEntries].filter(([key_14]) => tabFor(key_14) === tab_0).flatMap(([, group_6]) => group_6.entries)) && /* @__PURE__ */ jsx("span", {
|
|
@@ -7431,10 +7776,10 @@ function ConfigurationModalContent({ memory, windowBounds, poppedOut, floating,
|
|
|
7431
7776
|
children: [/* @__PURE__ */ jsxs("span", { children: ["Use static inputs until interaction", /* @__PURE__ */ jsx("small", { children: "Reduce work when rendering large forms." })] }), /* @__PURE__ */ jsx("input", {
|
|
7432
7777
|
type: "checkbox",
|
|
7433
7778
|
checked: config.renderStaticInput,
|
|
7434
|
-
onChange: (event_5) => setDraft((
|
|
7435
|
-
...
|
|
7779
|
+
onChange: (event_5) => setDraft((current_14) => ({
|
|
7780
|
+
...current_14,
|
|
7436
7781
|
config: {
|
|
7437
|
-
...
|
|
7782
|
+
...current_14.config,
|
|
7438
7783
|
renderStaticInput: event_5.target.checked
|
|
7439
7784
|
}
|
|
7440
7785
|
}))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as AppCanvasPage, r as AppCanvasFrameBridge, t as APP_CANVAS_PATH } from "../AppCanvasPage-
|
|
1
|
+
import { n as AppCanvasPage, r as AppCanvasFrameBridge, t as APP_CANVAS_PATH } from "../AppCanvasPage-vta1j5YQ.js";
|
|
2
2
|
import { c } from "react/compiler-runtime";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { createRoute, notFound, useRouter, useRouterState } from "@tanstack/react-router";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as defaultCanvasBreakpoints, c as layoutCanvasPages, i as AppCanvas, n as AppCanvasPage, o as isAppCanvasFrame, r as AppCanvasFrameBridge, s as discoverCanvasPages, t as APP_CANVAS_PATH } from "../AppCanvasPage-
|
|
1
|
+
import { a as defaultCanvasBreakpoints, c as layoutCanvasPages, i as AppCanvas, n as AppCanvasPage, o as isAppCanvasFrame, r as AppCanvasFrameBridge, s as discoverCanvasPages, t as APP_CANVAS_PATH } from "../AppCanvasPage-vta1j5YQ.js";
|
|
2
2
|
export { APP_CANVAS_PATH, AppCanvas, AppCanvasFrameBridge, AppCanvasPage, defaultCanvasBreakpoints, discoverCanvasPages, isAppCanvasFrame, layoutCanvasPages };
|
|
@@ -12,6 +12,8 @@ interface UIConfiguratorPluginOptions {
|
|
|
12
12
|
* `ui-configuration.json` beside the theme file. Import it with `resolveUIConfiguration`. false disables it.
|
|
13
13
|
*/
|
|
14
14
|
configurationFile?: string | false;
|
|
15
|
+
/** Existing app CSS entrypoint for installed Fontsource imports. false disables font installation. */
|
|
16
|
+
fontCssFile?: string | false;
|
|
15
17
|
}
|
|
16
18
|
/** Adds the configurator's Save theme action to this app's development server. */
|
|
17
19
|
declare function uiConfigurator(options?: UIConfiguratorPluginOptions): Plugin;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { lstat, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, lstat, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
4
7
|
//#region src/configurator/repository-storage-protocol.ts
|
|
5
8
|
const componentConfigurationMetaName = "povio-ui-component-configuration";
|
|
6
9
|
/** Dev-server event sent when a repository storage file changes, so other tabs reload it. */
|
|
@@ -13,7 +16,7 @@ var StorageError = class extends Error {
|
|
|
13
16
|
this.status = status;
|
|
14
17
|
}
|
|
15
18
|
};
|
|
16
|
-
const inside = (root, path) => {
|
|
19
|
+
const inside$1 = (root, path) => {
|
|
17
20
|
const rel = relative(root, path);
|
|
18
21
|
return !!rel && !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
19
22
|
};
|
|
@@ -46,7 +49,7 @@ async function configureRepositoryStorage(server, { filename, route, field, labe
|
|
|
46
49
|
const root = await realpath(server.config.root);
|
|
47
50
|
const filePath = resolve(root, filename);
|
|
48
51
|
const parent = dirname(filePath);
|
|
49
|
-
if (!inside(root, filePath) || !filePath.endsWith(".json")) throw new Error(`Povio UI ${noun} file must be a JSON file inside this app's Vite root.`);
|
|
52
|
+
if (!inside$1(root, filePath) || !filePath.endsWith(".json")) throw new Error(`Povio UI ${noun} file must be a JSON file inside this app's Vite root.`);
|
|
50
53
|
const guard = async () => {
|
|
51
54
|
if (await realpath(parent) !== parent) throw new StorageError(`${label} file parent cannot be a symbolic link.`, 409);
|
|
52
55
|
try {
|
|
@@ -391,6 +394,231 @@ function configureComponentConfigurationStorage(server, filename) {
|
|
|
391
394
|
});
|
|
392
395
|
}
|
|
393
396
|
//#endregion
|
|
397
|
+
//#region src/configurator/theme-writer-protocol.ts
|
|
398
|
+
/** Shared by the browser configurator and the isolated Node Vite entry. */
|
|
399
|
+
const themeWriterMetaName = "povio-ui-theme-writer";
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/configurator/fontsource-vite.ts
|
|
402
|
+
const execute = promisify(execFile);
|
|
403
|
+
const fontId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
404
|
+
const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
405
|
+
var FontError = class extends Error {
|
|
406
|
+
constructor(message, status = 400) {
|
|
407
|
+
super(message);
|
|
408
|
+
this.status = status;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const dependencies = {
|
|
412
|
+
fetch: (...args) => globalThis.fetch(...args),
|
|
413
|
+
install: async (command, args, cwd) => {
|
|
414
|
+
await execute(command, args, {
|
|
415
|
+
cwd,
|
|
416
|
+
shell: false,
|
|
417
|
+
timeout: 18e4,
|
|
418
|
+
maxBuffer: 1048576,
|
|
419
|
+
env: {
|
|
420
|
+
...process.env,
|
|
421
|
+
npm_config_ignore_scripts: "true",
|
|
422
|
+
YARN_ENABLE_SCRIPTS: "false"
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
const exists = async (file) => {
|
|
428
|
+
try {
|
|
429
|
+
await access(file);
|
|
430
|
+
return true;
|
|
431
|
+
} catch {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
const inside = (root, file) => {
|
|
436
|
+
const path = relative(root, file);
|
|
437
|
+
return !!path && !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
|
|
438
|
+
};
|
|
439
|
+
async function manager(root) {
|
|
440
|
+
let directory = root;
|
|
441
|
+
while (true) {
|
|
442
|
+
let declared = "";
|
|
443
|
+
try {
|
|
444
|
+
declared = JSON.parse(await readFile(resolve(directory, "package.json"), "utf8")).packageManager ?? "";
|
|
445
|
+
} catch {}
|
|
446
|
+
if (/^(bun|npm|pnpm|yarn)@/.test(declared)) return {
|
|
447
|
+
name: declared.split("@")[0],
|
|
448
|
+
modernYarn: /^yarn@[2-9]/.test(declared)
|
|
449
|
+
};
|
|
450
|
+
for (const [file, name] of [
|
|
451
|
+
["bun.lock", "bun"],
|
|
452
|
+
["bun.lockb", "bun"],
|
|
453
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
454
|
+
["package-lock.json", "npm"],
|
|
455
|
+
["yarn.lock", "yarn"]
|
|
456
|
+
]) if (await exists(resolve(directory, file))) return {
|
|
457
|
+
name,
|
|
458
|
+
modernYarn: name === "yarn" && (await readFile(resolve(directory, file), "utf8")).includes("__metadata:")
|
|
459
|
+
};
|
|
460
|
+
const parent = dirname(directory);
|
|
461
|
+
if (parent === directory) break;
|
|
462
|
+
directory = parent;
|
|
463
|
+
}
|
|
464
|
+
throw new FontError("No existing package manager was detected. Add a lockfile or packageManager field first.", 409);
|
|
465
|
+
}
|
|
466
|
+
async function metadata(fetcher, url) {
|
|
467
|
+
const response = await fetcher(url, {
|
|
468
|
+
redirect: "error",
|
|
469
|
+
signal: AbortSignal.timeout(2e4)
|
|
470
|
+
});
|
|
471
|
+
if (!response.ok) throw new FontError(response.status === 404 ? "Fontsource font/package was not found." : "Fontsource metadata could not be loaded.", response.status === 404 ? 400 : 502);
|
|
472
|
+
return response.json();
|
|
473
|
+
}
|
|
474
|
+
/** Internal service factory: injection is for server tests, never a plugin/client execution option. */
|
|
475
|
+
async function createFontsourceInstaller(rootInput, cssName, deps = dependencies) {
|
|
476
|
+
const root = await realpath(rootInput), css = resolve(root, cssName), packageFile = resolve(root, "package.json");
|
|
477
|
+
if (!inside(root, css) || !css.endsWith(".css")) throw new FontError("fontCssFile must be a CSS file inside the app root.");
|
|
478
|
+
if (!await exists(css) || !await exists(packageFile)) return void 0;
|
|
479
|
+
const guard = async () => {
|
|
480
|
+
if (await realpath(css) !== css || await realpath(packageFile) !== packageFile) throw new FontError("Font CSS and package.json must not use symbolic links.", 409);
|
|
481
|
+
};
|
|
482
|
+
await guard();
|
|
483
|
+
let pending = Promise.resolve();
|
|
484
|
+
const install = (id) => {
|
|
485
|
+
const task = pending.then(async () => {
|
|
486
|
+
if (typeof id !== "string" || id.length > 100 || !fontId.test(id)) throw new FontError("Expected a valid Fontsource font ID.");
|
|
487
|
+
await guard();
|
|
488
|
+
const font = await metadata(deps.fetch, `https://api.fontsource.org/v1/fonts/${id}`);
|
|
489
|
+
if (font.id !== id || typeof font.family !== "string" || !font.family.trim() || !Array.isArray(font.weights) || !Array.isArray(font.styles)) throw new FontError("Invalid Fontsource font metadata.", 502);
|
|
490
|
+
let variable = false;
|
|
491
|
+
if (font.variable) variable = !!(await metadata(deps.fetch, `https://api.fontsource.org/v1/variable/${id}`)).axes?.wght;
|
|
492
|
+
const packageName = `${variable ? "@fontsource-variable" : "@fontsource"}/${id}`;
|
|
493
|
+
const registry = await metadata(deps.fetch, `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`);
|
|
494
|
+
if (registry.name !== packageName || typeof registry.version !== "string" || !versionPattern.test(registry.version)) throw new FontError("Invalid npm package version metadata.", 502);
|
|
495
|
+
const weights = font.weights.filter((weight) => typeof weight === "number" && Number.isInteger(weight) && weight >= 1 && weight <= 1e3);
|
|
496
|
+
const styles = ["normal", "italic"].filter((style) => font.styles.includes(style));
|
|
497
|
+
if (!weights.length || !styles.length) throw new FontError("This font has no supported styles.");
|
|
498
|
+
const imports = variable ? styles.map((style) => `${packageName}/wght${style === "italic" ? "-italic" : ""}.css`) : weights.flatMap((weight) => styles.map((style) => `${packageName}/${weight}${style === "italic" ? "-italic" : ""}.css`));
|
|
499
|
+
const require = createRequire(packageFile);
|
|
500
|
+
const installed = async (exact = false) => {
|
|
501
|
+
try {
|
|
502
|
+
const declared = JSON.parse(await readFile(packageFile, "utf8"));
|
|
503
|
+
if (!declared.dependencies?.[packageName] && !declared.devDependencies?.[packageName]) return false;
|
|
504
|
+
const pkg = JSON.parse(await readFile(require.resolve(`${packageName}/package.json`), "utf8"));
|
|
505
|
+
if (exact && pkg.version !== registry.version) return false;
|
|
506
|
+
if (!versionPattern.test(pkg.version) || pkg.version.split(".")[0] !== registry.version.split(".")[0]) return false;
|
|
507
|
+
await Promise.all(imports.map((item) => access(require.resolve(item))));
|
|
508
|
+
return true;
|
|
509
|
+
} catch {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
const alreadyInstalled = await installed();
|
|
514
|
+
if (!alreadyInstalled) {
|
|
515
|
+
const pm = await manager(root);
|
|
516
|
+
const spec = `${packageName}@${registry.version}`;
|
|
517
|
+
const args = pm.name === "npm" ? [
|
|
518
|
+
"install",
|
|
519
|
+
"--save-exact",
|
|
520
|
+
"--ignore-scripts",
|
|
521
|
+
spec
|
|
522
|
+
] : pm.name === "yarn" ? [
|
|
523
|
+
"add",
|
|
524
|
+
"--exact",
|
|
525
|
+
...pm.modernYarn ? [] : ["--ignore-scripts"],
|
|
526
|
+
spec
|
|
527
|
+
] : [
|
|
528
|
+
"add",
|
|
529
|
+
"--exact",
|
|
530
|
+
"--ignore-scripts",
|
|
531
|
+
spec
|
|
532
|
+
];
|
|
533
|
+
if (pm.name === "pnpm") args[1] = "--save-exact";
|
|
534
|
+
try {
|
|
535
|
+
await deps.install(pm.name, args, root);
|
|
536
|
+
} catch {
|
|
537
|
+
throw new FontError("Font installation failed. Existing CSS imports were left unchanged.", 502);
|
|
538
|
+
}
|
|
539
|
+
if (!await installed(true)) throw new FontError("Installed font files could not be verified. CSS imports were left unchanged.", 502);
|
|
540
|
+
}
|
|
541
|
+
await guard();
|
|
542
|
+
const source = await readFile(css, "utf8");
|
|
543
|
+
const existing = new Set([...source.matchAll(/@import\s+(?:url\(\s*)?["']([^"']+)["']/g)].map((match) => match[1]));
|
|
544
|
+
const missing = imports.filter((item) => !existing.has(item));
|
|
545
|
+
if (missing.length) {
|
|
546
|
+
const additions = missing.map((item) => `@import "${item}";`).join("\n");
|
|
547
|
+
const charset = source.match(/^(?:\uFEFF)?@charset\s+["'][^"']+["'];\s*/i)?.[0] ?? "";
|
|
548
|
+
const next = `${charset}${additions}\n${source.slice(charset.length)}`;
|
|
549
|
+
const temporary = `${css}.fontsource-${randomUUID()}.tmp`;
|
|
550
|
+
try {
|
|
551
|
+
await writeFile(temporary, next, { flag: "wx" });
|
|
552
|
+
await guard();
|
|
553
|
+
await rename(temporary, css);
|
|
554
|
+
} finally {
|
|
555
|
+
await unlink(temporary).catch(() => {});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
family: `${font.family}${variable ? " Variable" : ""}`,
|
|
560
|
+
packageName,
|
|
561
|
+
imports,
|
|
562
|
+
alreadyInstalled
|
|
563
|
+
};
|
|
564
|
+
});
|
|
565
|
+
pending = task.then(() => {}, () => {});
|
|
566
|
+
return task;
|
|
567
|
+
};
|
|
568
|
+
return {
|
|
569
|
+
cssFile: relative(root, css).split(sep).join("/"),
|
|
570
|
+
install
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
async function configureFontsource(server, cssName, token, base) {
|
|
574
|
+
const installer = await createFontsourceInstaller(server.config.root, cssName);
|
|
575
|
+
if (!installer) return void 0;
|
|
576
|
+
const endpoint = `${base}__povio_ui/fontsource`;
|
|
577
|
+
server.middlewares.use((req, res, next) => {
|
|
578
|
+
if (req.url?.split("?")[0] !== endpoint) return next();
|
|
579
|
+
handle(req, res).catch((error) => respond(res, error instanceof FontError ? error.status : 500, { error: error instanceof Error ? error.message : "Could not install font." }));
|
|
580
|
+
});
|
|
581
|
+
function respond(res, status, body) {
|
|
582
|
+
res.writeHead(status, {
|
|
583
|
+
"Content-Type": "application/json",
|
|
584
|
+
"Cache-Control": "no-store"
|
|
585
|
+
});
|
|
586
|
+
res.end(JSON.stringify(body));
|
|
587
|
+
}
|
|
588
|
+
async function handle(req, res) {
|
|
589
|
+
if (req.method !== "POST") throw new FontError("Use POST to install a font.", 405);
|
|
590
|
+
if (req.headers["x-povio-ui-theme-token"] !== token) throw new FontError("Invalid theme token.", 403);
|
|
591
|
+
if (req.headers["sec-fetch-site"] === "cross-site") throw new FontError("Font installation requires the app origin.", 403);
|
|
592
|
+
if (req.headers.origin) {
|
|
593
|
+
let sameOrigin = false;
|
|
594
|
+
try {
|
|
595
|
+
sameOrigin = new URL(req.headers.origin).origin === `${server.config.server.https ? "https" : "http"}://${req.headers.host}`;
|
|
596
|
+
} catch {}
|
|
597
|
+
if (!sameOrigin) throw new FontError("Font installation requires the app origin.", 403);
|
|
598
|
+
}
|
|
599
|
+
if (!req.headers["content-type"]?.startsWith("application/json")) throw new FontError("Expected application/json.", 415);
|
|
600
|
+
let size = 0;
|
|
601
|
+
const chunks = [];
|
|
602
|
+
for await (const chunk of req) {
|
|
603
|
+
size += Buffer.byteLength(chunk);
|
|
604
|
+
if (size > 1024) throw new FontError("Font request is too large.", 413);
|
|
605
|
+
chunks.push(Buffer.from(chunk));
|
|
606
|
+
}
|
|
607
|
+
let data;
|
|
608
|
+
try {
|
|
609
|
+
data = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
610
|
+
} catch {
|
|
611
|
+
throw new FontError("Expected JSON containing a font ID.");
|
|
612
|
+
}
|
|
613
|
+
const id = data && typeof data === "object" && !Array.isArray(data) ? data.id : void 0;
|
|
614
|
+
respond(res, 200, await installer.install(id));
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
endpoint,
|
|
618
|
+
cssFile: installer.cssFile
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
//#endregion
|
|
394
622
|
//#region src/configurator/theme-model.ts
|
|
395
623
|
const declarations = (source) => Object.fromEntries([...source.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)].map(([, name, value]) => [name, value.trim()]));
|
|
396
624
|
const selectorDeclarations = (source, selector) => Object.assign({}, ...[...source.matchAll(selector)].map((match) => declarations(match[1])));
|
|
@@ -460,10 +688,6 @@ function updateThemeSource(source, exportedCss) {
|
|
|
460
688
|
return source;
|
|
461
689
|
}
|
|
462
690
|
//#endregion
|
|
463
|
-
//#region src/configurator/theme-writer-protocol.ts
|
|
464
|
-
/** Shared by the browser configurator and the isolated Node Vite entry. */
|
|
465
|
-
const themeWriterMetaName = "povio-ui-theme-writer";
|
|
466
|
-
//#endregion
|
|
467
691
|
//#region src/configurator/vite.ts
|
|
468
692
|
var ThemeWriteError = class extends Error {
|
|
469
693
|
constructor(message, status) {
|
|
@@ -512,13 +736,15 @@ function uiConfigurator(options = {}) {
|
|
|
512
736
|
if (!file || isAbsolute(file) || file === ".." || file.startsWith(`..${sep}`)) throw new Error("Povio UI themeFile must be inside this app's Vite root.");
|
|
513
737
|
if (!themeFile.endsWith(".css")) throw new Error("Povio UI themeFile must be a CSS file.");
|
|
514
738
|
if (options.configurationFile !== false) componentStorage = await configureComponentConfigurationStorage(server, options.configurationFile ?? join(dirname(file), "ui-configuration.json"));
|
|
515
|
-
const
|
|
739
|
+
const base = new URL(server.config.base, "http://localhost").pathname.replace(/\/?$/, "/");
|
|
740
|
+
const endpoint = `${base}__povio_ui/theme`;
|
|
516
741
|
connection = {
|
|
517
742
|
endpoint,
|
|
518
743
|
token: randomUUID(),
|
|
519
744
|
file: file.split(sep).join("/")
|
|
520
745
|
};
|
|
521
746
|
const token = connection.token;
|
|
747
|
+
if (options.fontCssFile !== false) connection.fontsource = await configureFontsource(server, options.fontCssFile ?? "src/styles/globals.css", token, base);
|
|
522
748
|
const save = async (request, response) => {
|
|
523
749
|
if (request.method !== "POST") {
|
|
524
750
|
response.setHeader("Allow", "POST");
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { d as useUIConfiguration, i as resolveUIConfiguration, n as ColorModeSwitch, t as UIConfigurationModal, u as UIConfigurationProvider } from "../UIConfigurator-
|
|
1
|
+
import { d as useUIConfiguration, i as resolveUIConfiguration, n as ColorModeSwitch, t as UIConfigurationModal, u as UIConfigurationProvider } from "../UIConfigurator-CjAW1SJY.js";
|
|
2
2
|
import { i as exportCss, v as parseThemeCss } from "../theme-model-DtTsIxou.js";
|
|
3
3
|
export { ColorModeSwitch, UIConfigurationModal, UIConfigurationProvider, exportCss, parseThemeCss, resolveUIConfiguration, useUIConfiguration };
|
|
@@ -3692,3 +3692,205 @@
|
|
|
3692
3692
|
outline: 2px solid #555;
|
|
3693
3693
|
outline-offset: 1px;
|
|
3694
3694
|
}
|
|
3695
|
+
/* Fontsource browsing stays compact even when the preview theme changes. */
|
|
3696
|
+
.studio-fontsource {
|
|
3697
|
+
display: flex;
|
|
3698
|
+
align-items: center;
|
|
3699
|
+
gap: 8px;
|
|
3700
|
+
padding-top: 8px;
|
|
3701
|
+
}
|
|
3702
|
+
.studio-fontsource-notice {
|
|
3703
|
+
color: #777;
|
|
3704
|
+
font-size: 10px;
|
|
3705
|
+
}
|
|
3706
|
+
.studio-fontsource-popover {
|
|
3707
|
+
width: min(340px, calc(100vw - 24px));
|
|
3708
|
+
max-height: min(680px, calc(100dvh - 24px));
|
|
3709
|
+
overflow: auto;
|
|
3710
|
+
padding: 12px;
|
|
3711
|
+
border: 1px solid #e5e5e5;
|
|
3712
|
+
border-radius: 10px;
|
|
3713
|
+
background: #fff;
|
|
3714
|
+
color: #303036;
|
|
3715
|
+
box-shadow: 0 8px 32px #24202d26;
|
|
3716
|
+
font:
|
|
3717
|
+
11px/1.4 Inter,
|
|
3718
|
+
system-ui,
|
|
3719
|
+
sans-serif;
|
|
3720
|
+
z-index: 10020;
|
|
3721
|
+
}
|
|
3722
|
+
.studio-fontsource-popover * {
|
|
3723
|
+
box-sizing: border-box;
|
|
3724
|
+
}
|
|
3725
|
+
.studio-fontsource-popover [role="dialog"] {
|
|
3726
|
+
outline: none;
|
|
3727
|
+
}
|
|
3728
|
+
.studio-fontsource-popover header {
|
|
3729
|
+
display: flex;
|
|
3730
|
+
align-items: center;
|
|
3731
|
+
justify-content: space-between;
|
|
3732
|
+
gap: 8px;
|
|
3733
|
+
margin-bottom: 10px;
|
|
3734
|
+
}
|
|
3735
|
+
.studio-fontsource-popover h2 {
|
|
3736
|
+
margin: 0;
|
|
3737
|
+
font-size: 12px;
|
|
3738
|
+
font-weight: 600;
|
|
3739
|
+
}
|
|
3740
|
+
.studio-fontsource-popover button,
|
|
3741
|
+
.studio-fontsource-popover input,
|
|
3742
|
+
.studio-fontsource-popover select {
|
|
3743
|
+
font: inherit;
|
|
3744
|
+
color: inherit;
|
|
3745
|
+
}
|
|
3746
|
+
.studio-fontsource-popover button {
|
|
3747
|
+
border: 1px solid #e5e5e5;
|
|
3748
|
+
border-radius: 5px;
|
|
3749
|
+
padding: 4px 7px;
|
|
3750
|
+
background: #fff;
|
|
3751
|
+
cursor: pointer;
|
|
3752
|
+
}
|
|
3753
|
+
.studio-fontsource-popover button:disabled {
|
|
3754
|
+
opacity: 0.5;
|
|
3755
|
+
cursor: default;
|
|
3756
|
+
}
|
|
3757
|
+
.studio-fontsource-popover header button {
|
|
3758
|
+
display: grid;
|
|
3759
|
+
place-items: center;
|
|
3760
|
+
padding: 3px;
|
|
3761
|
+
border: 0;
|
|
3762
|
+
}
|
|
3763
|
+
.studio-fontsource-popover input:not([type="checkbox"]),
|
|
3764
|
+
.studio-fontsource-popover select {
|
|
3765
|
+
min-width: 0;
|
|
3766
|
+
width: 100%;
|
|
3767
|
+
border: 1px solid #e5e5e5;
|
|
3768
|
+
border-radius: 5px;
|
|
3769
|
+
padding: 6px 7px;
|
|
3770
|
+
background: #fafafa;
|
|
3771
|
+
}
|
|
3772
|
+
.studio-fontsource-popover :focus-visible {
|
|
3773
|
+
outline: 2px solid #7655d7;
|
|
3774
|
+
outline-offset: 1px;
|
|
3775
|
+
}
|
|
3776
|
+
.studio-fontsource-search {
|
|
3777
|
+
position: relative;
|
|
3778
|
+
display: flex;
|
|
3779
|
+
align-items: center;
|
|
3780
|
+
}
|
|
3781
|
+
.studio-fontsource-search svg {
|
|
3782
|
+
position: absolute;
|
|
3783
|
+
left: 8px;
|
|
3784
|
+
color: #888;
|
|
3785
|
+
}
|
|
3786
|
+
.studio-fontsource-popover .studio-fontsource-search input {
|
|
3787
|
+
padding-left: 27px;
|
|
3788
|
+
}
|
|
3789
|
+
.studio-fontsource-filters {
|
|
3790
|
+
display: flex;
|
|
3791
|
+
align-items: center;
|
|
3792
|
+
gap: 8px;
|
|
3793
|
+
margin: 8px 0;
|
|
3794
|
+
}
|
|
3795
|
+
.studio-fontsource-filters select {
|
|
3796
|
+
flex: 1;
|
|
3797
|
+
}
|
|
3798
|
+
.studio-fontsource-filters label {
|
|
3799
|
+
display: flex;
|
|
3800
|
+
align-items: center;
|
|
3801
|
+
gap: 4px;
|
|
3802
|
+
white-space: nowrap;
|
|
3803
|
+
}
|
|
3804
|
+
.studio-fontsource-filters input {
|
|
3805
|
+
margin: 0;
|
|
3806
|
+
accent-color: #7655d7;
|
|
3807
|
+
}
|
|
3808
|
+
.studio-fontsource-count {
|
|
3809
|
+
display: block;
|
|
3810
|
+
margin: 8px 0 3px;
|
|
3811
|
+
font-size: 10px;
|
|
3812
|
+
color: #888;
|
|
3813
|
+
}
|
|
3814
|
+
.studio-fontsource-list {
|
|
3815
|
+
display: grid;
|
|
3816
|
+
gap: 2px;
|
|
3817
|
+
max-height: 180px;
|
|
3818
|
+
overflow: auto;
|
|
3819
|
+
padding: 3px;
|
|
3820
|
+
margin-inline: -3px;
|
|
3821
|
+
}
|
|
3822
|
+
.studio-fontsource-list button {
|
|
3823
|
+
display: grid;
|
|
3824
|
+
grid-template-columns: 1fr auto;
|
|
3825
|
+
align-items: center;
|
|
3826
|
+
text-align: left;
|
|
3827
|
+
border-color: transparent;
|
|
3828
|
+
padding: 5px 7px;
|
|
3829
|
+
}
|
|
3830
|
+
.studio-fontsource-list small {
|
|
3831
|
+
grid-column: 1;
|
|
3832
|
+
color: #888;
|
|
3833
|
+
font-size: 9px;
|
|
3834
|
+
}
|
|
3835
|
+
.studio-fontsource-list svg {
|
|
3836
|
+
grid-row: 1 / span 2;
|
|
3837
|
+
grid-column: 2;
|
|
3838
|
+
}
|
|
3839
|
+
.studio-fontsource-list button:hover {
|
|
3840
|
+
background: #f6f4fa;
|
|
3841
|
+
}
|
|
3842
|
+
.studio-fontsource-list button[aria-pressed="true"] {
|
|
3843
|
+
color: #6540c2;
|
|
3844
|
+
background: #eee8ff;
|
|
3845
|
+
}
|
|
3846
|
+
.studio-fontsource-preview {
|
|
3847
|
+
border-top: 1px solid #eee;
|
|
3848
|
+
margin-top: 10px;
|
|
3849
|
+
padding-top: 10px;
|
|
3850
|
+
}
|
|
3851
|
+
.studio-fontsource-preview > strong {
|
|
3852
|
+
display: block;
|
|
3853
|
+
margin-bottom: 6px;
|
|
3854
|
+
}
|
|
3855
|
+
.studio-fontsource-sample {
|
|
3856
|
+
margin: 10px 0;
|
|
3857
|
+
font-size: 26px;
|
|
3858
|
+
line-height: 1.4;
|
|
3859
|
+
overflow-wrap: anywhere;
|
|
3860
|
+
}
|
|
3861
|
+
.studio-fontsource-preview > [role="status"] {
|
|
3862
|
+
color: #888;
|
|
3863
|
+
font-size: 10px;
|
|
3864
|
+
}
|
|
3865
|
+
.studio-fontsource-role {
|
|
3866
|
+
display: flex;
|
|
3867
|
+
align-items: center;
|
|
3868
|
+
gap: 8px;
|
|
3869
|
+
margin: 8px 0;
|
|
3870
|
+
}
|
|
3871
|
+
.studio-fontsource-role select {
|
|
3872
|
+
flex: 1;
|
|
3873
|
+
}
|
|
3874
|
+
.studio-fontsource-popover .studio-primary {
|
|
3875
|
+
width: 100%;
|
|
3876
|
+
background: #7655d7;
|
|
3877
|
+
border-color: #7655d7;
|
|
3878
|
+
color: white;
|
|
3879
|
+
}
|
|
3880
|
+
.studio-fontsource-help {
|
|
3881
|
+
margin: 10px 0 0;
|
|
3882
|
+
color: #888;
|
|
3883
|
+
font-size: 10px;
|
|
3884
|
+
line-height: 1.5;
|
|
3885
|
+
}
|
|
3886
|
+
.studio-fontsource-popover [role="alert"] {
|
|
3887
|
+
color: #ad2937;
|
|
3888
|
+
}
|
|
3889
|
+
.studio-fontsource-popover [role="alert"] button {
|
|
3890
|
+
margin-left: 8px;
|
|
3891
|
+
}
|
|
3892
|
+
.studio-fontsource-popover .studio-fontsource-more {
|
|
3893
|
+
width: 100%;
|
|
3894
|
+
margin-top: 5px;
|
|
3895
|
+
font-size: 10px;
|
|
3896
|
+
}
|