@fasl-work/caos-app-shell 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/caos-shell-gate.mjs +9 -3
- package/dist/chart.d.ts +41 -2
- package/dist/chart.js +206 -20
- package/dist/index.d.ts +137 -48
- package/dist/index.js +220 -97
- package/gate/inpage.mjs +77 -3
- package/gate/run.mjs +129 -13
- package/gate/serve.mjs +13 -3
- package/package.json +3 -4
- package/reserved-classes.json +16 -2
- package/styles.css +46 -4
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useThemeStore, useShellLang, useLangStore, SHELL_TOKENS, pick, useStage
|
|
|
2
2
|
export { NBSP, NOT_AVAILABLE, SCIENTIFIC_BELOW, SHELL_TOKENS, Stage, applyTheme, formatNumber, formatTicks, pick, readTheme, resolveToken, useFormat, useLangStore, useShellLang, useStageSize, useThemeStore, useThemeTokens } from './chunk-YECZXGVG.js';
|
|
3
3
|
import { APP_ROUTE, LANDING_ROUTE } from './chunk-33WTUQ7Q.js';
|
|
4
4
|
export { APP_ROUTE, LANDING_ROUTE, LANG_STORAGE_KEY, STANDARD_ROUTES, THEME_BOOT_SCRIPT, THEME_STORAGE_KEY } from './chunk-33WTUQ7Q.js';
|
|
5
|
-
import { createContext, Component, useState, useRef, useId, useEffect, useLayoutEffect, useCallback, useContext, useMemo, Children } from 'react';
|
|
5
|
+
import { createContext, Component, useState, useRef, useId, useEffect, useLayoutEffect, useCallback, useContext, useMemo, Children, isValidElement } from 'react';
|
|
6
6
|
import { useInRouterContext, NavLink, useLocation, Navigate, Link } from 'react-router';
|
|
7
7
|
import { Sun, Moon, Languages, Boxes, CodeXml, Globe, Briefcase, Info, ChevronLeft, ChevronRight, Maximize2, RotateCw } from 'lucide-react';
|
|
8
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -540,7 +540,7 @@ function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, s
|
|
|
540
540
|
ro?.disconnect();
|
|
541
541
|
};
|
|
542
542
|
}, []);
|
|
543
|
-
return /* @__PURE__ */ jsxs(Root, { className: "caos-focus-shell", "data-focus-shell": "", children: [
|
|
543
|
+
return /* @__PURE__ */ jsxs(Root, { className: rail === null || rail === void 0 ? "caos-focus-shell caos-focus-norail" : "caos-focus-shell", "data-focus-shell": "", children: [
|
|
544
544
|
/* @__PURE__ */ jsxs("section", { ref: stageRef, className: "caos-focus-stage", "aria-label": stageLabel, children: [
|
|
545
545
|
/* @__PURE__ */ jsx("div", { className: "caos-focus-instrument", children: stage }),
|
|
546
546
|
/* @__PURE__ */ jsxs("div", { className: "caos-focus-label", children: [
|
|
@@ -557,7 +557,7 @@ function FocusShell({ stage, rail, title, description, hud, onExit, exitLabel, s
|
|
|
557
557
|
/* @__PURE__ */ jsx("button", { type: "button", "data-focus-exit": "", onClick: onExit, children: exitLabel })
|
|
558
558
|
] })
|
|
559
559
|
] }),
|
|
560
|
-
/* @__PURE__ */ jsx("aside", { className: "caos-focus-rail", "data-rail": "", children: rail })
|
|
560
|
+
rail !== null && rail !== void 0 && /* @__PURE__ */ jsx("aside", { className: "caos-focus-rail", "data-rail": "", children: rail })
|
|
561
561
|
] });
|
|
562
562
|
}
|
|
563
563
|
|
|
@@ -795,17 +795,64 @@ function CaseSelector(props) {
|
|
|
795
795
|
const visible = casesInSource(cases, source);
|
|
796
796
|
const groups = groupByCategory(visible);
|
|
797
797
|
const modified = cases.find((c) => c.id === modifiedFromId) ?? null;
|
|
798
|
+
const wrapRef = useRef(null);
|
|
799
|
+
const [minHeight, setMinHeight] = useState(null);
|
|
800
|
+
const nameKey = visible.map((c) => c.name).join("");
|
|
801
|
+
useLayoutEffect(() => {
|
|
802
|
+
const wrap = wrapRef.current;
|
|
803
|
+
const display = wrap?.querySelector(".cs-select-display");
|
|
804
|
+
if (!wrap || !display) return void 0;
|
|
805
|
+
const names = nameKey ? nameKey.split("") : [];
|
|
806
|
+
const measure = () => {
|
|
807
|
+
const st = getComputedStyle(display);
|
|
808
|
+
const room = display.clientWidth - parseFloat(st.paddingLeft) - parseFloat(st.paddingRight);
|
|
809
|
+
if (!(room > 0)) return;
|
|
810
|
+
const copy = document.createElement("span");
|
|
811
|
+
copy.className = "cs-select-text";
|
|
812
|
+
copy.setAttribute("aria-hidden", "true");
|
|
813
|
+
copy.setAttribute("style", `position:absolute;visibility:hidden;left:0;top:0;width:${room}px;display:block;overflow:visible;max-height:none;-webkit-line-clamp:unset;line-clamp:unset`);
|
|
814
|
+
display.append(copy);
|
|
815
|
+
let tallest = 0;
|
|
816
|
+
for (const n of names) {
|
|
817
|
+
copy.textContent = n;
|
|
818
|
+
tallest = Math.max(tallest, copy.getBoundingClientRect().height);
|
|
819
|
+
}
|
|
820
|
+
const line = parseFloat(getComputedStyle(copy).lineHeight) || parseFloat(st.fontSize) * 1.25;
|
|
821
|
+
copy.remove();
|
|
822
|
+
const frame = parseFloat(st.paddingTop) + parseFloat(st.paddingBottom) + parseFloat(st.borderTopWidth) + parseFloat(st.borderBottomWidth);
|
|
823
|
+
const next = tallest > line * 1.5 ? Math.ceil((tallest + frame) * 100) / 100 : null;
|
|
824
|
+
setMinHeight((prev) => prev === next ? prev : next);
|
|
825
|
+
};
|
|
826
|
+
measure();
|
|
827
|
+
let width = wrap.getBoundingClientRect().width;
|
|
828
|
+
const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
|
|
829
|
+
const w = wrap.getBoundingClientRect().width;
|
|
830
|
+
if (w !== width) {
|
|
831
|
+
width = w;
|
|
832
|
+
measure();
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
ro?.observe(wrap);
|
|
836
|
+
void document.fonts?.ready.then(measure);
|
|
837
|
+
return () => ro?.disconnect();
|
|
838
|
+
}, [layout, nameKey]);
|
|
839
|
+
const options = groups.flatMap((g) => g.cases);
|
|
840
|
+
const selectedName = (options.find((c) => c.id === selectedId) ?? options.find((c) => !c.disabled))?.name ?? "";
|
|
798
841
|
const select = /* @__PURE__ */ jsx(
|
|
799
842
|
"select",
|
|
800
843
|
{
|
|
801
844
|
"data-control": "case",
|
|
802
|
-
className: "select cs-select",
|
|
845
|
+
className: "select cs-select cs-select-native",
|
|
803
846
|
"aria-label": ariaLabel ?? "Case selector",
|
|
804
847
|
value: selectedId,
|
|
805
848
|
onChange: (e) => onSelect(e.target.value),
|
|
806
849
|
children: groups.map((g) => /* @__PURE__ */ jsx("optgroup", { label: g.category || " ", children: g.cases.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, disabled: c.disabled, title: caseTooltip(c) || void 0, children: c.name }, c.id)) }, g.category || "_"))
|
|
807
850
|
}
|
|
808
851
|
);
|
|
852
|
+
const selectBox = /* @__PURE__ */ jsxs("div", { className: "cs-select-wrap", ref: wrapRef, children: [
|
|
853
|
+
/* @__PURE__ */ jsx("div", { className: "cs-select-display", style: minHeight === null ? void 0 : { minHeight }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "cs-select-text", children: selectedName }) }),
|
|
854
|
+
select
|
|
855
|
+
] });
|
|
809
856
|
return /* @__PURE__ */ jsxs("div", { className: `cs${className ? ` ${className}` : ""}`, role: "group", "aria-label": ariaLabel ?? "Case selector", children: [
|
|
810
857
|
source && lanes.length > 1 && /* @__PURE__ */ jsxs("div", { className: "cs-source", children: [
|
|
811
858
|
/* @__PURE__ */ jsx("span", { className: "cs-source-label", children: t2.source }),
|
|
@@ -824,9 +871,9 @@ function CaseSelector(props) {
|
|
|
824
871
|
] }),
|
|
825
872
|
source && source !== "synthetic" && /* @__PURE__ */ jsx("p", { className: "cs-locked banner", children: lockedNote ?? t2.lockedNote }),
|
|
826
873
|
layout === "select" && (props.accessory ? /* @__PURE__ */ jsxs("div", { className: "cs-select-row", children: [
|
|
827
|
-
|
|
874
|
+
selectBox,
|
|
828
875
|
props.accessory
|
|
829
|
-
] }) :
|
|
876
|
+
] }) : selectBox),
|
|
830
877
|
layout === "chips" && groups.map((g) => /* @__PURE__ */ jsxs("div", { className: "cs-group", role: "group", "aria-label": g.category || void 0, "data-control": "case", children: [
|
|
831
878
|
g.category && /* @__PURE__ */ jsx("span", { className: "cs-group-label", children: g.category }),
|
|
832
879
|
/* @__PURE__ */ jsx("div", { className: "cs-chips", children: g.cases.map((c) => {
|
|
@@ -867,7 +914,8 @@ function caseKindOf(c) {
|
|
|
867
914
|
return c.kind ?? "synthetic";
|
|
868
915
|
}
|
|
869
916
|
var ViewMemoryContext = createContext(null);
|
|
870
|
-
var
|
|
917
|
+
var PanelPathContext = createContext("");
|
|
918
|
+
var memoryKey = (kind, path, ids) => `${kind}|${path}|${ids.join(",")}`;
|
|
871
919
|
var CitationsContext = createContext({ list: [], byId: {} });
|
|
872
920
|
function CitationsProvider({ items, children }) {
|
|
873
921
|
const byId = {};
|
|
@@ -964,9 +1012,9 @@ function DocReferences({ ids }) {
|
|
|
964
1012
|
const c = byId[id];
|
|
965
1013
|
const h = href(c);
|
|
966
1014
|
return /* @__PURE__ */ jsxs("li", { children: [
|
|
967
|
-
/* @__PURE__ */ jsx("span", { children: c.citation }),
|
|
1015
|
+
/* @__PURE__ */ jsx("span", { translate: "no", children: c.citation }),
|
|
968
1016
|
" ",
|
|
969
|
-
h ? /* @__PURE__ */ jsx("a", { href: h, target: "_blank", rel: "noreferrer noopener", className: "faint", children: c.doi ? `doi:${c.doi}` : "link" }) : null
|
|
1017
|
+
h ? /* @__PURE__ */ jsx("a", { href: h, target: "_blank", rel: "noreferrer noopener", className: "faint", children: c.doi ? /* @__PURE__ */ jsx("span", { translate: "no", children: `doi:${c.doi}` }) : "link" }) : null
|
|
970
1018
|
] }, id);
|
|
971
1019
|
}) })
|
|
972
1020
|
] });
|
|
@@ -990,19 +1038,20 @@ function ReferenceList({ ids, heading }) {
|
|
|
990
1038
|
/* @__PURE__ */ jsx("ol", { className: "reference-list", children: items.map((c) => {
|
|
991
1039
|
const h = href(c);
|
|
992
1040
|
return /* @__PURE__ */ jsxs("li", { children: [
|
|
993
|
-
/* @__PURE__ */ jsx("span", { children: c.citation }),
|
|
1041
|
+
/* @__PURE__ */ jsx("span", { translate: "no", children: c.citation }),
|
|
994
1042
|
" ",
|
|
995
|
-
h ? /* @__PURE__ */ jsx("a", { href: h, target: "_blank", rel: "noreferrer noopener", className: "faint", children: c.doi ? `doi:${c.doi}` : "link" }) : null
|
|
1043
|
+
h ? /* @__PURE__ */ jsx("a", { href: h, target: "_blank", rel: "noreferrer noopener", className: "faint", children: c.doi ? /* @__PURE__ */ jsx("span", { translate: "no", children: `doi:${c.doi}` }) : "link" }) : null
|
|
996
1044
|
] }, c.id);
|
|
997
1045
|
}) })
|
|
998
1046
|
] });
|
|
999
1047
|
}
|
|
1000
1048
|
var MAX_PEER_TABS = 6;
|
|
1001
|
-
function Tabs({ tabs, initial, value, onChange, ariaLabel }) {
|
|
1049
|
+
function Tabs({ tabs, initial, value, onChange, ariaLabel, trailing }) {
|
|
1002
1050
|
const baseId = useId();
|
|
1003
1051
|
const first = tabs[0]?.id ?? "";
|
|
1004
1052
|
const memory = useContext(ViewMemoryContext);
|
|
1005
|
-
const
|
|
1053
|
+
const path = useContext(PanelPathContext);
|
|
1054
|
+
const mkey = memoryKey("tabs", path, tabs.map((t2) => t2.id));
|
|
1006
1055
|
const [own, setOwn] = useState(() => {
|
|
1007
1056
|
const kept = memory?.get(mkey);
|
|
1008
1057
|
return kept && tabs.some((t2) => t2.id === kept) ? kept : initial ?? first;
|
|
@@ -1033,7 +1082,7 @@ function Tabs({ tabs, initial, value, onChange, ariaLabel }) {
|
|
|
1033
1082
|
}
|
|
1034
1083
|
}
|
|
1035
1084
|
const current = tabs.find((t2) => t2.id === active) ?? tabs[0];
|
|
1036
|
-
return /* @__PURE__ */ jsxs("div", { className: "tabs", children: [
|
|
1085
|
+
return /* @__PURE__ */ jsxs("div", { className: trailing === void 0 ? "tabs" : "tabs tabs-has-slot", children: [
|
|
1037
1086
|
/* @__PURE__ */ jsx("div", { className: "tablist", role: "tablist", "aria-label": ariaLabel, ref: rowRef, children: tabs.map((tab, idx) => {
|
|
1038
1087
|
const selected = tab.id === current?.id;
|
|
1039
1088
|
return /* @__PURE__ */ jsx(
|
|
@@ -1054,6 +1103,7 @@ function Tabs({ tabs, initial, value, onChange, ariaLabel }) {
|
|
|
1054
1103
|
tab.id
|
|
1055
1104
|
);
|
|
1056
1105
|
}) }),
|
|
1106
|
+
trailing !== void 0 && /* @__PURE__ */ jsx("div", { className: "tabs-slot", "data-tabs-slot": "", children: trailing }),
|
|
1057
1107
|
current && /* @__PURE__ */ jsx(
|
|
1058
1108
|
"div",
|
|
1059
1109
|
{
|
|
@@ -1063,7 +1113,7 @@ function Tabs({ tabs, initial, value, onChange, ariaLabel }) {
|
|
|
1063
1113
|
tabIndex: 0,
|
|
1064
1114
|
className: "tabpanel",
|
|
1065
1115
|
"data-panel": current.id,
|
|
1066
|
-
children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: current.content }) })
|
|
1116
|
+
children: /* @__PURE__ */ jsx(PanelPathContext.Provider, { value: `${path}/${current.id}`, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: current.content }) }) })
|
|
1067
1117
|
},
|
|
1068
1118
|
current.id
|
|
1069
1119
|
)
|
|
@@ -1073,7 +1123,8 @@ function SubTabs({ tabs, initial, value, onChange, ariaLabel, orientation = "hor
|
|
|
1073
1123
|
const baseId = useId();
|
|
1074
1124
|
const first = tabs[0]?.id ?? "";
|
|
1075
1125
|
const memory = useContext(ViewMemoryContext);
|
|
1076
|
-
const
|
|
1126
|
+
const path = useContext(PanelPathContext);
|
|
1127
|
+
const mkey = memoryKey("subtabs", path, tabs.map((t2) => t2.id));
|
|
1077
1128
|
const [own, setOwn] = useState(() => {
|
|
1078
1129
|
const kept = memory?.get(mkey);
|
|
1079
1130
|
return kept && tabs.some((t2) => t2.id === kept) ? kept : initial ?? first;
|
|
@@ -1147,7 +1198,7 @@ function SubTabs({ tabs, initial, value, onChange, ariaLabel, orientation = "hor
|
|
|
1147
1198
|
tabIndex: 0,
|
|
1148
1199
|
className: "subtabpanel",
|
|
1149
1200
|
"data-panel": current.id,
|
|
1150
|
-
children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: current.content }) })
|
|
1201
|
+
children: /* @__PURE__ */ jsx(PanelPathContext.Provider, { value: `${path}/${current.id}`, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: current.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: current.content }) }) })
|
|
1151
1202
|
},
|
|
1152
1203
|
current.id
|
|
1153
1204
|
) })
|
|
@@ -2920,20 +2971,155 @@ function useUrlView(ids, param, value, onChange) {
|
|
|
2920
2971
|
function viewParamOf(option) {
|
|
2921
2972
|
return option === false ? null : typeof option === "string" ? option : "view";
|
|
2922
2973
|
}
|
|
2923
|
-
|
|
2974
|
+
|
|
2975
|
+
// src/workbench/focus.ts
|
|
2976
|
+
var FOCUS_TEXT = {
|
|
2977
|
+
enter: { en: "Focus", es: "Enfocar" },
|
|
2978
|
+
enterLabel: { en: "Open this case in focus mode", es: "Abrir este caso en modo enfoque" },
|
|
2979
|
+
enterViewLabel: { en: "Open this view in focus mode", es: "Abrir esta vista en modo enfoque" },
|
|
2980
|
+
exit: { en: "Back to the App", es: "Volver a la App" },
|
|
2981
|
+
stage: { en: "Focus stage", es: "Escenario de enfoque" }
|
|
2982
|
+
};
|
|
2983
|
+
var FOCUS_PARAM = "focus";
|
|
2984
|
+
|
|
2985
|
+
// src/shell/useFocusMode.ts
|
|
2986
|
+
function useFocusMode(cfg, options = {}) {
|
|
2987
|
+
const enabled = options.enabled ?? true;
|
|
2988
|
+
const param = cfg?.param ?? FOCUS_PARAM;
|
|
2989
|
+
const [on, setOn] = useState(
|
|
2990
|
+
() => cfg !== null && typeof window !== "undefined" && readCaseParam(window.location.search, param) === "1" && (options.initialAllowed?.() ?? true)
|
|
2991
|
+
);
|
|
2992
|
+
const enterRef = useRef(null);
|
|
2993
|
+
const memory = useRef(/* @__PURE__ */ new Map());
|
|
2994
|
+
const turned = useRef(null);
|
|
2995
|
+
const onChangeRef = useRef(cfg?.onChange);
|
|
2996
|
+
onChangeRef.current = cfg?.onChange;
|
|
2997
|
+
const setFocus = useCallback(
|
|
2998
|
+
(next) => {
|
|
2999
|
+
turned.current = next ? "in" : "out";
|
|
3000
|
+
setOn(next);
|
|
3001
|
+
if (next) replaceQueryParam(param, "1");
|
|
3002
|
+
else removeQueryParam(param);
|
|
3003
|
+
onChangeRef.current?.(next);
|
|
3004
|
+
},
|
|
3005
|
+
[param]
|
|
3006
|
+
);
|
|
3007
|
+
const show = cfg !== null && on && enabled;
|
|
3008
|
+
useEffect(() => {
|
|
3009
|
+
if (cfg === null || enabled) return;
|
|
3010
|
+
if (on) setOn(false);
|
|
3011
|
+
if (typeof window !== "undefined" && readCaseParam(window.location.search, param) === "1") removeQueryParam(param);
|
|
3012
|
+
}, [cfg === null, enabled, on, param]);
|
|
3013
|
+
useEffect(() => {
|
|
3014
|
+
if (!show) return void 0;
|
|
3015
|
+
const onKey = (e) => {
|
|
3016
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
3017
|
+
e.preventDefault();
|
|
3018
|
+
setFocus(false);
|
|
3019
|
+
};
|
|
3020
|
+
document.addEventListener("keydown", onKey);
|
|
3021
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
3022
|
+
}, [show, setFocus]);
|
|
3023
|
+
useEffect(() => {
|
|
3024
|
+
if (!show) return void 0;
|
|
3025
|
+
const frame = document.querySelector("[data-focus-shell]");
|
|
3026
|
+
if (!frame) return void 0;
|
|
3027
|
+
const made = [];
|
|
3028
|
+
for (let node = frame; node && node !== document.body; node = node.parentElement) {
|
|
3029
|
+
for (const sib of Array.from(node.parentElement?.children ?? [])) {
|
|
3030
|
+
if (sib !== node && !sib.hasAttribute("inert") && sib.tagName !== "SCRIPT" && sib.tagName !== "STYLE") {
|
|
3031
|
+
sib.setAttribute("inert", "");
|
|
3032
|
+
made.push(sib);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
return () => {
|
|
3037
|
+
for (const el of made) el.removeAttribute("inert");
|
|
3038
|
+
};
|
|
3039
|
+
}, [show]);
|
|
3040
|
+
useEffect(() => {
|
|
3041
|
+
if (turned.current === "in" && show) document.querySelector("[data-focus-exit]")?.focus();
|
|
3042
|
+
else if (turned.current === "out" && !show) enterRef.current?.focus();
|
|
3043
|
+
turned.current = null;
|
|
3044
|
+
}, [show]);
|
|
3045
|
+
return { show, setFocus, enterRef, memory: memory.current };
|
|
3046
|
+
}
|
|
3047
|
+
function textOf(node) {
|
|
3048
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
3049
|
+
if (Array.isArray(node)) return node.map(textOf).join("");
|
|
3050
|
+
if (isValidElement(node)) return textOf(node.props.children);
|
|
3051
|
+
return "";
|
|
3052
|
+
}
|
|
3053
|
+
var viewFocus = (tab) => !tab || !tab.focus ? null : tab.focus === true ? {} : tab.focus;
|
|
3054
|
+
function SurfacePage({ title, lede, actions, tabs, value, onChange, deepLinkView = true, ariaLabel, instrument, focus, children }) {
|
|
2924
3055
|
const lang = useShellLang();
|
|
2925
|
-
const
|
|
2926
|
-
const
|
|
2927
|
-
|
|
3056
|
+
const placeAbove = useContext(PanelPathContext);
|
|
3057
|
+
const ids = (tabs ?? []).map((t2) => t2.id);
|
|
3058
|
+
const viewParam = tabs ? viewParamOf(deepLinkView) : null;
|
|
3059
|
+
const [open, select] = useUrlView(ids, viewParam, value, onChange);
|
|
3060
|
+
const focusCfg = focus === true ? {} : focus || null;
|
|
3061
|
+
const openTab = tabs?.find((t2) => t2.id === open) ?? tabs?.[0];
|
|
3062
|
+
const openFocus = focusCfg ? viewFocus(openTab) : null;
|
|
3063
|
+
const mode = useFocusMode(focusCfg, {
|
|
3064
|
+
enabled: openFocus !== null,
|
|
3065
|
+
// a link opens Focus only on a view with a stage: the view it names (the first one when it names none), never an unknown one
|
|
3066
|
+
initialAllowed: () => {
|
|
3067
|
+
const named = value ?? (viewParam && typeof window !== "undefined" ? readCaseParam(window.location.search, viewParam) : null);
|
|
3068
|
+
if (named !== null && !ids.includes(named)) return false;
|
|
3069
|
+
return viewFocus(tabs?.find((t2) => t2.id === (named ?? ids[0]))) !== null;
|
|
3070
|
+
}
|
|
3071
|
+
});
|
|
3072
|
+
const reserve = focusCfg !== null && Boolean(tabs?.some((t2) => viewFocus(t2) !== null));
|
|
3073
|
+
const idle = openFocus === null;
|
|
3074
|
+
const enter = reserve && /* @__PURE__ */ jsxs(
|
|
3075
|
+
"button",
|
|
3076
|
+
{
|
|
3077
|
+
ref: mode.enterRef,
|
|
3078
|
+
type: "button",
|
|
3079
|
+
className: "btn caos-focus-enter",
|
|
3080
|
+
"data-focus-enter": "",
|
|
3081
|
+
"aria-label": FOCUS_TEXT.enterViewLabel[lang],
|
|
3082
|
+
title: FOCUS_TEXT.enterViewLabel[lang],
|
|
3083
|
+
"aria-hidden": idle ? true : void 0,
|
|
3084
|
+
tabIndex: idle ? -1 : void 0,
|
|
3085
|
+
style: idle ? { visibility: "hidden" } : void 0,
|
|
3086
|
+
onClick: () => mode.setFocus(true),
|
|
3087
|
+
children: [
|
|
3088
|
+
/* @__PURE__ */ jsx(Maximize2, { size: 14, "aria-hidden": "true" }),
|
|
3089
|
+
/* @__PURE__ */ jsx("span", { children: FOCUS_TEXT.enter[lang] })
|
|
3090
|
+
]
|
|
3091
|
+
}
|
|
3092
|
+
);
|
|
3093
|
+
if (mode.show && openTab && openFocus) {
|
|
3094
|
+
const label = textOf(openTab.label);
|
|
3095
|
+
const surfaceTitle = title !== void 0 ? pick(title, lang) : "";
|
|
3096
|
+
return /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: mode.memory, children: /* @__PURE__ */ jsx(
|
|
3097
|
+
FocusShell,
|
|
3098
|
+
{
|
|
3099
|
+
title: openFocus.title !== void 0 ? pick(openFocus.title, lang) : label,
|
|
3100
|
+
description: openFocus.description !== void 0 ? pick(openFocus.description, lang) : surfaceTitle,
|
|
3101
|
+
hud: (openFocus.hud ?? []).map((h) => ({ label: pick(h.label, lang), value: h.value })),
|
|
3102
|
+
onExit: () => mode.setFocus(false),
|
|
3103
|
+
exitLabel: FOCUS_TEXT.exit[lang],
|
|
3104
|
+
stageLabel: FOCUS_TEXT.stage[lang],
|
|
3105
|
+
landmark: false,
|
|
3106
|
+
stage: /* @__PURE__ */ jsx("div", { className: "caos-surface-focus", "data-surface-focus": "", "data-view-param": viewParam ?? "", "data-focus-param": focusCfg?.param ?? FOCUS_PARAM, "data-focus-view": openTab.id, "data-focus-group": openTab.id, "data-instrument": "", children: /* @__PURE__ */ jsx("div", { className: "tabs", children: /* @__PURE__ */ jsx("div", { className: "tabpanel", role: "tabpanel", tabIndex: 0, "aria-label": label || void 0, "data-panel": openTab.id, children: /* @__PURE__ */ jsx(PanelPathContext.Provider, { value: `${placeAbove}/${openTab.id}`, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: openTab.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: openTab.content }) }) }) }) }) }),
|
|
3107
|
+
rail: openFocus.rail !== void 0 && openFocus.rail !== null ? /* @__PURE__ */ jsx("div", { className: "caos-focus-rail-body", children: openFocus.rail }) : null
|
|
3108
|
+
}
|
|
3109
|
+
) });
|
|
3110
|
+
}
|
|
3111
|
+
const head = title !== void 0 || lede !== void 0 || actions !== void 0 && actions !== null;
|
|
3112
|
+
const page = /* @__PURE__ */ jsxs("div", { className: "page-body wide caos-surface", "data-surface": "", "data-view-param": reserve ? viewParam ?? "" : void 0, children: [
|
|
2928
3113
|
head && /* @__PURE__ */ jsxs("div", { className: "caos-surface-head", children: [
|
|
2929
3114
|
/* @__PURE__ */ jsxs("div", { className: "caos-surface-heading", children: [
|
|
2930
3115
|
title !== void 0 && /* @__PURE__ */ jsx("h1", { className: "caos-surface-title", children: pick(title, lang) }),
|
|
2931
3116
|
lede !== void 0 && /* @__PURE__ */ jsx("p", { className: "caos-surface-lede", children: lede })
|
|
2932
3117
|
] }),
|
|
2933
|
-
actions !== void 0 && /* @__PURE__ */ jsx("div", { className: "caos-surface-actions", children: actions })
|
|
3118
|
+
actions !== void 0 && actions !== null && /* @__PURE__ */ jsx("div", { className: "caos-surface-actions", children: actions })
|
|
2934
3119
|
] }),
|
|
2935
|
-
/* @__PURE__ */ jsx("div", { className: "caos-surface-body", "data-instrument": instrument ? "" : void 0, children: tabs ? /* @__PURE__ */ jsx(Tabs, { tabs, value: open, onChange: select, ariaLabel: pick(ariaLabel ?? title ?? { en: "Views", es: "Vistas" }, lang) }) : children })
|
|
3120
|
+
/* @__PURE__ */ jsx("div", { className: "caos-surface-body", "data-instrument": instrument ? "" : void 0, children: tabs ? /* @__PURE__ */ jsx(Tabs, { tabs, value: open, onChange: select, ariaLabel: pick(ariaLabel ?? title ?? { en: "Views", es: "Vistas" }, lang), trailing: enter || void 0 }) : children })
|
|
2936
3121
|
] });
|
|
3122
|
+
return focusCfg ? /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: mode.memory, children: page }) : page;
|
|
2937
3123
|
}
|
|
2938
3124
|
|
|
2939
3125
|
// src/lib/breakpoints.ts
|
|
@@ -3298,15 +3484,6 @@ function VariantBar({ variants, activeId, onSelect, title, lane }) {
|
|
|
3298
3484
|
active?.note && /* @__PURE__ */ jsx("p", { className: "caos-variant-note", children: pick(active.note, lang) })
|
|
3299
3485
|
] });
|
|
3300
3486
|
}
|
|
3301
|
-
|
|
3302
|
-
// src/workbench/focus.ts
|
|
3303
|
-
var FOCUS_TEXT = {
|
|
3304
|
-
enter: { en: "Focus", es: "Enfocar" },
|
|
3305
|
-
enterLabel: { en: "Open this case in focus mode", es: "Abrir este caso en modo enfoque" },
|
|
3306
|
-
exit: { en: "Back to the App", es: "Volver a la App" },
|
|
3307
|
-
stage: { en: "Focus stage", es: "Escenario de enfoque" }
|
|
3308
|
-
};
|
|
3309
|
-
var FOCUS_PARAM = "focus";
|
|
3310
3487
|
var MAX_WORKBENCH_GROUPS = MAX_PEER_TABS;
|
|
3311
3488
|
function panel(id, lane, provenance, content) {
|
|
3312
3489
|
return /* @__PURE__ */ jsx("div", { className: "caos-cw-panel", "data-group": id, "data-lane": lane, "data-provenance": provenance, children: content });
|
|
@@ -3315,56 +3492,8 @@ function CaseWorkbench(props) {
|
|
|
3315
3492
|
const { caseId, cases, source, controls, replayOnly, variants, rail, railLabel, groups, compare, context: context2, group, onGroupChange, deepLinkView = true, ariaLabel, focus } = props;
|
|
3316
3493
|
const lang = useShellLang();
|
|
3317
3494
|
const focusCfg = focus === true ? {} : focus || null;
|
|
3318
|
-
const
|
|
3319
|
-
const
|
|
3320
|
-
const enterRef = useRef(null);
|
|
3321
|
-
const memory = useRef(/* @__PURE__ */ new Map());
|
|
3322
|
-
const turned = useRef(null);
|
|
3323
|
-
const onChangeRef = useRef(focusCfg?.onChange);
|
|
3324
|
-
onChangeRef.current = focusCfg?.onChange;
|
|
3325
|
-
const setFocus = useCallback(
|
|
3326
|
-
(on) => {
|
|
3327
|
-
turned.current = on ? "in" : "out";
|
|
3328
|
-
setFocusOn(on);
|
|
3329
|
-
if (on) replaceQueryParam(focusParam, "1");
|
|
3330
|
-
else removeQueryParam(focusParam);
|
|
3331
|
-
onChangeRef.current?.(on);
|
|
3332
|
-
},
|
|
3333
|
-
[focusParam]
|
|
3334
|
-
);
|
|
3335
|
-
const showFocus = focusCfg !== null && focusOn;
|
|
3336
|
-
useEffect(() => {
|
|
3337
|
-
if (!showFocus) return void 0;
|
|
3338
|
-
const onKey = (e) => {
|
|
3339
|
-
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
3340
|
-
e.preventDefault();
|
|
3341
|
-
setFocus(false);
|
|
3342
|
-
};
|
|
3343
|
-
document.addEventListener("keydown", onKey);
|
|
3344
|
-
return () => document.removeEventListener("keydown", onKey);
|
|
3345
|
-
}, [showFocus, setFocus]);
|
|
3346
|
-
useEffect(() => {
|
|
3347
|
-
if (!showFocus) return void 0;
|
|
3348
|
-
const frame = document.querySelector("[data-focus-shell]");
|
|
3349
|
-
if (!frame) return void 0;
|
|
3350
|
-
const made = [];
|
|
3351
|
-
for (let node = frame; node && node !== document.body; node = node.parentElement) {
|
|
3352
|
-
for (const sib of Array.from(node.parentElement?.children ?? [])) {
|
|
3353
|
-
if (sib !== node && !sib.hasAttribute("inert") && sib.tagName !== "SCRIPT" && sib.tagName !== "STYLE") {
|
|
3354
|
-
sib.setAttribute("inert", "");
|
|
3355
|
-
made.push(sib);
|
|
3356
|
-
}
|
|
3357
|
-
}
|
|
3358
|
-
}
|
|
3359
|
-
return () => {
|
|
3360
|
-
for (const el of made) el.removeAttribute("inert");
|
|
3361
|
-
};
|
|
3362
|
-
}, [showFocus]);
|
|
3363
|
-
useEffect(() => {
|
|
3364
|
-
if (turned.current === "in" && showFocus) document.querySelector("[data-focus-exit]")?.focus();
|
|
3365
|
-
else if (turned.current === "out" && !showFocus) enterRef.current?.focus();
|
|
3366
|
-
turned.current = null;
|
|
3367
|
-
}, [showFocus]);
|
|
3495
|
+
const { show: showFocus, setFocus, enterRef, memory } = useFocusMode(focusCfg);
|
|
3496
|
+
const placeAbove = useContext(PanelPathContext);
|
|
3368
3497
|
const variantId = variants?.activeId;
|
|
3369
3498
|
const stateKey = useMemo(
|
|
3370
3499
|
() => makeStateKey({ caseId, variantId: variantId ?? null, source: source ?? null, controls: controls ?? {} }),
|
|
@@ -3405,19 +3534,12 @@ function CaseWorkbench(props) {
|
|
|
3405
3534
|
]
|
|
3406
3535
|
}
|
|
3407
3536
|
);
|
|
3408
|
-
const besideSelect = Boolean(cases && (cases.layout ?? "chips") === "select");
|
|
3409
3537
|
const pickers = cases || variants ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3410
|
-
cases && /* @__PURE__ */ jsx(CaseSelector, { lang, ...cases
|
|
3538
|
+
cases && /* @__PURE__ */ jsx(CaseSelector, { lang, ...cases }),
|
|
3411
3539
|
variants && /* @__PURE__ */ jsx(VariantBar, { ...variants })
|
|
3412
3540
|
] }) : null;
|
|
3413
|
-
const
|
|
3414
|
-
|
|
3415
|
-
variants && /* @__PURE__ */ jsx(VariantBar, { ...variants })
|
|
3416
|
-
] }) : null;
|
|
3417
|
-
const railHead = pickers || enter ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3418
|
-
pickers,
|
|
3419
|
-
besideSelect ? null : enter
|
|
3420
|
-
] }) : null;
|
|
3541
|
+
const railHead = pickers;
|
|
3542
|
+
const focusPickers = pickers;
|
|
3421
3543
|
const instrumentData = {
|
|
3422
3544
|
"data-case-workbench": "",
|
|
3423
3545
|
"data-group-count": tabs.length,
|
|
@@ -3430,7 +3552,7 @@ function CaseWorkbench(props) {
|
|
|
3430
3552
|
if (showFocus && focusCfg) {
|
|
3431
3553
|
const open = tabs.find((t2) => t2.id === openGroup) ?? tabs[0];
|
|
3432
3554
|
const caseName = cases?.cases.find((c) => c.id === caseId)?.name ?? caseId;
|
|
3433
|
-
return /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: memory
|
|
3555
|
+
return /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: memory, children: /* @__PURE__ */ jsx(WorkbenchStateContext.Provider, { value: { stateKey, caseId, variantId, source }, children: /* @__PURE__ */ jsx(
|
|
3434
3556
|
FocusShell,
|
|
3435
3557
|
{
|
|
3436
3558
|
title: focusCfg.title !== void 0 ? pick(focusCfg.title, lang) : caseName,
|
|
@@ -3440,18 +3562,19 @@ function CaseWorkbench(props) {
|
|
|
3440
3562
|
exitLabel: FOCUS_TEXT.exit[lang],
|
|
3441
3563
|
stageLabel: FOCUS_TEXT.stage[lang],
|
|
3442
3564
|
landmark: false,
|
|
3443
|
-
stage: /* @__PURE__ */ jsx("div", { className: "caos-cw caos-cw-focus", ...instrumentData, "data-focus-group": open?.id, "data-instrument": "", children: /* @__PURE__ */ jsx("div", { className: "tabs", children: open && /* @__PURE__ */ jsx("div", { className: "tabpanel", role: "tabpanel", tabIndex: 0, "aria-label": open.label, "data-panel": open.id, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: open.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: open.content }) }) }) }) }),
|
|
3565
|
+
stage: /* @__PURE__ */ jsx("div", { className: "caos-cw caos-cw-focus", ...instrumentData, "data-focus-group": open?.id, "data-focus-param": focusCfg.param ?? FOCUS_PARAM, "data-instrument": "", children: /* @__PURE__ */ jsx("div", { className: "tabs", children: open && /* @__PURE__ */ jsx("div", { className: "tabpanel", role: "tabpanel", tabIndex: 0, "aria-label": open.label, "data-panel": open.id, children: /* @__PURE__ */ jsx(PanelPathContext.Provider, { value: `${placeAbove}/${open.id}`, children: /* @__PURE__ */ jsx(PanelBoundary, { panel: open.id, children: /* @__PURE__ */ jsx(DocRefsScope, { children: open.content }) }) }) }) }) }),
|
|
3444
3566
|
rail: /* @__PURE__ */ jsx("div", { className: "caos-focus-rail-body", children: /* @__PURE__ */ jsx(RailBody, { rail: rail ?? null, railHead: focusPickers, railLabel }) })
|
|
3445
3567
|
}
|
|
3446
3568
|
) }) });
|
|
3447
3569
|
}
|
|
3448
|
-
return /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: memory
|
|
3570
|
+
return /* @__PURE__ */ jsx(ViewMemoryContext.Provider, { value: memory, children: /* @__PURE__ */ jsx(WorkbenchStateContext.Provider, { value: { stateKey, caseId, variantId, source }, children: /* @__PURE__ */ jsx(WorkbenchLayout, { rail: rail ?? null, railHead, railLabel, children: /* @__PURE__ */ jsx("div", { className: "caos-cw", ...instrumentData, children: /* @__PURE__ */ jsx(
|
|
3449
3571
|
Tabs,
|
|
3450
3572
|
{
|
|
3451
3573
|
tabs,
|
|
3452
3574
|
value: openGroup,
|
|
3453
3575
|
onChange: selectGroup,
|
|
3454
|
-
ariaLabel: pick(ariaLabel ?? { en: "Case views", es: "Vistas del caso" }, lang)
|
|
3576
|
+
ariaLabel: pick(ariaLabel ?? { en: "Case views", es: "Vistas del caso" }, lang),
|
|
3577
|
+
trailing: enter || void 0
|
|
3455
3578
|
}
|
|
3456
3579
|
) }) }) }) });
|
|
3457
3580
|
}
|
|
@@ -3655,4 +3778,4 @@ function ViewsRow({ children, shares }) {
|
|
|
3655
3778
|
}) });
|
|
3656
3779
|
}
|
|
3657
3780
|
|
|
3658
|
-
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CardDeck, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, DocPage, DocReferences, DocRefsScope, DocSection, EXECUTIVE_SCHEMA_VERSION, Equation, ExecutiveView, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, FocusShell, Gauge, Glossary, InlineMath, Knob, Landing, LaneBadge, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Question, Readout, ReferenceList, Refs, Stat, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, ViewsRow, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, caseKind, caseTooltip, casesInSource, createVizLoop, deckPosition, deckWindow, firstAppearance, fitLabel, fontFamily, groupByCategory, isStale, makeStateKey, niceStep, niceTicks, perPageFor, readCaseParam, removeQueryParam, replaceQueryParam, sourcesPresent, textWidth, usePausedViz, useUrlView, useWorkbenchState, validateArchitectureConfig, validateExecutive, viewParamOf, widestLabel, withCaseParam };
|
|
3781
|
+
export { AppShell, ArchitectureModal, BREAKPOINTS, BarChart, Callout, CardDeck, CaseSelector, CaseWorkbench, ChipGroup, CitationsProvider, Cite, DECK_TEXT, DOC_AREAS, DocPage, DocReferences, DocRefsScope, DocSection, EXECUTIVE_SCHEMA_VERSION, Equation, ExecutiveView, FLIP_LIMITS, FLIP_TEXT, FOCUS_PARAM, FOCUS_TEXT, FOUR_CS, Figure, FlipCard, FocusShell, Gauge, Glossary, InlineMath, Knob, Landing, LaneBadge, LanguageToggle, MAX_PEER_TABS, MAX_WORKBENCH_GROUPS, PanelBoundary, PlotCard, Question, Readout, ReferenceList, Refs, Stat, SubTabs, SurfacePage, TabGroups, Tabs, ThemeToggle, VariantBar, Verdict, ViewsRow, WorkbenchLayout, WorkbenchShell, WorkbenchStateContext, caseKind, caseTooltip, casesInSource, createVizLoop, deckPosition, deckWindow, firstAppearance, fitLabel, fontFamily, groupByCategory, isStale, makeStateKey, niceStep, niceTicks, perPageFor, readCaseParam, removeQueryParam, replaceQueryParam, sourcesPresent, textWidth, useFocusMode, usePausedViz, useUrlView, useWorkbenchState, validateArchitectureConfig, validateExecutive, viewParamOf, widestLabel, withCaseParam };
|
package/gate/inpage.mjs
CHANGED
|
@@ -181,6 +181,67 @@ export function installLib() {
|
|
|
181
181
|
};
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Selects whose selected option does not fit the room it leaves, or a case select that shows another name than the one it holds (BL-074, shell
|
|
186
|
+
* 0.17.0; the reviews of 0.17.0). What is measured is what the reader sees:
|
|
187
|
+
* - a select with the shell's display (`.cs-select-wrap`, the case select since the trial of 0.17.0) shows the name whole at any length in the
|
|
188
|
+
* display's real `.cs-select-text` element: the name is cut when that element overflows its box (`scrollHeight` or `scrollWidth` past its
|
|
189
|
+
* `clientHeight` or `clientWidth` by more than 1px, e.g. a product stylesheet that clamps it), measured on the element itself, never on a probe
|
|
190
|
+
* copy, and when the display's own box is smaller than its content (a product stylesheet that fixes its height); the display is stale when the name it shows, whitespace normalized, differs from the text of the option the select holds;
|
|
191
|
+
* - any other select shows its text on one line: the room is its width less its own CSS padding and less the native arrow, which is
|
|
192
|
+
* measured, not assumed (a probe select of the same class holding one character, sized to its content; 0 with `appearance: none`).
|
|
193
|
+
* Plain text is measured with the select's own font on one canvas. Runs in the App and inside Focus (its rail holds a case select too).
|
|
194
|
+
*/
|
|
195
|
+
const selectCuts = () => {
|
|
196
|
+
const cuts = [];
|
|
197
|
+
const mctx = document.createElement('canvas').getContext('2d');
|
|
198
|
+
const norm = (t) => (t ?? '').trim().replace(/\s+/g, ' ');
|
|
199
|
+
for (const sel of document.querySelectorAll('select')) {
|
|
200
|
+
if (!mctx || !visible(sel)) continue;
|
|
201
|
+
const ss = getComputedStyle(sel);
|
|
202
|
+
const held = norm(sel.options[sel.selectedIndex]?.textContent);
|
|
203
|
+
const display = sel.closest('.cs-select-wrap')?.querySelector('.cs-select-display');
|
|
204
|
+
if (display) {
|
|
205
|
+
const t = display.querySelector('.cs-select-text') ?? display;
|
|
206
|
+
const shown = norm(t.textContent);
|
|
207
|
+
if (shown !== held) {
|
|
208
|
+
cuts.push(`${describe(sel)} shows "${shown}" while the select holds "${held}" (a stale display)`);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const overV = t.scrollHeight > t.clientHeight + 1;
|
|
212
|
+
const overH = t.scrollWidth > t.clientWidth + 1;
|
|
213
|
+
if (overV || overH) {
|
|
214
|
+
const line = Number.parseFloat(getComputedStyle(t).lineHeight) || 1;
|
|
215
|
+
cuts.push(`${describe(sel)} cuts the case name "${shown}" (the name needs ${Math.round(t.scrollHeight / line)} lines, ${Math.round(t.scrollHeight)}px high and ${Math.round(t.scrollWidth)}px wide, and its box holds ${Math.round(t.clientHeight)}px by ${Math.round(t.clientWidth)}px; held option "${held}")`);
|
|
216
|
+
} else if (display.scrollHeight > display.clientHeight + 1 || display.scrollWidth > display.clientWidth + 1) {
|
|
217
|
+
// the text fits its own box but the display's box is smaller (a product stylesheet that fixes the display's height)
|
|
218
|
+
cuts.push(`${describe(sel)} cuts the case name "${shown}" in its display (the name needs ${Math.round(display.scrollHeight)}px by ${Math.round(display.scrollWidth)}px, the display holds ${Math.round(display.clientHeight)}px by ${Math.round(display.clientWidth)}px; held option "${held}")`);
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const text = held;
|
|
223
|
+
if (!text) continue;
|
|
224
|
+
mctx.font = `${ss.fontStyle} ${ss.fontWeight} ${ss.fontSize} ${ss.fontFamily}`;
|
|
225
|
+
const pad = Number.parseFloat(ss.paddingLeft) + Number.parseFloat(ss.paddingRight);
|
|
226
|
+
let arrow = 0;
|
|
227
|
+
const host = sel.parentElement;
|
|
228
|
+
if (host) {
|
|
229
|
+
const probe = sel.cloneNode(false);
|
|
230
|
+
for (const a of [...probe.attributes]) if (a.name !== 'class') probe.removeAttribute(a.name);
|
|
231
|
+
probe.append(new Option('x'));
|
|
232
|
+
probe.setAttribute('style', 'position:absolute;visibility:hidden;width:auto;min-width:0;max-width:none;flex:none;left:0;top:0');
|
|
233
|
+
host.append(probe);
|
|
234
|
+
const pv = getComputedStyle(probe);
|
|
235
|
+
arrow = Math.max(0, probe.clientWidth - Number.parseFloat(pv.paddingLeft) - Number.parseFloat(pv.paddingRight) - mctx.measureText('x').width);
|
|
236
|
+
probe.remove();
|
|
237
|
+
}
|
|
238
|
+
const need = mctx.measureText(text).width;
|
|
239
|
+
const has = sel.clientWidth - pad - arrow;
|
|
240
|
+
if (need > has + 1) cuts.push(`${describe(sel)} cuts "${text}" (${Math.round(need)}px of text in ${Math.round(has)}px of room, the ${Math.round(arrow)}px arrow taken out)`);
|
|
241
|
+
}
|
|
242
|
+
return cuts;
|
|
243
|
+
};
|
|
244
|
+
|
|
184
245
|
/** The static layout of the current view (G5, G6 inputs, ADR-0071 and ADR-0017 measures). */
|
|
185
246
|
G.facts = () => {
|
|
186
247
|
const vw = window.innerWidth;
|
|
@@ -272,6 +333,8 @@ export function installLib() {
|
|
|
272
333
|
if (!named) truncated.push(describe(el));
|
|
273
334
|
}
|
|
274
335
|
|
|
336
|
+
const cutSelects = selectCuts();
|
|
337
|
+
|
|
275
338
|
// Containers that cut their content: overflow hidden or clip with more content than box (S1, G5).
|
|
276
339
|
const clipped = [];
|
|
277
340
|
const containers = new Set([de, body, document.getElementById('root'), ...document.querySelectorAll('main, .page, .page-body, [data-instrument], [data-rail], .tabpanel, .caos-cw, [data-plot]')]);
|
|
@@ -415,6 +478,8 @@ export function installLib() {
|
|
|
415
478
|
rail,
|
|
416
479
|
truncated: truncated.slice(0, 5),
|
|
417
480
|
truncatedCount: truncated.length,
|
|
481
|
+
cutSelects: cutSelects.slice(0, 5),
|
|
482
|
+
cutSelectCount: cutSelects.length,
|
|
418
483
|
clipped,
|
|
419
484
|
instrument,
|
|
420
485
|
surfaces,
|
|
@@ -1029,7 +1094,9 @@ export function installLib() {
|
|
|
1029
1094
|
/** The App or Focus mode as the reader meets it: which one, the case and the group, the URL, the page and the rail. */
|
|
1030
1095
|
G.focusFacts = () => {
|
|
1031
1096
|
const shell = document.querySelector('[data-focus-shell]');
|
|
1032
|
-
|
|
1097
|
+
// the instrument of the App: a case workbench, or (a surface App, 0.16.0) the surface or its Focus frame
|
|
1098
|
+
// by explicit order, never the first match in document order: a workbench nested in a surface view is read as the workbench
|
|
1099
|
+
const stage = document.querySelector('[data-case-workbench]') ?? document.querySelector('[data-surface-focus]') ?? document.querySelector('[data-surface]');
|
|
1033
1100
|
const rail = document.querySelector('.caos-focus-rail');
|
|
1034
1101
|
const lists = [...document.querySelectorAll('[data-focus-shell] [role="tablist"]')].filter(visible);
|
|
1035
1102
|
const rows = lists.map((t) => new Set([...t.querySelectorAll('[role="tab"]')].map((b) => Math.round(b.getBoundingClientRect().top))).size);
|
|
@@ -1061,12 +1128,19 @@ export function installLib() {
|
|
|
1061
1128
|
return {
|
|
1062
1129
|
focus: Boolean(shell),
|
|
1063
1130
|
covered,
|
|
1131
|
+
cutSelects: (() => { const c = selectCuts(); return { count: c.length, list: c.slice(0, 5) }; })(),
|
|
1064
1132
|
enterInside: [...document.querySelectorAll('[data-focus-shell] [data-focus-enter]')].filter(visible).length,
|
|
1065
|
-
app: Boolean(document.querySelector('[data-workbench]')),
|
|
1133
|
+
app: Boolean(document.querySelector('[data-workbench], [data-surface]')),
|
|
1066
1134
|
caseId: stage ? stage.getAttribute('data-case') : null,
|
|
1067
1135
|
group: shell ? (stage ? stage.getAttribute('data-focus-group') : null) : panel ? panel.getAttribute('data-panel') : null,
|
|
1068
1136
|
search: location.search,
|
|
1069
|
-
|
|
1137
|
+
// the query parameter that holds Focus (`focus`, or `param` of the product's focus configuration), written on the Focus frame
|
|
1138
|
+
focusParam: (() => { const e = document.querySelector('[data-focus-param]'); return e ? e.getAttribute('data-focus-param') : null; })(),
|
|
1139
|
+
// a head with no title, lede or actions (an empty head row) on a surface
|
|
1140
|
+
emptyHead: (() => { const h = document.querySelector('[data-surface] > .caos-surface-head'); return h && !h.querySelector('.caos-surface-title, .caos-surface-lede, .caos-surface-actions') ? Math.round(h.getBoundingClientRect().height) : null; })(),
|
|
1141
|
+
// the query parameter the surface holds its open view in (`deepLinkView`): `view`, or the product's own name
|
|
1142
|
+
viewParam: (() => { const e = document.querySelector('[data-surface-focus]') ?? document.querySelector('[data-surface]'); return e && e.hasAttribute('data-view-param') ? e.getAttribute('data-view-param') : null; })(),
|
|
1143
|
+
subview: (() => { const t = document.querySelector('[data-case-workbench] .subtab.active, [data-surface] .subtab.active, [data-surface-focus] .subtab.active'); return t ? t.getAttribute('data-tab') : null; })(),
|
|
1070
1144
|
nestedMain: document.querySelectorAll('main').length > 1 || Boolean(document.querySelector('main main')),
|
|
1071
1145
|
vw: window.innerWidth,
|
|
1072
1146
|
vh: window.innerHeight,
|