@apliteni/apliteni-ui 0.58.2 → 0.59.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/package.json +11 -3
- package/react/dist/index.d.ts +1 -1
- package/react/dist/index.js +152 -96
- package/src/components/command-palette.js +2 -128
- package/src/components/dropdown.js +2 -16
- package/src/components/index.js +3 -1
- package/src/components/pagination.js +3 -2
- package/src/components/segmented.js +2 -9
- package/src/components/table-values.js +7 -7
- package/src/index.d.ts +128 -0
- package/src/index.js +7 -0
- package/src/logic/callout.js +1 -0
- package/src/logic/command-palette.js +127 -0
- package/src/logic/dropdown.js +16 -0
- package/src/logic/pagination.js +2 -0
- package/src/logic/segmented.js +8 -0
- package/src/logic/table-values.js +10 -0
- package/src/motion.d.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apliteni/apliteni-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"workspaces": [
|
|
5
5
|
"react"
|
|
6
6
|
],
|
|
@@ -18,9 +18,17 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"exports": {
|
|
21
|
-
".":
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./src/index.d.ts",
|
|
23
|
+
"import": "./src/index.js",
|
|
24
|
+
"default": "./src/index.js"
|
|
25
|
+
},
|
|
22
26
|
"./guidelines/*": "./guidelines/*",
|
|
23
|
-
"./motion":
|
|
27
|
+
"./motion": {
|
|
28
|
+
"types": "./src/motion.d.ts",
|
|
29
|
+
"import": "./src/motion.js",
|
|
30
|
+
"default": "./src/motion.js"
|
|
31
|
+
},
|
|
24
32
|
"./inline": "./src/inline.js",
|
|
25
33
|
"./css": "./src/index.css",
|
|
26
34
|
"./tokens": "./src/tokens/tokens.css",
|
package/react/dist/index.d.ts
CHANGED
|
@@ -465,7 +465,7 @@ type FilterBarProps = {
|
|
|
465
465
|
onClear: () => void;
|
|
466
466
|
onChange: (id: string, value: string | undefined) => void;
|
|
467
467
|
};
|
|
468
|
-
declare function FilterBar(
|
|
468
|
+
declare function FilterBar({ filters, label, clearLabel, disabled, busy, onRemove, onClear, onChange }: FilterBarProps): react.JSX.Element;
|
|
469
469
|
|
|
470
470
|
type KeyValueRow = {
|
|
471
471
|
label: string;
|
package/react/dist/index.js
CHANGED
|
@@ -1796,13 +1796,18 @@ function BackLink({
|
|
|
1796
1796
|
}
|
|
1797
1797
|
|
|
1798
1798
|
// src/TableValues.tsx
|
|
1799
|
-
import {
|
|
1799
|
+
import { formatNumericValue, formatDeltaValue } from "@apliteni/apliteni-ui";
|
|
1800
1800
|
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1801
1801
|
function NumericValue(props) {
|
|
1802
|
-
|
|
1802
|
+
const value = formatNumericValue(props);
|
|
1803
|
+
return /* @__PURE__ */ jsx17("span", { children: /* @__PURE__ */ jsxs15("span", { className: "ui-value", "aria-label": value.missing, children: [
|
|
1804
|
+
value.text,
|
|
1805
|
+
value.unit && /* @__PURE__ */ jsx17("span", { className: "ui-value__unit", children: value.unit })
|
|
1806
|
+
] }) });
|
|
1803
1807
|
}
|
|
1804
1808
|
function DeltaValue(props) {
|
|
1805
|
-
|
|
1809
|
+
const value = formatDeltaValue(props);
|
|
1810
|
+
return /* @__PURE__ */ jsx17("span", { children: /* @__PURE__ */ jsx17("span", { className: value.className, "aria-describedby": value.basisId || void 0, children: value.text }) });
|
|
1806
1811
|
}
|
|
1807
1812
|
function RowIdentity({ symbol, name, logo, href }) {
|
|
1808
1813
|
const Tag = href ? "a" : "span";
|
|
@@ -1852,44 +1857,95 @@ function Segmented({ options, value, onChange, label, appearance = "pill", disab
|
|
|
1852
1857
|
}
|
|
1853
1858
|
|
|
1854
1859
|
// src/FilterBar.tsx
|
|
1855
|
-
import {
|
|
1856
|
-
import {
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
const
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
const
|
|
1868
|
-
const
|
|
1869
|
-
const
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1860
|
+
import { useRef as useRef11, useState as useState7 } from "react";
|
|
1861
|
+
import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1862
|
+
function FilterBar({ filters, label = "Filters", clearLabel = "Clear all filters", disabled = false, busy = false, onRemove, onClear, onChange }) {
|
|
1863
|
+
const bar = useRef11(null);
|
|
1864
|
+
const focused = useRef11(null);
|
|
1865
|
+
const [opened, setOpened] = useState7({});
|
|
1866
|
+
const blocked = disabled || busy;
|
|
1867
|
+
useIsoLayoutEffect(() => {
|
|
1868
|
+
const stale = Object.keys(opened).filter((id) => !filters.some((filter) => filter.id === id && filter.open === opened[id].against));
|
|
1869
|
+
if (stale.length) setOpened((previous) => Object.fromEntries(Object.entries(previous).filter(([id]) => !stale.includes(id))));
|
|
1870
|
+
const prior = focused.current;
|
|
1871
|
+
if (!prior || prior.id === null && filters.length > 0 || filters.some((f) => f.id === prior.id)) return;
|
|
1872
|
+
const chips = Array.from(bar.current?.querySelectorAll("[data-filter-id]") ?? []);
|
|
1873
|
+
const next = chips.slice(prior.index).find((chip) => !chip.disabled) || chips.slice(0, prior.index).reverse().find((chip) => !chip.disabled);
|
|
1874
|
+
const target = !blocked && next?.querySelector("[data-dropdown-trigger]");
|
|
1875
|
+
focused.current = null;
|
|
1876
|
+
(target || bar.current)?.focus();
|
|
1877
|
+
});
|
|
1878
|
+
return /* @__PURE__ */ jsx19("div", { children: /* @__PURE__ */ jsxs16(
|
|
1879
|
+
"fieldset",
|
|
1880
|
+
{
|
|
1881
|
+
ref: bar,
|
|
1882
|
+
className: "ui-filter-bar",
|
|
1883
|
+
"data-filter-bar": "",
|
|
1884
|
+
disabled: blocked,
|
|
1885
|
+
"aria-busy": busy || void 0,
|
|
1886
|
+
tabIndex: -1,
|
|
1887
|
+
onBlur: (event) => {
|
|
1888
|
+
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) focused.current = null;
|
|
1889
|
+
},
|
|
1890
|
+
children: [
|
|
1891
|
+
/* @__PURE__ */ jsx19("legend", { className: "ui-filter-bar__legend", children: label }),
|
|
1892
|
+
filters.map((filter, index) => /* @__PURE__ */ jsxs16(
|
|
1893
|
+
"fieldset",
|
|
1894
|
+
{
|
|
1895
|
+
className: "ui-filter-bar__chip",
|
|
1896
|
+
"data-filter-id": filter.id,
|
|
1897
|
+
disabled: filter.disabled,
|
|
1898
|
+
onFocus: () => {
|
|
1899
|
+
focused.current = { id: filter.id, index };
|
|
1900
|
+
},
|
|
1901
|
+
children: [
|
|
1902
|
+
/* @__PURE__ */ jsx19("legend", { className: "ui-filter-bar__legend", children: filter.label }),
|
|
1903
|
+
/* @__PURE__ */ jsx19(
|
|
1904
|
+
Dropdown,
|
|
1905
|
+
{
|
|
1906
|
+
label: filter.label,
|
|
1907
|
+
value: filter.value,
|
|
1908
|
+
items: filter.items,
|
|
1909
|
+
variant: "select",
|
|
1910
|
+
ariaLabel: `${filter.label}: ${filter.value}`,
|
|
1911
|
+
open: !blocked && !filter.disabled && (opened[filter.id] && opened[filter.id].against === filter.open ? opened[filter.id].open : !!filter.open),
|
|
1912
|
+
onOpenChange: (open) => setOpened((previous) => ({ ...previous, [filter.id]: { open, against: filter.open } })),
|
|
1913
|
+
onSelect: (value) => {
|
|
1914
|
+
if (!blocked && !filter.disabled) onChange(filter.id, value == null ? void 0 : String(value));
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
),
|
|
1918
|
+
/* @__PURE__ */ jsx19(
|
|
1919
|
+
"button",
|
|
1920
|
+
{
|
|
1921
|
+
type: "button",
|
|
1922
|
+
className: "ui-filter-bar__remove",
|
|
1923
|
+
"data-filter-remove": "",
|
|
1924
|
+
"aria-label": `Remove ${filter.label} filter`,
|
|
1925
|
+
onClick: () => {
|
|
1926
|
+
if (!blocked && !filter.disabled) onRemove(filter.id);
|
|
1927
|
+
},
|
|
1928
|
+
children: "\xD7"
|
|
1929
|
+
}
|
|
1930
|
+
)
|
|
1931
|
+
]
|
|
1932
|
+
},
|
|
1933
|
+
filter.id
|
|
1934
|
+
)),
|
|
1935
|
+
/* @__PURE__ */ jsx19("span", { "data-filter-clear": "", onFocus: () => {
|
|
1936
|
+
focused.current = { id: null, index: 0 };
|
|
1937
|
+
}, children: /* @__PURE__ */ jsx19(Button, { size: "sm", variant: "ghost", disabled: !filters.length, onClick: () => {
|
|
1938
|
+
if (!blocked) onClear();
|
|
1939
|
+
}, children: clearLabel }) })
|
|
1940
|
+
]
|
|
1941
|
+
}
|
|
1942
|
+
) });
|
|
1887
1943
|
}
|
|
1888
1944
|
|
|
1889
1945
|
// src/KeyValueList.tsx
|
|
1890
|
-
import { jsx as jsx20, jsxs as
|
|
1946
|
+
import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1891
1947
|
function detail({ value, redacted }) {
|
|
1892
|
-
if (redacted) return /* @__PURE__ */
|
|
1948
|
+
if (redacted) return /* @__PURE__ */ jsxs17("span", { className: "ui-kv__hidden", children: [
|
|
1893
1949
|
/* @__PURE__ */ jsx20(Icon, { name: "eyeOff" }),
|
|
1894
1950
|
"Hidden"
|
|
1895
1951
|
] });
|
|
@@ -1897,24 +1953,24 @@ function detail({ value, redacted }) {
|
|
|
1897
1953
|
return typeof value === "boolean" ? String(value) : value;
|
|
1898
1954
|
}
|
|
1899
1955
|
function KeyValueList({ rows, columns = 1, className, ...rest }) {
|
|
1900
|
-
return /* @__PURE__ */ jsx20("dl", { ...rest, className: ["ui-kv", columns === 2 && "ui-kv--two-columns", className].filter(Boolean).join(" "), children: rows.map((row, index) => /* @__PURE__ */
|
|
1956
|
+
return /* @__PURE__ */ jsx20("dl", { ...rest, className: ["ui-kv", columns === 2 && "ui-kv--two-columns", className].filter(Boolean).join(" "), children: rows.map((row, index) => /* @__PURE__ */ jsxs17("div", { className: "ui-drawer__row", children: [
|
|
1901
1957
|
/* @__PURE__ */ jsx20("dt", { children: row.label }),
|
|
1902
1958
|
/* @__PURE__ */ jsx20("dd", { children: detail(row) })
|
|
1903
1959
|
] }, index)) });
|
|
1904
1960
|
}
|
|
1905
1961
|
function DrawerSection({ title, headingLevel = 3, className, children, ...rest }) {
|
|
1906
1962
|
const Heading = `h${headingLevel}`;
|
|
1907
|
-
return /* @__PURE__ */
|
|
1963
|
+
return /* @__PURE__ */ jsxs17("section", { ...rest, className: ["ui-drawer__section", className].filter(Boolean).join(" "), children: [
|
|
1908
1964
|
title != null && /* @__PURE__ */ jsx20(Heading, { className: "ui-drawer__section-title", children: title }),
|
|
1909
1965
|
children
|
|
1910
1966
|
] });
|
|
1911
1967
|
}
|
|
1912
1968
|
|
|
1913
1969
|
// src/Snippet.tsx
|
|
1914
|
-
import { useEffect as useEffect7, useRef as useRef12, useState as
|
|
1915
|
-
import { jsx as jsx21, jsxs as
|
|
1970
|
+
import { useEffect as useEffect7, useRef as useRef12, useState as useState8 } from "react";
|
|
1971
|
+
import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1916
1972
|
function Snippet({ label = "shell", code = "", reveal = false, copyLabel = "Copy" }) {
|
|
1917
|
-
const [status, setStatus] =
|
|
1973
|
+
const [status, setStatus] = useState8("idle");
|
|
1918
1974
|
const request = useRef12(0);
|
|
1919
1975
|
const timer = useRef12(void 0);
|
|
1920
1976
|
useEffect7(() => {
|
|
@@ -1937,10 +1993,10 @@ function Snippet({ label = "shell", code = "", reveal = false, copyLabel = "Copy
|
|
|
1937
1993
|
if (current === request.current) setStatus("failed");
|
|
1938
1994
|
}
|
|
1939
1995
|
}
|
|
1940
|
-
return /* @__PURE__ */
|
|
1941
|
-
/* @__PURE__ */
|
|
1996
|
+
return /* @__PURE__ */ jsxs18("div", { className: reveal ? "ui-snippet ui-snippet--reveal" : "ui-snippet", children: [
|
|
1997
|
+
/* @__PURE__ */ jsxs18("div", { className: "ui-snippet__bar", children: [
|
|
1942
1998
|
/* @__PURE__ */ jsx21("span", { children: label }),
|
|
1943
|
-
/* @__PURE__ */
|
|
1999
|
+
/* @__PURE__ */ jsxs18("button", { type: "button", className: "ui-snippet__copy", "aria-live": "polite", "aria-atomic": "true", onClick: copy, children: [
|
|
1944
2000
|
/* @__PURE__ */ jsx21(Icon, { name: status === "copied" ? "check" : "copy" }),
|
|
1945
2001
|
status === "copied" ? "Copied" : status === "failed" ? "Copy failed" : copyLabel
|
|
1946
2002
|
] })
|
|
@@ -1952,7 +2008,7 @@ function Snippet({ label = "shell", code = "", reveal = false, copyLabel = "Copy
|
|
|
1952
2008
|
// src/Tabs.tsx
|
|
1953
2009
|
import { useEffect as useEffect8, useId as useId9, useRef as useRef13 } from "react";
|
|
1954
2010
|
import { playEntrance } from "@apliteni/apliteni-ui/motion";
|
|
1955
|
-
import { Fragment as Fragment6, jsx as jsx22, jsxs as
|
|
2011
|
+
import { Fragment as Fragment6, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1956
2012
|
function Tabs({ items, value, onChange, label }) {
|
|
1957
2013
|
const id = useId9();
|
|
1958
2014
|
const previous = useRef13(value);
|
|
@@ -1962,8 +2018,8 @@ function Tabs({ items, value, onChange, label }) {
|
|
|
1962
2018
|
if (previous.current !== value) playEntrance(activePanel.current);
|
|
1963
2019
|
previous.current = value;
|
|
1964
2020
|
}, [value]);
|
|
1965
|
-
return /* @__PURE__ */
|
|
1966
|
-
/* @__PURE__ */ jsx22("div", { className: "ui-tabs__list", role: "tablist", "aria-label": label, children: items.map((item, i) => /* @__PURE__ */
|
|
2021
|
+
return /* @__PURE__ */ jsxs19("div", { className: "ui-tabs", children: [
|
|
2022
|
+
/* @__PURE__ */ jsx22("div", { className: "ui-tabs__list", role: "tablist", "aria-label": label, children: items.map((item, i) => /* @__PURE__ */ jsxs19(
|
|
1967
2023
|
"button",
|
|
1968
2024
|
{
|
|
1969
2025
|
type: "button",
|
|
@@ -2001,7 +2057,7 @@ function Tabs({ items, value, onChange, label }) {
|
|
|
2001
2057
|
},
|
|
2002
2058
|
children: [
|
|
2003
2059
|
item.label,
|
|
2004
|
-
item.count != null && /* @__PURE__ */
|
|
2060
|
+
item.count != null && /* @__PURE__ */ jsxs19(Fragment6, { children: [
|
|
2005
2061
|
" ",
|
|
2006
2062
|
item.count
|
|
2007
2063
|
] })
|
|
@@ -2029,7 +2085,7 @@ function Tabs({ items, value, onChange, label }) {
|
|
|
2029
2085
|
// src/Timeline.tsx
|
|
2030
2086
|
import { useEffect as useEffect9, useRef as useRef14 } from "react";
|
|
2031
2087
|
import { playEntrance as playEntrance2, prefersReducedMotion } from "@apliteni/apliteni-ui/motion";
|
|
2032
|
-
import { jsx as jsx23, jsxs as
|
|
2088
|
+
import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2033
2089
|
var kindIcons = { person: "user", rule: "bolt", reversal: "refresh" };
|
|
2034
2090
|
function Timeline({ events, className, ...rest }) {
|
|
2035
2091
|
const previousIds = useRef14(new Set(events.map((event) => event.id)));
|
|
@@ -2046,7 +2102,7 @@ function Timeline({ events, className, ...rest }) {
|
|
|
2046
2102
|
}
|
|
2047
2103
|
previousIds.current = new Set(events.map((event) => event.id));
|
|
2048
2104
|
}, [events]);
|
|
2049
|
-
return /* @__PURE__ */ jsx23("ol", { ...rest, role: "list", className: ["ui-timeline", events.some((event) => event.kind) && "ui-timeline--kinds", className].filter(Boolean).join(" "), children: events.map((event, index) => /* @__PURE__ */
|
|
2105
|
+
return /* @__PURE__ */ jsx23("ol", { ...rest, role: "list", className: ["ui-timeline", events.some((event) => event.kind) && "ui-timeline--kinds", className].filter(Boolean).join(" "), children: events.map((event, index) => /* @__PURE__ */ jsxs20(
|
|
2050
2106
|
"li",
|
|
2051
2107
|
{
|
|
2052
2108
|
className: "ui-timeline__event",
|
|
@@ -2060,13 +2116,13 @@ function Timeline({ events, className, ...rest }) {
|
|
|
2060
2116
|
event.kind && "ui-timeline__marker--kind",
|
|
2061
2117
|
event.kind === "reversal" && "ui-timeline__marker--reversal"
|
|
2062
2118
|
].filter(Boolean).join(" "), children: event.kind && /* @__PURE__ */ jsx23(Icon, { name: kindIcons[event.kind] }) }),
|
|
2063
|
-
/* @__PURE__ */
|
|
2119
|
+
/* @__PURE__ */ jsxs20("div", { className: "ui-timeline__head", children: [
|
|
2064
2120
|
/* @__PURE__ */ jsx23("span", { className: "ui-timeline__actor", children: event.actor }),
|
|
2065
2121
|
/* @__PURE__ */ jsx23("time", { dateTime: event.dateTime, children: event.timestamp }),
|
|
2066
2122
|
index === events.length - 1 && event.relativeTimestamp && /* @__PURE__ */ jsx23("span", { className: "ui-timeline__relative", children: event.relativeTimestamp })
|
|
2067
2123
|
] }),
|
|
2068
2124
|
/* @__PURE__ */ jsx23("p", { className: "ui-timeline__description", children: event.description }),
|
|
2069
|
-
(event.meta != null || event.undo) && /* @__PURE__ */
|
|
2125
|
+
(event.meta != null || event.undo) && /* @__PURE__ */ jsxs20("div", { className: "ui-timeline__meta", children: [
|
|
2070
2126
|
event.meta != null && /* @__PURE__ */ jsx23("span", { children: event.meta }),
|
|
2071
2127
|
event.undo && /* @__PURE__ */ jsx23(Button, { variant: "ghost", size: "xs", className: "ui-btn ui-btn--ghost ui-btn--xs ui-timeline__undo", "aria-label": event.undo.label, onClick: event.undo.onUndo, children: "Undo" })
|
|
2072
2128
|
] })
|
|
@@ -2077,15 +2133,15 @@ function Timeline({ events, className, ...rest }) {
|
|
|
2077
2133
|
}
|
|
2078
2134
|
|
|
2079
2135
|
// src/Field.tsx
|
|
2080
|
-
import { useEffect as useEffect10, useId as useId10, useRef as useRef15, useState as
|
|
2136
|
+
import { useEffect as useEffect10, useId as useId10, useRef as useRef15, useState as useState9 } from "react";
|
|
2081
2137
|
import { esc, icon as icon4 } from "@apliteni/apliteni-ui";
|
|
2082
|
-
import { jsx as jsx24, jsxs as
|
|
2138
|
+
import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2083
2139
|
function messages(id, hint, error) {
|
|
2084
2140
|
return [error && `${id}-error`, hint && `${id}-hint`].filter(Boolean).join(" ") || void 0;
|
|
2085
2141
|
}
|
|
2086
2142
|
function Frame({ id, label, hint, error, required, children }) {
|
|
2087
|
-
return /* @__PURE__ */
|
|
2088
|
-
/* @__PURE__ */
|
|
2143
|
+
return /* @__PURE__ */ jsxs21("div", { className: "ui-field", children: [
|
|
2144
|
+
/* @__PURE__ */ jsxs21("label", { className: "ui-field__label", htmlFor: id, children: [
|
|
2089
2145
|
label,
|
|
2090
2146
|
required && /* @__PURE__ */ jsx24("span", { className: "ui-field__req", "aria-hidden": "true", children: "*" })
|
|
2091
2147
|
] }),
|
|
@@ -2121,7 +2177,7 @@ function TextField({ label, hint, error, required, type = "text", unit, classNam
|
|
|
2121
2177
|
className: `ui-input${error ? " is-invalid" : ""} ${className}`
|
|
2122
2178
|
}
|
|
2123
2179
|
);
|
|
2124
|
-
return /* @__PURE__ */ jsx24(Frame, { ...{ id, label, hint, error, required }, children: hasUnit ? /* @__PURE__ */
|
|
2180
|
+
return /* @__PURE__ */ jsx24(Frame, { ...{ id, label, hint, error, required }, children: hasUnit ? /* @__PURE__ */ jsxs21("div", { className: "ui-field-number", children: [
|
|
2125
2181
|
control,
|
|
2126
2182
|
/* @__PURE__ */ jsx24("span", { id: `${id}-unit`, className: "ui-field-number__unit", children: unit })
|
|
2127
2183
|
] }) : control });
|
|
@@ -2155,8 +2211,8 @@ function SelectField({ label, hint, error, required, className = "", children, .
|
|
|
2155
2211
|
function FileField({ label, hint, error, required, disabled, onFileChange, className = "", ...props }) {
|
|
2156
2212
|
const id = useId10();
|
|
2157
2213
|
const input = useRef15(null);
|
|
2158
|
-
const [file, setFile] =
|
|
2159
|
-
const [dragging, setDragging] =
|
|
2214
|
+
const [file, setFile] = useState9(null);
|
|
2215
|
+
const [dragging, setDragging] = useState9(false);
|
|
2160
2216
|
useEffect10(() => {
|
|
2161
2217
|
const form = input.current?.form;
|
|
2162
2218
|
const reset = () => {
|
|
@@ -2170,7 +2226,7 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
|
|
|
2170
2226
|
setFile(next);
|
|
2171
2227
|
onFileChange?.(next);
|
|
2172
2228
|
};
|
|
2173
|
-
return /* @__PURE__ */ jsx24(Frame, { ...{ id, label, hint, error, required }, children: /* @__PURE__ */
|
|
2229
|
+
return /* @__PURE__ */ jsx24(Frame, { ...{ id, label, hint, error, required }, children: /* @__PURE__ */ jsxs21(
|
|
2174
2230
|
"div",
|
|
2175
2231
|
{
|
|
2176
2232
|
className: `ui-file${dragging && !disabled ? " is-dragging" : ""}${file ? " has-file" : ""}${error ? " is-invalid" : ""}${disabled ? " is-disabled" : ""}`,
|
|
@@ -2194,7 +2250,7 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
|
|
|
2194
2250
|
/* @__PURE__ */ jsx24("span", { className: "ui-file__glyph", children: /* @__PURE__ */ jsx24(Icon, { name: "upload" }) }),
|
|
2195
2251
|
/* @__PURE__ */ jsx24("div", { "aria-live": "polite", children: file ? file.name : "Drop a file here" }),
|
|
2196
2252
|
/* @__PURE__ */ jsx24("div", { children: "or choose one from your device" }),
|
|
2197
|
-
/* @__PURE__ */
|
|
2253
|
+
/* @__PURE__ */ jsxs21("span", { className: "ui-file__choose", children: [
|
|
2198
2254
|
/* @__PURE__ */ jsx24("span", { className: "ui-btn ui-btn--secondary ui-btn--sm", "aria-hidden": "true", "aria-disabled": disabled || void 0, children: file ? "Replace file" : "Choose file" }),
|
|
2199
2255
|
/* @__PURE__ */ jsx24(
|
|
2200
2256
|
"input",
|
|
@@ -2216,9 +2272,9 @@ function FileField({ label, hint, error, required, disabled, onFileChange, class
|
|
|
2216
2272
|
}
|
|
2217
2273
|
|
|
2218
2274
|
// src/Toast.tsx
|
|
2219
|
-
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect11, useLayoutEffect as
|
|
2275
|
+
import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect11, useLayoutEffect as useLayoutEffect4, useRef as useRef16, useState as useState10 } from "react";
|
|
2220
2276
|
import { createPortal as createPortal4 } from "react-dom";
|
|
2221
|
-
import { jsx as jsx25, jsxs as
|
|
2277
|
+
import { jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2222
2278
|
var Context = createContext2(null);
|
|
2223
2279
|
var glyphs = { success: "circleCheck", danger: "circleX", warn: "circleAlert", info: "info", neutral: "bolt" };
|
|
2224
2280
|
function useToast() {
|
|
@@ -2228,11 +2284,11 @@ function useToast() {
|
|
|
2228
2284
|
}
|
|
2229
2285
|
function Notice({ notice, remove }) {
|
|
2230
2286
|
const root = useRef16(null);
|
|
2231
|
-
const [leaving2, setLeaving] =
|
|
2287
|
+
const [leaving2, setLeaving] = useState10(false);
|
|
2232
2288
|
const dismissed = useRef16(false);
|
|
2233
2289
|
const remaining = useRef16(5e3);
|
|
2234
|
-
const [hovered, setHovered] =
|
|
2235
|
-
const [focused, setFocused] =
|
|
2290
|
+
const [hovered, setHovered] = useState10(false);
|
|
2291
|
+
const [focused, setFocused] = useState10(false);
|
|
2236
2292
|
const paused = hovered || focused;
|
|
2237
2293
|
const tone = notice.tone ?? "success";
|
|
2238
2294
|
const dismiss = useCallback3(() => {
|
|
@@ -2264,7 +2320,7 @@ function Notice({ notice, remove }) {
|
|
|
2264
2320
|
remaining.current = Math.max(0, remaining.current - (Date.now() - started));
|
|
2265
2321
|
};
|
|
2266
2322
|
}, [dismiss, leaving2, notice.action, paused]);
|
|
2267
|
-
return /* @__PURE__ */
|
|
2323
|
+
return /* @__PURE__ */ jsxs22(
|
|
2268
2324
|
"div",
|
|
2269
2325
|
{
|
|
2270
2326
|
ref: root,
|
|
@@ -2279,7 +2335,7 @@ function Notice({ notice, remove }) {
|
|
|
2279
2335
|
},
|
|
2280
2336
|
children: [
|
|
2281
2337
|
/* @__PURE__ */ jsx25("span", { className: "ui-toast__icon", children: /* @__PURE__ */ jsx25(Icon, { name: glyphs[tone] }) }),
|
|
2282
|
-
/* @__PURE__ */
|
|
2338
|
+
/* @__PURE__ */ jsxs22("div", { className: "ui-toast__body", children: [
|
|
2283
2339
|
/* @__PURE__ */ jsx25("div", { className: "ui-toast__title", children: notice.title }),
|
|
2284
2340
|
notice.text && /* @__PURE__ */ jsx25("div", { className: "ui-toast__text", children: notice.text })
|
|
2285
2341
|
] }),
|
|
@@ -2313,7 +2369,7 @@ function Notice({ notice, remove }) {
|
|
|
2313
2369
|
}
|
|
2314
2370
|
var REACH = "--rx-toast-stack";
|
|
2315
2371
|
function usePublishedReach(stack2, count) {
|
|
2316
|
-
|
|
2372
|
+
useLayoutEffect4(() => {
|
|
2317
2373
|
const element = stack2.current;
|
|
2318
2374
|
const view = element?.ownerDocument.defaultView;
|
|
2319
2375
|
const root = element?.ownerDocument.documentElement;
|
|
@@ -2332,7 +2388,7 @@ function usePublishedReach(stack2, count) {
|
|
|
2332
2388
|
}, [stack2, count]);
|
|
2333
2389
|
}
|
|
2334
2390
|
function Toast({ children }) {
|
|
2335
|
-
const [entries, setEntries] =
|
|
2391
|
+
const [entries, setEntries] = useState10([]);
|
|
2336
2392
|
const nextId = useRef16(0);
|
|
2337
2393
|
const stack2 = useRef16(null);
|
|
2338
2394
|
const push = useCallback3((notice) => {
|
|
@@ -2341,7 +2397,7 @@ function Toast({ children }) {
|
|
|
2341
2397
|
setEntries((current) => [...current, { id, notice, remove }]);
|
|
2342
2398
|
}, []);
|
|
2343
2399
|
usePublishedReach(stack2, entries.length);
|
|
2344
|
-
return /* @__PURE__ */
|
|
2400
|
+
return /* @__PURE__ */ jsxs22(Context.Provider, { value: push, children: [
|
|
2345
2401
|
children,
|
|
2346
2402
|
typeof document !== "undefined" && createPortal4(
|
|
2347
2403
|
/* @__PURE__ */ jsx25("div", { className: "ui-toast-stack rx-toast-stack", ref: stack2, children: entries.map((entry) => /* @__PURE__ */ jsx25(Notice, { notice: entry.notice, remove: entry.remove }, entry.id)) }),
|
|
@@ -2351,9 +2407,9 @@ function Toast({ children }) {
|
|
|
2351
2407
|
}
|
|
2352
2408
|
|
|
2353
2409
|
// src/AppShell.tsx
|
|
2354
|
-
import { useEffect as useEffect12, useId as useId11, useState as
|
|
2410
|
+
import { useEffect as useEffect12, useId as useId11, useState as useState11 } from "react";
|
|
2355
2411
|
import { applyTheme, paletteHotkey, prism, railCollapsed, themeIcon, themeName } from "@apliteni/apliteni-ui";
|
|
2356
|
-
import { Fragment as Fragment7, jsx as jsx26, jsxs as
|
|
2412
|
+
import { Fragment as Fragment7, jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2357
2413
|
var path = (value) => value.split(/[?#]/)[0].replace(/\/+$/, "") || "/";
|
|
2358
2414
|
function AppShell({
|
|
2359
2415
|
sections,
|
|
@@ -2375,10 +2431,10 @@ function AppShell({
|
|
|
2375
2431
|
renderLink
|
|
2376
2432
|
}) {
|
|
2377
2433
|
const uid = useId11();
|
|
2378
|
-
const [collapsed, setCollapsed] =
|
|
2379
|
-
const [search, setSearch] =
|
|
2380
|
-
const [more, setMore] =
|
|
2381
|
-
const [theme, setTheme] =
|
|
2434
|
+
const [collapsed, setCollapsed] = useState11(defaultCollapsed);
|
|
2435
|
+
const [search, setSearch] = useState11(false);
|
|
2436
|
+
const [more, setMore] = useState11(false);
|
|
2437
|
+
const [theme, setTheme] = useState11("dark");
|
|
2382
2438
|
const current = path(pathname);
|
|
2383
2439
|
const active = sections.filter((section) => current === path(section.href) || path(section.href) !== "/" && current.startsWith(`${path(section.href)}/`)).sort((a, b) => path(b.href).length - path(a.href).length)[0];
|
|
2384
2440
|
useEffect12(() => {
|
|
@@ -2424,7 +2480,7 @@ function AppShell({
|
|
|
2424
2480
|
onClick: (event) => {
|
|
2425
2481
|
if (!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) setMore(false);
|
|
2426
2482
|
},
|
|
2427
|
-
children: /* @__PURE__ */
|
|
2483
|
+
children: /* @__PURE__ */ jsxs23(Fragment7, { children: [
|
|
2428
2484
|
/* @__PURE__ */ jsx26("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsx26(Icon, { name: section.icon }) }),
|
|
2429
2485
|
/* @__PURE__ */ jsx26("span", { className: "ui-nav__label", children: section.label }),
|
|
2430
2486
|
section.count != null && /* @__PURE__ */ jsx26("span", { className: "ui-nav__badge", children: section.count })
|
|
@@ -2436,24 +2492,24 @@ function AppShell({
|
|
|
2436
2492
|
const visible = sections.length > 4 ? sections.slice(0, 3) : sections;
|
|
2437
2493
|
const remaining = sections.slice(visible.length);
|
|
2438
2494
|
const initials = account.name.trim().split(/\s+/).map((part) => part[0]).slice(0, 2).join("");
|
|
2439
|
-
return /* @__PURE__ */
|
|
2440
|
-
/* @__PURE__ */
|
|
2441
|
-
/* @__PURE__ */ jsx26("div", { className: "ui-app__head", children: /* @__PURE__ */
|
|
2495
|
+
return /* @__PURE__ */ jsxs23("div", { className: `ui-app ui-app--topbar ui-react-app${collapsed ? " is-collapsed" : ""}`, children: [
|
|
2496
|
+
/* @__PURE__ */ jsxs23("div", { className: "ui-app__rail", children: [
|
|
2497
|
+
/* @__PURE__ */ jsx26("div", { className: "ui-app__head", children: /* @__PURE__ */ jsxs23("a", { className: "ui-app__brand", href: brandHref, "aria-label": word, children: [
|
|
2442
2498
|
brand ?? /* @__PURE__ */ jsx26("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(uid, 24) } }),
|
|
2443
2499
|
/* @__PURE__ */ jsx26("span", { children: word })
|
|
2444
2500
|
] }) }),
|
|
2445
2501
|
/* @__PURE__ */ jsx26("nav", { className: "ui-nav ui-nav--side", "aria-label": "Sections", children: /* @__PURE__ */ jsx26("ul", { className: "ui-nav__list", children: links(sections) }) }),
|
|
2446
|
-
/* @__PURE__ */ jsx26("div", { className: "ui-app__foot", children: /* @__PURE__ */ jsx26("div", { className: "ui-app__fold-row", children: /* @__PURE__ */
|
|
2447
|
-
/* @__PURE__ */ jsx26("span", { className: "ui-nav__ic", children: /* @__PURE__ */
|
|
2502
|
+
/* @__PURE__ */ jsx26("div", { className: "ui-app__foot", children: /* @__PURE__ */ jsx26("div", { className: "ui-app__fold-row", children: /* @__PURE__ */ jsxs23("button", { type: "button", className: "ui-nav__item ui-app__fold", "aria-expanded": !collapsed, "aria-label": foldLabel, onClick: fold, children: [
|
|
2503
|
+
/* @__PURE__ */ jsx26("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsxs23("svg", { className: "ui-icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
|
|
2448
2504
|
/* @__PURE__ */ jsx26("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
2449
2505
|
/* @__PURE__ */ jsx26("path", { className: "ui-app__fold-seam", d: "M9 3v18" })
|
|
2450
2506
|
] }) }),
|
|
2451
2507
|
/* @__PURE__ */ jsx26("span", { className: "ui-nav__label", children: foldLabel })
|
|
2452
2508
|
] }) }) })
|
|
2453
2509
|
] }),
|
|
2454
|
-
/* @__PURE__ */
|
|
2455
|
-
/* @__PURE__ */
|
|
2456
|
-
/* @__PURE__ */
|
|
2510
|
+
/* @__PURE__ */ jsxs23("div", { className: "ui-app__well", children: [
|
|
2511
|
+
/* @__PURE__ */ jsxs23("header", { className: "ui-app__bar", children: [
|
|
2512
|
+
/* @__PURE__ */ jsxs23("button", { type: "button", className: "ui-app__search", "aria-haspopup": "dialog", onClick: () => setSearch(true), children: [
|
|
2457
2513
|
/* @__PURE__ */ jsx26("span", { className: "ui-app__search-ic", children: /* @__PURE__ */ jsx26(Icon, { name: "search" }) }),
|
|
2458
2514
|
/* @__PURE__ */ jsx26("span", { className: "ui-app__search-txt", children: "Search or run a command\u2026" }),
|
|
2459
2515
|
/* @__PURE__ */ jsx26("kbd", { className: "ui-cmdk__key", children: paletteHotkey() })
|
|
@@ -2471,7 +2527,7 @@ function AppShell({
|
|
|
2471
2527
|
triggerClass: "ui-app__user-trigger",
|
|
2472
2528
|
panelClass: "ui-app__user-panel",
|
|
2473
2529
|
triggerContent: /* @__PURE__ */ jsx26("span", { className: "ui-app__av", "aria-hidden": "true", children: initials }),
|
|
2474
|
-
header: /* @__PURE__ */
|
|
2530
|
+
header: /* @__PURE__ */ jsxs23("div", { className: "ui-dropdown__head", children: [
|
|
2475
2531
|
/* @__PURE__ */ jsx26("b", { children: account.name }),
|
|
2476
2532
|
/* @__PURE__ */ jsx26("span", { children: account.email })
|
|
2477
2533
|
] }),
|
|
@@ -2480,7 +2536,7 @@ function AppShell({
|
|
|
2480
2536
|
}
|
|
2481
2537
|
) })
|
|
2482
2538
|
] }),
|
|
2483
|
-
/* @__PURE__ */
|
|
2539
|
+
/* @__PURE__ */ jsxs23("main", { className: `ui-app__main${width === "wide" ? " ui-app__main--wide" : ""}`, children: [
|
|
2484
2540
|
back && /* @__PURE__ */ jsx26(BackLink, { ...back }),
|
|
2485
2541
|
/* @__PURE__ */ jsx26("h1", { children: title }),
|
|
2486
2542
|
lede && /* @__PURE__ */ jsx26("p", { className: "ui-app__sub", children: lede }),
|
|
@@ -2488,9 +2544,9 @@ function AppShell({
|
|
|
2488
2544
|
/* @__PURE__ */ jsx26("div", { className: "ui-app__body", children })
|
|
2489
2545
|
] })
|
|
2490
2546
|
] }),
|
|
2491
|
-
/* @__PURE__ */ jsx26("nav", { className: "ui-nav ui-react-app__bottom", "aria-label": "Sections on mobile", children: /* @__PURE__ */
|
|
2547
|
+
/* @__PURE__ */ jsx26("nav", { className: "ui-nav ui-react-app__bottom", "aria-label": "Sections on mobile", children: /* @__PURE__ */ jsxs23("ul", { className: "ui-nav__list", children: [
|
|
2492
2548
|
links(visible),
|
|
2493
|
-
remaining.length > 0 && /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */
|
|
2549
|
+
remaining.length > 0 && /* @__PURE__ */ jsx26("li", { children: /* @__PURE__ */ jsxs23(
|
|
2494
2550
|
"button",
|
|
2495
2551
|
{
|
|
2496
2552
|
type: "button",
|
|
@@ -2512,7 +2568,7 @@ function AppShell({
|
|
|
2512
2568
|
|
|
2513
2569
|
// src/EmptyState.tsx
|
|
2514
2570
|
import { icon as kitIcon2, illo } from "@apliteni/apliteni-ui";
|
|
2515
|
-
import { jsx as jsx27, jsxs as
|
|
2571
|
+
import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2516
2572
|
var cases = {
|
|
2517
2573
|
"first-run": { title: "Nothing needs you yet", sub: "New items will appear here when they need your attention.", icon: "check" },
|
|
2518
2574
|
"no-matches": { title: "No results in this view", sub: "Try another search or clear your filters.", icon: "search" },
|
|
@@ -2524,7 +2580,7 @@ function EmptyState({ variant = "first-run", title, sub, icon: icon6, art, actio
|
|
|
2524
2580
|
const heading = title ?? preset.title;
|
|
2525
2581
|
const guidance = sub ?? preset.sub;
|
|
2526
2582
|
const glyph = icon6 ?? preset.icon;
|
|
2527
|
-
return /* @__PURE__ */
|
|
2583
|
+
return /* @__PURE__ */ jsxs24("div", { className: "ui-empty", children: [
|
|
2528
2584
|
art ? typeof art === "string" ? /* @__PURE__ */ jsx27("div", { className: "ui-empty__art", dangerouslySetInnerHTML: { __html: /<svg/.test(art) ? art : illo(art) } }) : /* @__PURE__ */ jsx27("div", { className: "ui-empty__art", "aria-hidden": "true", children: art }) : glyph ? /* @__PURE__ */ jsx27("div", { className: "ui-empty__icon", dangerouslySetInnerHTML: { __html: kitIcon2(glyph) } }) : null,
|
|
2529
2585
|
heading && /* @__PURE__ */ jsx27("div", { className: "ui-empty__title", children: heading }),
|
|
2530
2586
|
guidance && /* @__PURE__ */ jsx27("div", { className: "ui-empty__sub", children: guidance }),
|
|
@@ -2535,7 +2591,7 @@ function EmptyState({ variant = "first-run", title, sub, icon: icon6, art, actio
|
|
|
2535
2591
|
// src/ThemeToggle.tsx
|
|
2536
2592
|
import { useSyncExternalStore } from "react";
|
|
2537
2593
|
import { icon as icon5 } from "@apliteni/apliteni-ui";
|
|
2538
|
-
import { jsx as jsx28, jsxs as
|
|
2594
|
+
import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2539
2595
|
var STORAGE_KEY = "apliteni-strategy-theme";
|
|
2540
2596
|
var CHOICE_EVENT = "apliteni-theme-choice";
|
|
2541
2597
|
var THEME_INIT_SCRIPT = `(()=>{let t;try{t=localStorage.getItem('apliteni-strategy-theme')}catch{}document.documentElement.setAttribute('data-theme',t==='dark'||t==='light'?t:window.matchMedia?.('(prefers-color-scheme: light)').matches?'light':'dark')})()`;
|
|
@@ -2594,9 +2650,9 @@ function ThemeToggle({ labelled = false }) {
|
|
|
2594
2650
|
window.dispatchEvent(new Event(CHOICE_EVENT));
|
|
2595
2651
|
};
|
|
2596
2652
|
const button = /* @__PURE__ */ jsx28("button", { type: "button", className: "toggle", "aria-label": name, title: name, onClick: toggle, children: /* @__PURE__ */ jsx28("span", { className: "ic", "aria-hidden": "true", dangerouslySetInnerHTML: { __html: icon5(theme === "auto" ? "monitor" : theme === "light" ? "sun" : "moon") } }) });
|
|
2597
|
-
return labelled ? /* @__PURE__ */
|
|
2653
|
+
return labelled ? /* @__PURE__ */ jsxs25("span", { className: "ui-theme-toggle", children: [
|
|
2598
2654
|
button,
|
|
2599
|
-
/* @__PURE__ */
|
|
2655
|
+
/* @__PURE__ */ jsxs25("span", { children: [
|
|
2600
2656
|
label,
|
|
2601
2657
|
" theme"
|
|
2602
2658
|
] })
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SCORE, scoreCommand, rankCommands, rankGroups, paletteHotkey, norm } from '../logic/command-palette.js';
|
|
2
|
+
export { SCORE, scoreCommand, rankCommands, rankGroups, paletteHotkey } from '../logic/command-palette.js';
|
|
1
3
|
// Command palette — one overlay, a text box and a ranked list of things to run
|
|
2
4
|
// or go to.
|
|
3
5
|
//
|
|
@@ -16,134 +18,6 @@ const cx = (...a) => a.filter(Boolean).join(' ');
|
|
|
16
18
|
let _uid = 0;
|
|
17
19
|
const nextId = (p = 'cmdk') => `${p}-${++_uid}`;
|
|
18
20
|
|
|
19
|
-
/**
|
|
20
|
-
* How a match is worth more than another match.
|
|
21
|
-
*
|
|
22
|
-
* Six ways a query can meet an item, scored so that the whole beats the part
|
|
23
|
-
* and the name beats the note. The numbers are a ladder and not a measurement —
|
|
24
|
-
* what matters is the order, which is why src/components/command-palette.test.js
|
|
25
|
-
* asserts the ORDER of the results rather than any of these values.
|
|
26
|
-
*
|
|
27
|
-
* cmdk's command-score is the other published answer: one continuous score
|
|
28
|
-
* built out of SCORE_CONTINUE_MATCH 1, SCORE_SPACE_WORD_JUMP 0.9,
|
|
29
|
-
* SCORE_NON_SPACE_WORD_JUMP 0.8 and SCORE_CHARACTER_JUMP 0.17. It ranks
|
|
30
|
-
* beautifully and cannot be explained to a reader who asks why their item is
|
|
31
|
-
* third. A ladder can: the kit's palettes hold tens of items, not thousands.
|
|
32
|
-
*/
|
|
33
|
-
export const SCORE = {
|
|
34
|
-
exact: 100,
|
|
35
|
-
prefix: 90,
|
|
36
|
-
wordStart: 80,
|
|
37
|
-
contains: 70,
|
|
38
|
-
keyword: 60,
|
|
39
|
-
description: 40,
|
|
40
|
-
subsequence: 20,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const norm = (s) => String(s == null ? '' : s).toLowerCase().replace(/\s+/g, ' ').trim();
|
|
44
|
-
|
|
45
|
-
// Every character of `q`, in order, somewhere in `s` — "nc" finding "New
|
|
46
|
-
// campaign". The weakest match the kit accepts, and the only one that can pair
|
|
47
|
-
// a two-letter query with a twenty-letter label.
|
|
48
|
-
function subsequence(s, q) {
|
|
49
|
-
let at = 0;
|
|
50
|
-
for (const ch of q) {
|
|
51
|
-
at = s.indexOf(ch, at);
|
|
52
|
-
if (at === -1) return false;
|
|
53
|
-
at += 1;
|
|
54
|
-
}
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// A word here starts after a space or one of the separators a product name uses,
|
|
59
|
-
// so "camp" reaches the second word of "New campaign" and the "utm" in
|
|
60
|
-
// "url-utm_source".
|
|
61
|
-
const wordStarts = (s) => s.split(/[\s/\-_.:,]+/).filter(Boolean);
|
|
62
|
-
|
|
63
|
-
function scoreField({ label, description, keywords }, q) {
|
|
64
|
-
const name = norm(label);
|
|
65
|
-
if (!name && !description && !(keywords || []).length) return 0;
|
|
66
|
-
if (name === q) return SCORE.exact;
|
|
67
|
-
if (name.startsWith(q)) return SCORE.prefix;
|
|
68
|
-
if (wordStarts(name).some((w) => w.startsWith(q))) return SCORE.wordStart;
|
|
69
|
-
if (name.includes(q)) return SCORE.contains;
|
|
70
|
-
for (const k of keywords || []) {
|
|
71
|
-
const key = norm(k);
|
|
72
|
-
if (key === q || key.startsWith(q)) return SCORE.keyword;
|
|
73
|
-
}
|
|
74
|
-
if (norm(description).includes(q)) return SCORE.description;
|
|
75
|
-
if (subsequence(name, q)) return SCORE.subsequence;
|
|
76
|
-
return 0;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* What one item is worth against one query. 0 means it is not a result.
|
|
81
|
-
*
|
|
82
|
-
* An empty query scores everything the same, so a palette nobody has typed into
|
|
83
|
-
* shows what the caller passed, in the caller's order — which is where a
|
|
84
|
-
* product puts the four things somebody actually does here.
|
|
85
|
-
*
|
|
86
|
-
* A query with a space in it has to match as a whole OR token by token, every
|
|
87
|
-
* token landing somewhere: "new camp" reaches "New campaign" as a whole, and
|
|
88
|
-
* "camp new" reaches it one token at a time. The weakest token decides, because
|
|
89
|
-
* a result is only as good as the part of the query it answers worst.
|
|
90
|
-
*/
|
|
91
|
-
export function scoreCommand(item, query) {
|
|
92
|
-
const q = norm(query);
|
|
93
|
-
if (!q) return 1;
|
|
94
|
-
const whole = scoreField(item, q);
|
|
95
|
-
const tokens = q.split(' ');
|
|
96
|
-
if (tokens.length < 2) return whole;
|
|
97
|
-
let weakest = Infinity;
|
|
98
|
-
for (const t of tokens) {
|
|
99
|
-
const s = scoreField(item, t);
|
|
100
|
-
if (!s) { weakest = 0; break; }
|
|
101
|
-
weakest = Math.min(weakest, s);
|
|
102
|
-
}
|
|
103
|
-
return Math.max(whole, weakest === Infinity ? 0 : weakest);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* The items that answer `query`, best first.
|
|
108
|
-
*
|
|
109
|
-
* Ties keep the caller's order — that is the whole of the kit's ordering
|
|
110
|
-
* opinion, and it is what lets a product put its four most-used commands at the
|
|
111
|
-
* top of the list it passes and have them stay there.
|
|
112
|
-
*/
|
|
113
|
-
export function rankCommands(items, query) {
|
|
114
|
-
return (items || [])
|
|
115
|
-
.map((item, i) => ({ item, i, score: scoreCommand(item, query) }))
|
|
116
|
-
.filter((r) => r.score > 0)
|
|
117
|
-
.sort((a, b) => b.score - a.score || a.i - b.i)
|
|
118
|
-
.map((r) => r.item);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* The groups that answer `query`, best group first, each holding its own ranked
|
|
123
|
-
* rows and nothing that scored zero.
|
|
124
|
-
*
|
|
125
|
-
* A group is carried by its best row, and the caller's order breaks the tie at
|
|
126
|
-
* both levels — so a palette nobody has typed into is exactly the list that was
|
|
127
|
-
* passed in. cmdk ranks groups the same way; the tie-break is the kit's, and it
|
|
128
|
-
* is what keeps a heading from moving under a reader who has typed nothing.
|
|
129
|
-
*/
|
|
130
|
-
export function rankGroups(groups, query) {
|
|
131
|
-
return (groups || [])
|
|
132
|
-
.map((g, i) => {
|
|
133
|
-
const items = rankCommands(g.items || [], query);
|
|
134
|
-
const best = items.length ? scoreCommand(items[0], query) : 0;
|
|
135
|
-
return { group: { ...g, items }, i, best };
|
|
136
|
-
})
|
|
137
|
-
.filter((g) => g.group.items.length > 0)
|
|
138
|
-
.sort((a, b) => b.best - a.best || a.i - b.i)
|
|
139
|
-
.map((g) => g.group);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** The label for the key that opens it, on the platform the caller is on. */
|
|
143
|
-
export function paletteHotkey(platform = typeof navigator === 'undefined' ? '' : navigator.platform) {
|
|
144
|
-
return /mac|iphone|ipad|ipod/i.test(String(platform || '')) ? '⌘K' : 'Ctrl K';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
21
|
// The three fields a query is matched against, written onto the row so the
|
|
148
22
|
// wiring can re-rank what is already rendered without being handed the data a
|
|
149
23
|
// second time. Three attributes and not one blob: the ranking weighs a name
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { dropdownMatch, dropdownFiltering } from '../logic/dropdown.js';
|
|
2
|
+
export { dropdownMatch, dropdownFiltering } from '../logic/dropdown.js';
|
|
1
3
|
// Dropdown — the kit's one popover-list primitive. A trigger opens a panel of
|
|
2
4
|
// item rows; two flavours share the same panel and the same open/close JS:
|
|
3
5
|
//
|
|
@@ -79,22 +81,6 @@ function ddBody({ items, sections }, listbox, sx) {
|
|
|
79
81
|
// why: docs/specification.md#a-dropdown-with-a-search-field
|
|
80
82
|
// NFD takes the mark off é or ö; ł, ø, đ and the rest are letters of their own
|
|
81
83
|
// with nothing to take off, so they are mapped by hand.
|
|
82
|
-
const FOLD = { ł: 'l', ø: 'o', đ: 'd', ð: 'd', ß: 'ss', æ: 'ae', œ: 'oe', ı: 'i', þ: 'th' };
|
|
83
|
-
const fold = (s) => String(s == null ? '' : s).normalize('NFD').replace(/\p{M}/gu, '').toLowerCase()
|
|
84
|
-
.replace(/[łøđðßæœıþ]/g, (c) => FOLD[c]);
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Is this query narrowing anything? A query of spaces, or of marks with no letter
|
|
88
|
-
* under them, is not — it leaves every row showing, and the separators with them.
|
|
89
|
-
*
|
|
90
|
-
* Public because a second implementation of this dropdown cannot write its own: the
|
|
91
|
-
* React <Dropdown> asks these two, the way <CommandPalette> asks rankGroups(), so a
|
|
92
|
-
* list a server rendered and the same list after a keystroke hide the same rows.
|
|
93
|
-
*/
|
|
94
|
-
export const dropdownFiltering = (query) => fold(query).trim() !== '';
|
|
95
|
-
|
|
96
|
-
/** Does `label` match `query`? A substring, anywhere, ignoring case and accents. */
|
|
97
|
-
export const dropdownMatch = (label, query) => fold(label).includes(fold(query).trim());
|
|
98
84
|
const ddIsRow = (it) => it && it !== '---' && !it.separator;
|
|
99
85
|
|
|
100
86
|
let _ddSeq = 0;
|
package/src/components/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { calloutIcons } from '../logic/callout.js';
|
|
2
|
+
export { calloutIcons } from '../logic/callout.js';
|
|
1
3
|
// apliteni-ui component factories — each returns an HTML string, matching the
|
|
2
4
|
// viz/ server-render idiom so the portal can adopt them with no framework.
|
|
3
5
|
import { icon } from '../assets/icons.js';
|
|
@@ -218,7 +220,7 @@ export function switchToggle({ checked = false, disabled = false, name, label =
|
|
|
218
220
|
}
|
|
219
221
|
|
|
220
222
|
// ---- Callout / toast / success ------------------------------------------
|
|
221
|
-
|
|
223
|
+
|
|
222
224
|
|
|
223
225
|
export function callout({ variant, icon: ic = calloutIcons[variant] || 'info', body, actions } = {}) {
|
|
224
226
|
return `<div class="${esc(cx('ui-callout', variant && variant !== 'neutral' && `ui-callout--${variant}`))}"><span class="ui-callout__icon">${icon(ic)}</span><div class="ui-callout__body">${body}${actions != null ? `<div class="ui-toolbar ui-callout__actions">${actions}</div>` : ''}</div></div>`;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PAGE_SIZES, DEFAULT_PAGE_SIZE } from '../logic/pagination.js';
|
|
2
|
+
export { PAGE_SIZES, DEFAULT_PAGE_SIZE } from '../logic/pagination.js';
|
|
1
3
|
// Pagination — the strip under a table or a list, as an HTML string.
|
|
2
4
|
//
|
|
3
5
|
// It renders a page the CALLER computed. Rows never come in here: `page`,
|
|
@@ -15,8 +17,7 @@ import { safeUrl } from '../html.js';
|
|
|
15
17
|
// AG Grid defaults to 100, and the finance portal's two data-heavy surfaces
|
|
16
18
|
// already page at 100. A 250 step was in the first draft of this component and
|
|
17
19
|
// no surveyed kit offers one, so it went rather than being invented.
|
|
18
|
-
|
|
19
|
-
export const DEFAULT_PAGE_SIZE = 100;
|
|
20
|
+
|
|
20
21
|
|
|
21
22
|
// The classes button({ variant: 'ghost', size: 'sm' }) emits, written out because
|
|
22
23
|
// button() takes no extra class and every control here needs one of its own for
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
if (key === 'ArrowRight' || key === 'ArrowDown') return (index + 1) % length;
|
|
4
|
-
if (key === 'ArrowLeft' || key === 'ArrowUp') return (index - 1 + length) % length;
|
|
5
|
-
if (key === 'Home') return 0;
|
|
6
|
-
if (key === 'End') return length - 1;
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
|
|
1
|
+
import { segmentedNextIndex } from '../logic/segmented.js';
|
|
2
|
+
export { segmentedNextIndex } from '../logic/segmented.js';
|
|
10
3
|
export function initSegmented(root = document) {
|
|
11
4
|
const cleanups = [];
|
|
12
5
|
root.querySelectorAll('[data-seg]').forEach(group => {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { formatNumericValue, formatDeltaValue } from '../logic/table-values.js';
|
|
1
2
|
import { esc } from './index.js';
|
|
2
3
|
import { safeUrl } from '../html.js';
|
|
3
|
-
export function numericValue(
|
|
4
|
-
|
|
5
|
-
return `<span class="ui-value">${esc(value)}${unit ? `<span class="ui-value__unit">${esc(unit)}</span>` : ''}</span>`;
|
|
4
|
+
export function numericValue(options = {}) {
|
|
5
|
+
const value = formatNumericValue(options);
|
|
6
|
+
return `<span class="ui-value"${value.missing !== undefined ? ` aria-label="${esc(value.missing)}"` : ''}>${esc(value.text)}${value.unit ? `<span class="ui-value__unit">${esc(value.unit)}</span>` : ''}</span>`;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
export function deltaValue(
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
return `<span class="ui-delta${judged ? ` ui-delta--${tone}` : ''}"${basisId ? ` aria-describedby="${esc(basisId)}"` : ''}>${esc(present ? value : missing)}</span>`;
|
|
9
|
+
export function deltaValue(options = {}) {
|
|
10
|
+
const value = formatDeltaValue(options);
|
|
11
|
+
return `<span class="${value.className}"${value.basisId ? ` aria-describedby="${esc(value.basisId)}"` : ''}>${esc(value.text)}</span>`;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function rowIdentity({ symbol = '', name = '', logo, href } = {}) {
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export declare function esc(value: unknown): string;
|
|
2
|
+
export declare function field(opts?: Record<string, unknown>): string;
|
|
3
|
+
export declare function input(opts?: Record<string, unknown>): string;
|
|
4
|
+
export declare function textarea(opts?: Record<string, unknown>): string;
|
|
5
|
+
export declare function select(opts?: Record<string, unknown>): string;
|
|
6
|
+
export declare function numericValue(opts?: Record<string, unknown>): string;
|
|
7
|
+
export declare function deltaValue(opts?: Record<string, unknown>): string;
|
|
8
|
+
export declare function filterBar(opts?: Record<string, unknown>): string;
|
|
9
|
+
export declare function initFilterBar(host: Element, opts?: Record<string, unknown>): { update: (opts: Record<string, unknown>) => void; destroy: () => void };
|
|
10
|
+
export declare function segmentedNextIndex(key: string, index: number, length: number): number | null;
|
|
11
|
+
export declare function icon(name: string, className?: string): string;
|
|
12
|
+
export declare function snippet(opts?: Record<string, unknown>): string;
|
|
13
|
+
export declare function button(opts?: Record<string, unknown>): string;
|
|
14
|
+
export declare function badge(label: string, variant?: string): string;
|
|
15
|
+
export declare const calloutIcons: Record<'neutral' | 'info' | 'success' | 'warn' | 'danger', string>;
|
|
16
|
+
export declare function callout(opts?: Record<string, unknown>): string;
|
|
17
|
+
export declare function card(opts?: Record<string, unknown>): string;
|
|
18
|
+
export declare function pagination(opts?: Record<string, unknown>): string;
|
|
19
|
+
export declare function drawerSection(opts?: Record<string, unknown>): string;
|
|
20
|
+
export declare function drawer(opts?: Record<string, unknown>): string;
|
|
21
|
+
export declare function dropdown(opts?: Record<string, unknown>): string;
|
|
22
|
+
export declare function wireDropdown(root?: Document | Element): void;
|
|
23
|
+
/** The kit's own match, asked rather than re-implemented by <Dropdown>. */
|
|
24
|
+
export declare function dropdownMatch(label: unknown, query: unknown): boolean;
|
|
25
|
+
export declare function dropdownFiltering(query: unknown): boolean;
|
|
26
|
+
export declare function backLink(opts?: Record<string, unknown>): string;
|
|
27
|
+
export declare function statBand(opts?: Record<string, unknown>): string;
|
|
28
|
+
export declare function commandPalette(opts?: Record<string, unknown>): string;
|
|
29
|
+
export declare function rankGroups<G>(groups: readonly G[], query: string): G[];
|
|
30
|
+
export declare function rankCommands<I>(items: readonly I[], query: string): I[];
|
|
31
|
+
export declare function scoreCommand(item: Record<string, unknown>, query: string): number;
|
|
32
|
+
export declare function paletteHotkey(platform?: string): string;
|
|
33
|
+
export declare const PAGE_SIZES: number[];
|
|
34
|
+
export declare const DEFAULT_PAGE_SIZE: number;
|
|
35
|
+
export type NumericValueOptions = { value?: string | number | null; unit?: string; missing?: string };
|
|
36
|
+
export type DeltaValueOptions = { value?: string | null; tone?: 'success' | 'danger' | 'neutral'; basisId?: string; missing?: string };
|
|
37
|
+
export declare function formatNumericValue(options?: NumericValueOptions): { text: string; unit: string; missing: string | undefined };
|
|
38
|
+
export declare function formatDeltaValue(options?: DeltaValueOptions): { text: string; className: string; basisId: string | undefined };
|
|
39
|
+
export declare const SCORE: Record<'exact' | 'prefix' | 'wordStart' | 'contains' | 'keyword' | 'description' | 'subsequence', number>;
|
|
40
|
+
export declare const iconNames: string[];
|
|
41
|
+
export declare const iconCategories: { name: string; names: string[] }[];
|
|
42
|
+
export declare const iconOnlyAllowed: Record<string, string>;
|
|
43
|
+
export declare const iconMeanings: Record<string, string>;
|
|
44
|
+
export declare const sun: string;
|
|
45
|
+
export declare const moon: string;
|
|
46
|
+
export declare function prism(prefix?: string, size?: number): string;
|
|
47
|
+
export declare function seedling(prefix?: string, size?: number): string;
|
|
48
|
+
export declare function brand(options?: { p?: string; word?: string; size?: number; href?: string }): string;
|
|
49
|
+
export declare function illo(name: string): string;
|
|
50
|
+
export * from './motion.js';
|
|
51
|
+
export declare const illoNames: string[];
|
|
52
|
+
|
|
53
|
+
// Vanilla factories accept their existing option bags and return HTML.
|
|
54
|
+
export declare function accentPicker(opts?: Record<string, unknown>): string;
|
|
55
|
+
export declare function accountMenu(opts?: Record<string, unknown>): string;
|
|
56
|
+
export declare function accountShell(opts?: Record<string, unknown>): string;
|
|
57
|
+
export declare function appShell(opts?: Record<string, unknown>): string;
|
|
58
|
+
export declare function breadcrumbs(opts?: Record<string, unknown>): string;
|
|
59
|
+
export declare function busyRegion(opts?: Record<string, unknown>): string;
|
|
60
|
+
export declare function checkbox(opts?: Record<string, unknown>): string;
|
|
61
|
+
export declare function confirm(opts?: Record<string, unknown>): string;
|
|
62
|
+
export declare function deniedState(opts?: Record<string, unknown>): string;
|
|
63
|
+
export declare function emptyState(opts?: Record<string, unknown>): string;
|
|
64
|
+
export declare function feedbackWidget(opts?: Record<string, unknown>): string;
|
|
65
|
+
export declare function footer(opts?: Record<string, unknown>): string;
|
|
66
|
+
export declare function nav(opts?: Record<string, unknown>): string;
|
|
67
|
+
export declare function navTabs(opts?: Record<string, unknown>): string;
|
|
68
|
+
export declare function rowIdentity(opts?: Record<string, unknown>): string;
|
|
69
|
+
export declare function segmented(opts?: Record<string, unknown>): string;
|
|
70
|
+
export declare function sidebarNav(opts?: Record<string, unknown>): string;
|
|
71
|
+
export declare function skeleton(opts?: Record<string, unknown>): string;
|
|
72
|
+
export declare function skeletonTable(opts?: Record<string, unknown>): string;
|
|
73
|
+
export declare function success(opts?: Record<string, unknown>): string;
|
|
74
|
+
export declare function successPanel(opts?: Record<string, unknown>): string;
|
|
75
|
+
export declare function switchToggle(opts?: Record<string, unknown>): string;
|
|
76
|
+
export declare function tabs(opts?: Record<string, unknown>): string;
|
|
77
|
+
export declare function toast(opts?: Record<string, unknown>): string;
|
|
78
|
+
export declare function tooltip(opts?: Record<string, unknown>): string;
|
|
79
|
+
export declare function topbar(opts?: Record<string, unknown>): string;
|
|
80
|
+
export declare function commandPaletteList(groups?: readonly Record<string, unknown>[], opts?: { uid?: string; from?: number }): string;
|
|
81
|
+
export declare function deckTextSwitch(active?: string): string;
|
|
82
|
+
export declare function hlShell(raw: string): string;
|
|
83
|
+
export declare function pill(label: string, variant?: string): string;
|
|
84
|
+
export declare function statusDot(live?: boolean): string;
|
|
85
|
+
export declare function successCheck(): string;
|
|
86
|
+
export declare function themeIcon(theme: string): string;
|
|
87
|
+
export declare function themeName(theme: string): string;
|
|
88
|
+
export declare function themeToggle(theme?: string): string;
|
|
89
|
+
export declare function versionSwitcher(versions?: { label: string; meta?: string; badge?: string }[], activeIdx?: number): string;
|
|
90
|
+
|
|
91
|
+
export declare const ACCENTS: string[];
|
|
92
|
+
export declare const ACCOUNT_NAV: { id: string; icon: string; label: string }[];
|
|
93
|
+
export declare const RAIL_COOKIE: 'apliteni-ui-rail';
|
|
94
|
+
export declare const STAT_TONES: string[];
|
|
95
|
+
export declare const STAT_VARIANTS: string[];
|
|
96
|
+
|
|
97
|
+
export declare function applyAccent(name: string, root?: HTMLElement): void;
|
|
98
|
+
export declare function applyTheme(theme: string, root?: HTMLElement): void;
|
|
99
|
+
export declare function railCollapsed(cookies?: string): boolean | null;
|
|
100
|
+
export declare function closeCommandPalette(root: Element | null): void;
|
|
101
|
+
export declare function closeConfirm(root: Element | null): void;
|
|
102
|
+
export declare function closeDrawer(root: Element | null): void;
|
|
103
|
+
export declare function openCommandPalette(root: Element | null, returnFocusTo?: HTMLElement | null): void;
|
|
104
|
+
export declare function openConfirm(root: Element | null, returnFocusTo?: HTMLElement | null): void;
|
|
105
|
+
export declare function openDrawer(root: Element | null, returnFocusTo?: HTMLElement | null): void;
|
|
106
|
+
export declare function setPaletteResults(root: HTMLElement, groups: readonly Record<string, unknown>[]): void;
|
|
107
|
+
export declare function hideTooltip(host: HTMLElement): void;
|
|
108
|
+
export declare function showTooltip(host: HTMLElement, mark: Element): void;
|
|
109
|
+
export declare function initRowIdentity(root?: Document | Element): void;
|
|
110
|
+
export declare function initSegmented(root?: Document | Element): () => void;
|
|
111
|
+
export declare function initTabs(root?: Document | Element): void;
|
|
112
|
+
export declare function wireCommandPalette(root?: Document | Element): void;
|
|
113
|
+
export declare function wireConfirm(root?: Document | Element): void;
|
|
114
|
+
export declare function wireDrawer(root?: Document | Element): void;
|
|
115
|
+
export declare function wireFeedback(opts?: Record<string, unknown>): void;
|
|
116
|
+
export declare function wireNav(root?: Document | Element): void;
|
|
117
|
+
export declare function wireShell(root?: Document | Element, opts?: { persist?: boolean }): void;
|
|
118
|
+
export declare function wireTooltip(root?: Document | Element): void;
|
|
119
|
+
export declare function wireTopbar(root?: Document | Element): void;
|
|
120
|
+
export declare function wireSuccess(root: Element, opts?: { onDone?: () => void }): () => void;
|
|
121
|
+
export declare function wirePagination(root?: Document | Element | string | null, opts?: { onPage?: (page: number) => void; onPageSize?: (size: number) => void }): () => void;
|
|
122
|
+
export declare function nearestSection(node: Node | null, root?: Element | null): Element | null;
|
|
123
|
+
export declare function dismissToast(element: HTMLElement): void;
|
|
124
|
+
export declare function wireToastStack(container: Element | string | null): Element | null;
|
|
125
|
+
export declare function pushToast(container: Element | string | null, opts?: Record<string, unknown>): Element | null;
|
|
126
|
+
export declare function setBusy(root: Element | string | null, opts?: { busy?: boolean; message?: string; body?: string }): Element | null;
|
|
127
|
+
export declare function setButtonBusy(element: HTMLElement, opts?: { busy?: boolean }): void;
|
|
128
|
+
export declare function setPagerStatus(root: Element | string | null, text: string): Element | null;
|
package/src/index.js
CHANGED
|
@@ -26,3 +26,10 @@ export { setButtonBusy } from './components/button-busy.js';
|
|
|
26
26
|
export * from './components/table-values.js';
|
|
27
27
|
export * from './components/segmented.js';
|
|
28
28
|
export * from './components/filter-bar.js';
|
|
29
|
+
|
|
30
|
+
export { dropdownMatch, dropdownFiltering } from './logic/dropdown.js';
|
|
31
|
+
export { SCORE, rankGroups, rankCommands, scoreCommand, paletteHotkey } from './logic/command-palette.js';
|
|
32
|
+
export { segmentedNextIndex } from './logic/segmented.js';
|
|
33
|
+
export { PAGE_SIZES, DEFAULT_PAGE_SIZE } from './logic/pagination.js';
|
|
34
|
+
export { calloutIcons } from './logic/callout.js';
|
|
35
|
+
export { formatNumericValue, formatDeltaValue } from './logic/table-values.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const calloutIcons = { info: 'info', success: 'check', warn: 'alert', danger: 'alert', neutral: 'info' };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a match is worth more than another match.
|
|
3
|
+
*
|
|
4
|
+
* Six ways a query can meet an item, scored so that the whole beats the part
|
|
5
|
+
* and the name beats the note. The numbers are a ladder and not a measurement —
|
|
6
|
+
* what matters is the order, which is why src/components/command-palette.test.js
|
|
7
|
+
* asserts the ORDER of the results rather than any of these values.
|
|
8
|
+
*
|
|
9
|
+
* cmdk's command-score is the other published answer: one continuous score
|
|
10
|
+
* built out of SCORE_CONTINUE_MATCH 1, SCORE_SPACE_WORD_JUMP 0.9,
|
|
11
|
+
* SCORE_NON_SPACE_WORD_JUMP 0.8 and SCORE_CHARACTER_JUMP 0.17. It ranks
|
|
12
|
+
* beautifully and cannot be explained to a reader who asks why their item is
|
|
13
|
+
* third. A ladder can: the kit's palettes hold tens of items, not thousands.
|
|
14
|
+
*/
|
|
15
|
+
export const SCORE = {
|
|
16
|
+
exact: 100,
|
|
17
|
+
prefix: 90,
|
|
18
|
+
wordStart: 80,
|
|
19
|
+
contains: 70,
|
|
20
|
+
keyword: 60,
|
|
21
|
+
description: 40,
|
|
22
|
+
subsequence: 20,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const norm = (s) => String(s == null ? '' : s).toLowerCase().replace(/\s+/g, ' ').trim();
|
|
26
|
+
|
|
27
|
+
// Every character of `q`, in order, somewhere in `s` — "nc" finding "New
|
|
28
|
+
// campaign". The weakest match the kit accepts, and the only one that can pair
|
|
29
|
+
// a two-letter query with a twenty-letter label.
|
|
30
|
+
function subsequence(s, q) {
|
|
31
|
+
let at = 0;
|
|
32
|
+
for (const ch of q) {
|
|
33
|
+
at = s.indexOf(ch, at);
|
|
34
|
+
if (at === -1) return false;
|
|
35
|
+
at += 1;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A word here starts after a space or one of the separators a product name uses,
|
|
41
|
+
// so "camp" reaches the second word of "New campaign" and the "utm" in
|
|
42
|
+
// "url-utm_source".
|
|
43
|
+
const wordStarts = (s) => s.split(/[\s/\-_.:,]+/).filter(Boolean);
|
|
44
|
+
|
|
45
|
+
function scoreField({ label, description, keywords }, q) {
|
|
46
|
+
const name = norm(label);
|
|
47
|
+
if (!name && !description && !(keywords || []).length) return 0;
|
|
48
|
+
if (name === q) return SCORE.exact;
|
|
49
|
+
if (name.startsWith(q)) return SCORE.prefix;
|
|
50
|
+
if (wordStarts(name).some((w) => w.startsWith(q))) return SCORE.wordStart;
|
|
51
|
+
if (name.includes(q)) return SCORE.contains;
|
|
52
|
+
for (const k of keywords || []) {
|
|
53
|
+
const key = norm(k);
|
|
54
|
+
if (key === q || key.startsWith(q)) return SCORE.keyword;
|
|
55
|
+
}
|
|
56
|
+
if (norm(description).includes(q)) return SCORE.description;
|
|
57
|
+
if (subsequence(name, q)) return SCORE.subsequence;
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* What one item is worth against one query. 0 means it is not a result.
|
|
63
|
+
*
|
|
64
|
+
* An empty query scores everything the same, so a palette nobody has typed into
|
|
65
|
+
* shows what the caller passed, in the caller's order — which is where a
|
|
66
|
+
* product puts the four things somebody actually does here.
|
|
67
|
+
*
|
|
68
|
+
* A query with a space in it has to match as a whole OR token by token, every
|
|
69
|
+
* token landing somewhere: "new camp" reaches "New campaign" as a whole, and
|
|
70
|
+
* "camp new" reaches it one token at a time. The weakest token decides, because
|
|
71
|
+
* a result is only as good as the part of the query it answers worst.
|
|
72
|
+
*/
|
|
73
|
+
export function scoreCommand(item, query) {
|
|
74
|
+
const q = norm(query);
|
|
75
|
+
if (!q) return 1;
|
|
76
|
+
const whole = scoreField(item, q);
|
|
77
|
+
const tokens = q.split(' ');
|
|
78
|
+
if (tokens.length < 2) return whole;
|
|
79
|
+
let weakest = Infinity;
|
|
80
|
+
for (const t of tokens) {
|
|
81
|
+
const s = scoreField(item, t);
|
|
82
|
+
if (!s) { weakest = 0; break; }
|
|
83
|
+
weakest = Math.min(weakest, s);
|
|
84
|
+
}
|
|
85
|
+
return Math.max(whole, weakest === Infinity ? 0 : weakest);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The items that answer `query`, best first.
|
|
90
|
+
*
|
|
91
|
+
* Ties keep the caller's order — that is the whole of the kit's ordering
|
|
92
|
+
* opinion, and it is what lets a product put its four most-used commands at the
|
|
93
|
+
* top of the list it passes and have them stay there.
|
|
94
|
+
*/
|
|
95
|
+
export function rankCommands(items, query) {
|
|
96
|
+
return (items || [])
|
|
97
|
+
.map((item, i) => ({ item, i, score: scoreCommand(item, query) }))
|
|
98
|
+
.filter((r) => r.score > 0)
|
|
99
|
+
.sort((a, b) => b.score - a.score || a.i - b.i)
|
|
100
|
+
.map((r) => r.item);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The groups that answer `query`, best group first, each holding its own ranked
|
|
105
|
+
* rows and nothing that scored zero.
|
|
106
|
+
*
|
|
107
|
+
* A group is carried by its best row, and the caller's order breaks the tie at
|
|
108
|
+
* both levels — so a palette nobody has typed into is exactly the list that was
|
|
109
|
+
* passed in. cmdk ranks groups the same way; the tie-break is the kit's, and it
|
|
110
|
+
* is what keeps a heading from moving under a reader who has typed nothing.
|
|
111
|
+
*/
|
|
112
|
+
export function rankGroups(groups, query) {
|
|
113
|
+
return (groups || [])
|
|
114
|
+
.map((g, i) => {
|
|
115
|
+
const items = rankCommands(g.items || [], query);
|
|
116
|
+
const best = items.length ? scoreCommand(items[0], query) : 0;
|
|
117
|
+
return { group: { ...g, items }, i, best };
|
|
118
|
+
})
|
|
119
|
+
.filter((g) => g.group.items.length > 0)
|
|
120
|
+
.sort((a, b) => b.best - a.best || a.i - b.i)
|
|
121
|
+
.map((g) => g.group);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The label for the key that opens it, on the platform the caller is on. */
|
|
125
|
+
export function paletteHotkey(platform = typeof navigator === 'undefined' ? '' : navigator.platform) {
|
|
126
|
+
return /mac|iphone|ipad|ipod/i.test(String(platform || '')) ? '⌘K' : 'Ctrl K';
|
|
127
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const FOLD = { ł: 'l', ø: 'o', đ: 'd', ð: 'd', ß: 'ss', æ: 'ae', œ: 'oe', ı: 'i', þ: 'th' };
|
|
2
|
+
const fold = (s) => String(s == null ? '' : s).normalize('NFD').replace(/\p{M}/gu, '').toLowerCase()
|
|
3
|
+
.replace(/[łøđðßæœıþ]/g, (c) => FOLD[c]);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Is this query narrowing anything? A query of spaces, or of marks with no letter
|
|
7
|
+
* under them, is not — it leaves every row showing, and the separators with them.
|
|
8
|
+
*
|
|
9
|
+
* Public because a second implementation of this dropdown cannot write its own: the
|
|
10
|
+
* React <Dropdown> asks these two, the way <CommandPalette> asks rankGroups(), so a
|
|
11
|
+
* list a server rendered and the same list after a keystroke hide the same rows.
|
|
12
|
+
*/
|
|
13
|
+
export const dropdownFiltering = (query) => fold(query).trim() !== '';
|
|
14
|
+
|
|
15
|
+
/** Does `label` match `query`? A substring, anywhere, ignoring case and accents. */
|
|
16
|
+
export const dropdownMatch = (label, query) => fold(label).includes(fold(query).trim());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function segmentedNextIndex(key, index, length) {
|
|
2
|
+
if (!length) return null;
|
|
3
|
+
if (key === 'ArrowRight' || key === 'ArrowDown') return (index + 1) % length;
|
|
4
|
+
if (key === 'ArrowLeft' || key === 'ArrowUp') return (index - 1 + length) % length;
|
|
5
|
+
if (key === 'Home') return 0;
|
|
6
|
+
if (key === 'End') return length - 1;
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function formatNumericValue({ value, unit = '', missing = 'Not available' } = {}) {
|
|
2
|
+
const present = value != null && value !== '';
|
|
3
|
+
return { text: present ? String(value) : '—', unit: present ? unit : '', missing: present ? undefined : missing };
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function formatDeltaValue({ value, tone = 'neutral', basisId, missing = 'No earlier figure' } = {}) {
|
|
7
|
+
const present = value != null && value !== '';
|
|
8
|
+
const judged = present && ['success', 'danger'].includes(tone) && !/^[+−-]?0+(?:[.,]0+)?(?:[^\d.,].*)?$/.test(String(value).replace(/\s/g, ''));
|
|
9
|
+
return { text: String(present ? value : missing), className: `ui-delta${judged ? ` ui-delta--${tone}` : ''}`, basisId };
|
|
10
|
+
}
|
package/src/motion.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function prefersReducedMotion(): boolean;
|
|
2
|
+
export declare function staggerDelay(index: number, stepMs?: number): number;
|
|
3
|
+
export declare function initReveal(root?: Document | Element): IntersectionObserver | undefined;
|
|
4
|
+
export declare const ENTRANCE_FALLBACK_MS: number;
|
|
5
|
+
export declare function playEntrance(element: Element | null, className?: string): void;
|
|
6
|
+
export declare function replay(element: HTMLElement | null): void;
|