@syware/jlib 0.5.1 → 0.7.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/dist/all.js +11 -4
- package/dist/atoms/jl-app-icon.css_raw.js +3 -0
- package/dist/atoms/jl-app-icon.d.ts +34 -0
- package/dist/atoms/jl-app-icon.js +86 -0
- package/dist/atoms/jl-avatar.js +1 -1
- package/dist/atoms/jl-badge.js +9 -2
- package/dist/atoms/jl-button.d.ts +5 -0
- package/dist/atoms/jl-button.js +5 -2
- package/dist/atoms/jl-chip.js +3 -2
- package/dist/atoms/jl-input.js +5 -4
- package/dist/atoms/jl-link.js +2 -2
- package/dist/atoms/jl-rating.js +4 -5
- package/dist/atoms/jl-select.css_raw.js +1 -1
- package/dist/atoms/jl-select.d.ts +3 -2
- package/dist/atoms/jl-select.js +6 -5
- package/dist/atoms/jl-spinner.js +2 -2
- package/dist/base/jl-form-control.js +2 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -3
- package/dist/lib/app-icon.d.ts +8 -0
- package/dist/lib/app-icon.js +37 -0
- package/dist/lib/dom.d.ts +2 -0
- package/dist/lib/dom.js +3 -1
- package/dist/lib/download.d.ts +10 -0
- package/dist/lib/download.js +62 -0
- package/dist/lib/icons.d.ts +4 -0
- package/dist/lib/icons.js +7 -4
- package/dist/lib/input-list.d.ts +12 -0
- package/dist/lib/input-list.js +14 -0
- package/dist/lib/internals.d.ts +5 -0
- package/dist/lib/internals.js +18 -0
- package/dist/lib/palette.js +6 -0
- package/dist/lib/resize.d.ts +2 -0
- package/dist/lib/resize.js +10 -0
- package/dist/lib/table.d.ts +6 -1
- package/dist/lib/table.js +16 -1
- package/dist/lib/text.d.ts +12 -0
- package/dist/lib/text.js +16 -1
- package/dist/lib/texts.d.ts +97 -0
- package/dist/lib/texts.js +102 -0
- package/dist/lib/time.d.ts +5 -3
- package/dist/lib/time.js +23 -15
- package/dist/lib/tree.d.ts +10 -1
- package/dist/lib/tree.js +37 -3
- package/dist/lib/trigger.js +1 -1
- package/dist/molecules/jl-account-button.css_raw.js +1 -1
- package/dist/molecules/jl-account-button.d.ts +8 -1
- package/dist/molecules/jl-account-button.js +9 -2
- package/dist/molecules/jl-alert.js +3 -2
- package/dist/molecules/jl-breadcrumb.js +3 -3
- package/dist/molecules/jl-calendar.js +5 -5
- package/dist/molecules/jl-chip-group.js +3 -3
- package/dist/molecules/jl-code-block.css_raw.js +1 -1
- package/dist/molecules/jl-code-block.d.ts +5 -0
- package/dist/molecules/jl-code-block.js +5 -2
- package/dist/molecules/jl-combobox.js +6 -7
- package/dist/molecules/jl-copy-button.js +5 -4
- package/dist/molecules/jl-date-picker.js +7 -8
- package/dist/molecules/jl-empty.js +2 -2
- package/dist/molecules/jl-facts.css_raw.js +3 -0
- package/dist/molecules/jl-facts.d.ts +35 -0
- package/dist/molecules/jl-facts.js +64 -0
- package/dist/molecules/jl-file-drop.d.ts +5 -0
- package/dist/molecules/jl-file-drop.js +18 -15
- package/dist/molecules/jl-input-list.css_raw.js +3 -0
- package/dist/molecules/jl-input-list.d.ts +79 -0
- package/dist/molecules/jl-input-list.js +177 -0
- package/dist/molecules/jl-list-item.js +1 -1
- package/dist/molecules/jl-load-more.css_raw.js +3 -0
- package/dist/molecules/jl-load-more.d.ts +41 -0
- package/dist/molecules/jl-load-more.js +62 -0
- package/dist/molecules/jl-load-state.d.ts +4 -1
- package/dist/molecules/jl-load-state.js +20 -12
- package/dist/molecules/jl-menu.js +1 -1
- package/dist/molecules/jl-number-input.js +5 -5
- package/dist/molecules/jl-pagination.js +5 -5
- package/dist/molecules/jl-radio-group.js +2 -2
- package/dist/molecules/jl-search-button.js +5 -5
- package/dist/molecules/jl-search.js +5 -5
- package/dist/molecules/jl-segmented.js +3 -2
- package/dist/molecules/jl-steps.css_raw.js +1 -1
- package/dist/molecules/jl-steps.d.ts +2 -0
- package/dist/molecules/jl-steps.js +30 -14
- package/dist/molecules/jl-tabs.js +3 -2
- package/dist/molecules/jl-tag-input.js +3 -3
- package/dist/molecules/jl-timeline.js +1 -1
- package/dist/molecules/jl-toast.css_raw.js +1 -1
- package/dist/molecules/jl-toast.d.ts +13 -1
- package/dist/molecules/jl-toast.js +23 -3
- package/dist/molecules/jl-tooltip.d.ts +4 -1
- package/dist/molecules/jl-tooltip.js +27 -15
- package/dist/organisms/jl-command-palette.css_raw.js +1 -1
- package/dist/organisms/jl-command-palette.d.ts +14 -0
- package/dist/organisms/jl-command-palette.js +40 -10
- package/dist/organisms/jl-confirm.d.ts +2 -1
- package/dist/organisms/jl-confirm.js +18 -12
- package/dist/organisms/jl-dialog.js +2 -1
- package/dist/organisms/jl-drawer.js +2 -1
- package/dist/organisms/jl-flow.js +13 -13
- package/dist/organisms/jl-navbar.js +6 -6
- package/dist/organisms/jl-side-nav.css_raw.js +1 -1
- package/dist/organisms/jl-side-nav.d.ts +33 -1
- package/dist/organisms/jl-side-nav.js +71 -14
- package/dist/organisms/jl-tab-bar.js +3 -3
- package/dist/organisms/jl-table.css_raw.js +1 -1
- package/dist/organisms/jl-table.d.ts +9 -0
- package/dist/organisms/jl-table.js +36 -6
- package/dist/organisms/jl-toaster.css_raw.js +1 -1
- package/dist/organisms/jl-toaster.d.ts +9 -1
- package/dist/organisms/jl-toaster.js +148 -7
- package/dist/organisms/jl-tree.css_raw.js +1 -1
- package/dist/organisms/jl-tree.d.ts +71 -4
- package/dist/organisms/jl-tree.js +303 -40
- package/dist/styles/tokens.css_raw.js +1 -1
- package/dist/templates/jl-app-shell.css_raw.js +1 -1
- package/dist/templates/jl-app-shell.d.ts +16 -1
- package/dist/templates/jl-app-shell.js +42 -3
- package/dist/templates/jl-auth-layout.css_raw.js +3 -0
- package/dist/templates/jl-auth-layout.d.ts +35 -0
- package/dist/templates/jl-auth-layout.js +139 -0
- package/dist/templates/jl-page.css_raw.js +1 -1
- package/dist/templates/jl-page.js +3 -3
- package/dist/templates/jl-split-view.css_raw.js +1 -1
- package/dist/templates/jl-split-view.d.ts +21 -1
- package/dist/templates/jl-split-view.js +37 -4
- package/dist/tokens.css +2 -0
- package/dist/types/jl-app-icon.types.d.ts +5 -0
- package/dist/types/jl-command-palette.types.d.ts +7 -0
- package/dist/types/jl-facts.types.d.ts +17 -0
- package/dist/types/jl-side-nav.types.d.ts +2 -0
- package/dist/types/jl-split-view.types.d.ts +2 -0
- package/dist/types/jl-steps.types.d.ts +2 -0
- package/dist/types/jl-table.types.d.ts +9 -2
- package/dist/types/jl-toaster.types.d.ts +42 -1
- package/dist/types/jl-tree.types.d.ts +26 -0
- package/dist/types/texts.types.d.ts +5 -0
- package/package.json +3 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ICON_STROKE_WIDTH } from "./icons.js";
|
|
2
|
+
// src/lib/app-icon.ts
|
|
3
|
+
/** The glyph's share of the tile's side, at every size. */
|
|
4
|
+
var GLYPH_SHARE = .62;
|
|
5
|
+
/** The corner's share of the tile's side. */
|
|
6
|
+
var CORNER_SHARE = .26;
|
|
7
|
+
var FILE_SIDE = 64;
|
|
8
|
+
var GLYPH_GRID = 24;
|
|
9
|
+
var DECIMALS = 2;
|
|
10
|
+
var ATTRIBUTE_ESCAPES = {
|
|
11
|
+
"\"": """,
|
|
12
|
+
"&": "&",
|
|
13
|
+
"<": "<"
|
|
14
|
+
};
|
|
15
|
+
var NEEDS_ESCAPE = /["&<]/g;
|
|
16
|
+
var round = (value) => Number(value.toFixed(DECIMALS));
|
|
17
|
+
var escapeAttribute = (value) => String(value).replace(NEEDS_ESCAPE, (character) => ATTRIBUTE_ESCAPES[character] ?? character);
|
|
18
|
+
var attributesOf = (attributes) => Object.entries(attributes).flatMap(([name, value]) => value === void 0 ? [] : [` ${name}="${escapeAttribute(value)}"`]).join("");
|
|
19
|
+
/** An app's icon as the text of an SVG file: its glyph in `colors.glyph` on a rounded square of `colors.tile`. */
|
|
20
|
+
function appIconSvg(glyph, colors) {
|
|
21
|
+
const glyphSide = FILE_SIDE * GLYPH_SHARE;
|
|
22
|
+
const offset = round(24.32 / 2);
|
|
23
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${FILE_SIDE} ${FILE_SIDE}"><rect${attributesOf({
|
|
24
|
+
fill: colors.tile,
|
|
25
|
+
height: FILE_SIDE,
|
|
26
|
+
rx: round(FILE_SIDE * CORNER_SHARE),
|
|
27
|
+
width: FILE_SIDE
|
|
28
|
+
})}/><g${attributesOf({
|
|
29
|
+
fill: "none",
|
|
30
|
+
stroke: colors.glyph,
|
|
31
|
+
"stroke-linecap": "round",
|
|
32
|
+
"stroke-linejoin": "round",
|
|
33
|
+
"stroke-width": ICON_STROKE_WIDTH,
|
|
34
|
+
transform: `translate(${offset} ${offset}) scale(${round(glyphSide / GLYPH_GRID)})`
|
|
35
|
+
})}>${glyph.map(([tag, attributes]) => `<${tag}${attributesOf(attributes)}/>`).join("")}</g></svg>`;
|
|
36
|
+
}
|
|
37
|
+
export { CORNER_SHARE, GLYPH_SHARE, appIconSvg };
|
package/dist/lib/dom.d.ts
CHANGED
|
@@ -4,3 +4,5 @@ export declare function setOrRemove(element: Element, name: string, value: strin
|
|
|
4
4
|
export declare function uniqueId(prefix: string): string;
|
|
5
5
|
/** An SVG element with its attributes: JSX makes HTML elements only. */
|
|
6
6
|
export declare function svgElement<Tag extends keyof SVGElementTagNameMap>(tag: Tag, attributes: Record<string, string>): SVGElementTagNameMap[Tag];
|
|
7
|
+
/** Whether a click asks the browser for something of its own: a new tab or window, a download. */
|
|
8
|
+
export declare const isModifiedClick: (event: MouseEvent) => boolean;
|
package/dist/lib/dom.js
CHANGED
|
@@ -20,4 +20,6 @@ function svgElement(tag, attributes) {
|
|
|
20
20
|
for (const [name, value] of Object.entries(attributes)) element.setAttribute(name, value);
|
|
21
21
|
return element;
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
/** Whether a click asks the browser for something of its own: a new tab or window, a download. */
|
|
24
|
+
var isModifiedClick = (event) => event.ctrlKey || event.metaKey || event.shiftKey || event.altKey || event.button !== 0;
|
|
25
|
+
export { isModifiedClick, setOrRemove, svgElement, uniqueId };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The file name a Content-Disposition header gives (its UTF-8 form first), or none. */
|
|
2
|
+
export declare function dispositionFileName(header: string | null): string | undefined;
|
|
3
|
+
/** A file name for an address: its last path segment, or the fallback when it has none. */
|
|
4
|
+
export declare function urlFileName(url: string, fallback: string): string;
|
|
5
|
+
/** What a failed answer's body says: `{ error: { message } }`, `{ error: "…" }`, `{ message }`, or its status. */
|
|
6
|
+
export declare function answerError(body: unknown, status: number): string;
|
|
7
|
+
/** Reads an answer to its end, saying how many bytes came at most once per interval, and once more at the end. */
|
|
8
|
+
export declare function readWithProgress(response: Response, onProgress: (received: number) => void, intervalMs: number, now?: () => number): Promise<Blob>;
|
|
9
|
+
/** Hands a file to the browser, which saves it under that name. */
|
|
10
|
+
export declare function saveBlob(blob: Blob, name: string): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/lib/download.ts
|
|
2
|
+
var FILE_NAME_UTF8 = /filename\*\s*=\s*(?:UTF-8|utf-8)''([^;]+)/;
|
|
3
|
+
var FILE_NAME = /filename\s*=\s*"?([^";]+)"?/;
|
|
4
|
+
var REVOKE_DELAY_MS = 1e3;
|
|
5
|
+
var ADDRESS_BASE = "http://localhost/";
|
|
6
|
+
/** The file name a Content-Disposition header gives (its UTF-8 form first), or none. */
|
|
7
|
+
function dispositionFileName(header) {
|
|
8
|
+
const utf8 = FILE_NAME_UTF8.exec(header ?? "")?.[1];
|
|
9
|
+
if (utf8 !== void 0) return decodeURIComponent(utf8.trim());
|
|
10
|
+
return FILE_NAME.exec(header ?? "")?.[1]?.trim() || void 0;
|
|
11
|
+
}
|
|
12
|
+
/** A file name for an address: its last path segment, or the fallback when it has none. */
|
|
13
|
+
function urlFileName(url, fallback) {
|
|
14
|
+
const segment = new URL(url, ADDRESS_BASE).pathname.split("/").at(-1) ?? "";
|
|
15
|
+
return segment === "" ? fallback : decodeURIComponent(segment);
|
|
16
|
+
}
|
|
17
|
+
/** What a failed answer's body says: `{ error: { message } }`, `{ error: "…" }`, `{ message }`, or its status. */
|
|
18
|
+
function answerError(body, status) {
|
|
19
|
+
const record = typeof body === "object" && body !== null ? body : {};
|
|
20
|
+
const error = record.error;
|
|
21
|
+
return [
|
|
22
|
+
typeof error === "object" && error !== null ? error.message : void 0,
|
|
23
|
+
error,
|
|
24
|
+
record.message
|
|
25
|
+
].find((value) => typeof value === "string") ?? `HTTP ${status}`;
|
|
26
|
+
}
|
|
27
|
+
/** Reads an answer to its end, saying how many bytes came at most once per interval, and once more at the end. */
|
|
28
|
+
async function readWithProgress(response, onProgress, intervalMs, now = () => performance.now()) {
|
|
29
|
+
const type = response.headers.get("content-type") ?? "";
|
|
30
|
+
if (response.body === null) return response.blob();
|
|
31
|
+
const reader = response.body.getReader();
|
|
32
|
+
const chunks = [];
|
|
33
|
+
const progress = {
|
|
34
|
+
received: 0,
|
|
35
|
+
reported: Number.NEGATIVE_INFINITY,
|
|
36
|
+
said: 0
|
|
37
|
+
};
|
|
38
|
+
for (;;) {
|
|
39
|
+
const chunk = await reader.read();
|
|
40
|
+
if (chunk.done) break;
|
|
41
|
+
chunks.push(chunk.value);
|
|
42
|
+
progress.received += chunk.value.byteLength;
|
|
43
|
+
if (now() - progress.reported >= intervalMs) {
|
|
44
|
+
progress.reported = now();
|
|
45
|
+
progress.said = progress.received;
|
|
46
|
+
onProgress(progress.received);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (progress.said !== progress.received) onProgress(progress.received);
|
|
50
|
+
return new Blob(chunks, { type });
|
|
51
|
+
}
|
|
52
|
+
/** Hands a file to the browser, which saves it under that name. */
|
|
53
|
+
function saveBlob(blob, name) {
|
|
54
|
+
const link = document.createElement("a");
|
|
55
|
+
link.download = name;
|
|
56
|
+
link.href = URL.createObjectURL(blob);
|
|
57
|
+
document.body.append(link);
|
|
58
|
+
link.click();
|
|
59
|
+
link.remove();
|
|
60
|
+
setTimeout(() => URL.revokeObjectURL(link.href), REVOKE_DELAY_MS);
|
|
61
|
+
}
|
|
62
|
+
export { answerError, dispositionFileName, readWithProgress, saveBlob, urlFileName };
|
package/dist/lib/icons.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { IconNode } from 'lucide';
|
|
2
|
+
/** The width of every glyph's strokes, on lucide's 24-unit grid. */
|
|
3
|
+
export declare const ICON_STROKE_WIDTH = 1.9;
|
|
2
4
|
/**
|
|
3
5
|
* Adds icons to the ones `jl-icon` knows, by name. Any lucide icon works:
|
|
4
6
|
* `registerIcons({ rocket: Rocket })` after `import { Rocket } from 'lucide'`, or all the usual ones at once with
|
|
@@ -8,5 +10,7 @@ export declare function registerIcons(icons: Record<string, IconNode>): void;
|
|
|
8
10
|
/** The names `jl-icon` can draw now, in alphabetical order. */
|
|
9
11
|
export declare const iconNames: () => string[];
|
|
10
12
|
export declare const hasIcon: (name: string) => boolean;
|
|
13
|
+
/** An icon's shapes, by its name; a name nobody registered gives a neutral mark instead of nothing. */
|
|
14
|
+
export declare const iconNode: (name: string) => IconNode;
|
|
11
15
|
/** An icon as an SVG element; a name nobody registered draws a neutral mark instead of nothing. */
|
|
12
16
|
export declare function iconElement(name: string, size: number): SVGElement;
|
package/dist/lib/icons.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CircleAlert, createElement } from "lucide";
|
|
2
2
|
// src/lib/icons.ts
|
|
3
3
|
var FALLBACK = CircleAlert;
|
|
4
|
-
|
|
4
|
+
/** The width of every glyph's strokes, on lucide's 24-unit grid. */
|
|
5
|
+
var ICON_STROKE_WIDTH = 1.9;
|
|
5
6
|
/**
|
|
6
7
|
* The icons `jl-icon` can draw, by name. It starts empty: each component registers the icons it draws itself, and
|
|
7
8
|
* a page registers the names it writes, so a bundle holds only the icons in use.
|
|
@@ -27,13 +28,15 @@ function missing(name) {
|
|
|
27
28
|
}
|
|
28
29
|
return FALLBACK;
|
|
29
30
|
}
|
|
31
|
+
/** An icon's shapes, by its name; a name nobody registered gives a neutral mark instead of nothing. */
|
|
32
|
+
var iconNode = (name) => registry.get(name) ?? missing(name);
|
|
30
33
|
/** An icon as an SVG element; a name nobody registered draws a neutral mark instead of nothing. */
|
|
31
34
|
function iconElement(name, size) {
|
|
32
|
-
return createElement(
|
|
35
|
+
return createElement(iconNode(name), {
|
|
33
36
|
"aria-hidden": "true",
|
|
34
37
|
height: size,
|
|
35
|
-
"stroke-width":
|
|
38
|
+
"stroke-width": ICON_STROKE_WIDTH,
|
|
36
39
|
width: size
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
|
-
export { hasIcon, iconElement, iconNames, registerIcons };
|
|
42
|
+
export { ICON_STROKE_WIDTH, hasIcon, iconElement, iconNames, iconNode, registerIcons };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The rows shown: the values, or one empty row to type the first one in. */
|
|
2
|
+
export declare const rowsOf: (values: readonly string[]) => string[];
|
|
3
|
+
/** The rows with the one at `index` changed. */
|
|
4
|
+
export declare const withRowAt: (rows: readonly string[], index: number, value: string) => string[];
|
|
5
|
+
/** The rows without the one at `index`; one empty row stays when it was the last. */
|
|
6
|
+
export declare const withoutRowAt: (rows: readonly string[], index: number) => string[];
|
|
7
|
+
/** The values kept: the rows people filled in. */
|
|
8
|
+
export declare const filledValues: (rows: readonly string[]) => string[];
|
|
9
|
+
/** Whether another row fits under `max`; none or zero means no limit. */
|
|
10
|
+
export declare const canAddRow: (count: number, max: number) => boolean;
|
|
11
|
+
/** Whether two lists hold the same values in the same order. */
|
|
12
|
+
export declare const sameValues: (left: readonly string[], right: readonly string[]) => boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/lib/input-list.ts
|
|
2
|
+
/** The rows shown: the values, or one empty row to type the first one in. */
|
|
3
|
+
var rowsOf = (values) => values.length === 0 ? [""] : [...values];
|
|
4
|
+
/** The rows with the one at `index` changed. */
|
|
5
|
+
var withRowAt = (rows, index, value) => rows.map((row, at) => at === index ? value : row);
|
|
6
|
+
/** The rows without the one at `index`; one empty row stays when it was the last. */
|
|
7
|
+
var withoutRowAt = (rows, index) => rowsOf(rows.filter((_, at) => at !== index));
|
|
8
|
+
/** The values kept: the rows people filled in. */
|
|
9
|
+
var filledValues = (rows) => rows.filter((row) => row.trim() !== "");
|
|
10
|
+
/** Whether another row fits under `max`; none or zero means no limit. */
|
|
11
|
+
var canAddRow = (count, max) => max <= 0 || count < max;
|
|
12
|
+
/** Whether two lists hold the same values in the same order. */
|
|
13
|
+
var sameValues = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
|
|
14
|
+
export { canAddRow, filledValues, rowsOf, sameValues, withRowAt, withoutRowAt };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The internals of a form-associated element. Where the DOM gives them no form methods (jsdom), they get ones that
|
|
3
|
+
* keep nothing and find every value valid; a browser's are left as they are.
|
|
4
|
+
*/
|
|
5
|
+
export declare function formInternals(element: HTMLElement): ElementInternals;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/lib/internals.ts
|
|
2
|
+
/** What a form control asks of its internals that jsdom's lack: with them, a page's unit tests can create it. */
|
|
3
|
+
var FORM_METHODS = {
|
|
4
|
+
checkValidity: () => true,
|
|
5
|
+
reportValidity: () => true,
|
|
6
|
+
setFormValue: () => void 0,
|
|
7
|
+
setValidity: () => void 0
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The internals of a form-associated element. Where the DOM gives them no form methods (jsdom), they get ones that
|
|
11
|
+
* keep nothing and find every value valid; a browser's are left as they are.
|
|
12
|
+
*/
|
|
13
|
+
function formInternals(element) {
|
|
14
|
+
const internals = element.attachInternals();
|
|
15
|
+
if (typeof internals.setFormValue === "function") return internals;
|
|
16
|
+
return Object.assign(internals, FORM_METHODS);
|
|
17
|
+
}
|
|
18
|
+
export { formInternals };
|
package/dist/lib/palette.js
CHANGED
|
@@ -3,12 +3,18 @@ import { fuzzyMatch } from "./fuzzy.js";
|
|
|
3
3
|
/** A match on a keyword counts less than one on the label people read. */
|
|
4
4
|
var KEYWORD_WEIGHT = .5;
|
|
5
5
|
var NO_GROUP = "";
|
|
6
|
+
var RESULT_SCORE = 0;
|
|
6
7
|
function keywordScore(query, keywords) {
|
|
7
8
|
const scores = keywords.map((keyword) => fuzzyMatch(query, keyword)?.score).filter((score) => score !== void 0);
|
|
8
9
|
return scores.length === 0 ? null : Math.max(...scores) * KEYWORD_WEIGHT;
|
|
9
10
|
}
|
|
10
11
|
function matchCommand(query, command) {
|
|
11
12
|
const label = fuzzyMatch(query, command.label);
|
|
13
|
+
if (command.result === true) return {
|
|
14
|
+
command,
|
|
15
|
+
positions: label?.positions ?? [],
|
|
16
|
+
score: RESULT_SCORE
|
|
17
|
+
};
|
|
12
18
|
const keyword = keywordScore(query, command.keywords ?? []);
|
|
13
19
|
if (label === null && keyword === null) return null;
|
|
14
20
|
if (label === null) return {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/lib/resize.ts
|
|
2
|
+
/** A stand-in that watches nothing, for a DOM without ResizeObserver (jsdom, which has no layout either). */
|
|
3
|
+
var INERT_OBSERVER = {
|
|
4
|
+
disconnect: () => void 0,
|
|
5
|
+
observe: () => void 0,
|
|
6
|
+
unobserve: () => void 0
|
|
7
|
+
};
|
|
8
|
+
/** Watches elements' sizes; where the DOM has no ResizeObserver, nothing changes size and nothing is watched. */
|
|
9
|
+
var watchSizes = (callback) => typeof ResizeObserver === "function" ? new ResizeObserver(callback) : INERT_OBSERVER;
|
|
10
|
+
export { watchSizes };
|
package/dist/lib/table.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TableAlign, TableCellFormat, TableColumn, TableRow, TableRowKey, TableSelectionState, TableSortDirection } from '../types/jl-table.types';
|
|
1
|
+
import type { TableAlign, TableCellFormat, TableCellRenderer, TableColumn, TableRow, TableRowKey, TableSelectionState, TableSortDirection } from '../types/jl-table.types';
|
|
2
2
|
export declare const nextSortDirection: (direction: TableSortDirection) => TableSortDirection;
|
|
3
3
|
/** A cell's value as a number, `NaN` when it is not one. */
|
|
4
4
|
export declare function toNumber(value: unknown): number;
|
|
@@ -28,3 +28,8 @@ export declare function selectionState(keys: readonly TableRowKey[], selected: R
|
|
|
28
28
|
export declare function toggleAll(keys: readonly TableRowKey[], selected: ReadonlySet<TableRowKey>): TableRowKey[];
|
|
29
29
|
/** The selection with one row added or taken out. */
|
|
30
30
|
export declare function toggleKey(selected: readonly TableRowKey[], key: TableRowKey, on: boolean): TableRowKey[];
|
|
31
|
+
/**
|
|
32
|
+
* A renderer of `renderers` written for the page's own type of row: the table hands each renderer back a row the
|
|
33
|
+
* page gave it, so `cellOf<Order>((order) => <JlLink href={order.href} label={order.name} />)` reads it typed.
|
|
34
|
+
*/
|
|
35
|
+
export declare const cellOf: <Row>(draw: (row: Row) => Node) => TableCellRenderer;
|
package/dist/lib/table.js
CHANGED
|
@@ -11,6 +11,10 @@ var DIRECTION_SIGN = {
|
|
|
11
11
|
none: 0
|
|
12
12
|
};
|
|
13
13
|
var DATE_FORMAT = { dateStyle: "medium" };
|
|
14
|
+
var DATE_TIME_FORMAT = {
|
|
15
|
+
dateStyle: "medium",
|
|
16
|
+
timeStyle: "short"
|
|
17
|
+
};
|
|
14
18
|
var nextSortDirection = (direction) => NEXT_DIRECTION[direction];
|
|
15
19
|
var isEmpty = (value) => value === null || value === void 0 || value === "";
|
|
16
20
|
/** A cell's value as a number, `NaN` when it is not one. */
|
|
@@ -31,6 +35,7 @@ var COMPARERS = {
|
|
|
31
35
|
badge: (a, b, locale) => String(a).localeCompare(String(b), locale),
|
|
32
36
|
code: (a, b, locale) => String(a).localeCompare(String(b), locale),
|
|
33
37
|
date: (a, b) => compareNumbers(toTime(a), toTime(b)),
|
|
38
|
+
datetime: (a, b) => compareNumbers(toTime(a), toTime(b)),
|
|
34
39
|
number: (a, b) => compareNumbers(toNumber(a), toNumber(b)),
|
|
35
40
|
text: (a, b, locale) => String(a).localeCompare(String(b), locale, { numeric: true })
|
|
36
41
|
};
|
|
@@ -41,6 +46,7 @@ function isUnorderable(value, format) {
|
|
|
41
46
|
badge: () => false,
|
|
42
47
|
code: () => false,
|
|
43
48
|
date: () => Number.isNaN(toTime(value)),
|
|
49
|
+
datetime: () => Number.isNaN(toTime(value)),
|
|
44
50
|
number: () => Number.isNaN(toNumber(value)),
|
|
45
51
|
text: () => false
|
|
46
52
|
}[format]();
|
|
@@ -66,6 +72,10 @@ var FORMATTERS = {
|
|
|
66
72
|
const time = toTime(value);
|
|
67
73
|
return Number.isNaN(time) ? String(value) : new Intl.DateTimeFormat(locale, DATE_FORMAT).format(time);
|
|
68
74
|
},
|
|
75
|
+
datetime: (value, locale) => {
|
|
76
|
+
const time = toTime(value);
|
|
77
|
+
return Number.isNaN(time) ? String(value) : new Intl.DateTimeFormat(locale, DATE_TIME_FORMAT).format(time);
|
|
78
|
+
},
|
|
69
79
|
number: (value, locale) => {
|
|
70
80
|
const number = toNumber(value);
|
|
71
81
|
return Number.isNaN(number) ? String(value) : new Intl.NumberFormat(locale).format(number);
|
|
@@ -107,4 +117,9 @@ function toggleKey(selected, key, on) {
|
|
|
107
117
|
const without = selected.filter((candidate) => candidate !== key);
|
|
108
118
|
return on ? [...without, key] : without;
|
|
109
119
|
}
|
|
110
|
-
|
|
120
|
+
/**
|
|
121
|
+
* A renderer of `renderers` written for the page's own type of row: the table hands each renderer back a row the
|
|
122
|
+
* page gave it, so `cellOf<Order>((order) => <JlLink href={order.href} label={order.name} />)` reads it typed.
|
|
123
|
+
*/
|
|
124
|
+
var cellOf = (draw) => (row) => draw(row);
|
|
125
|
+
export { cellOf, columnAlign, columnWidth, formatCell, nextSortDirection, rowKeyOf, selectionState, sortRows, toNumber, toTime, toggleAll, toggleKey };
|
package/dist/lib/text.d.ts
CHANGED
|
@@ -3,3 +3,15 @@
|
|
|
3
3
|
* an HTML element reads as it was meant.
|
|
4
4
|
*/
|
|
5
5
|
export declare function dedent(text: string): string;
|
|
6
|
+
/** A run of a headline: plain, or `accent` for the words written between asterisks. */
|
|
7
|
+
export type StarredPart = {
|
|
8
|
+
text: string;
|
|
9
|
+
accent: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A headline's runs: the words between asterisks are the accented ones, "Workflows that *ask* before they act."
|
|
13
|
+
* A lone asterisk stays as it is; empty runs are left out.
|
|
14
|
+
*/
|
|
15
|
+
export declare function starredParts(text: string): StarredPart[];
|
|
16
|
+
/** A text with its `{placeholders}` filled; one without a value stays as it is. */
|
|
17
|
+
export declare const fillText: (template: string, values: Readonly<Record<string, string>>) => string;
|
package/dist/lib/text.js
CHANGED
|
@@ -10,4 +10,19 @@ function dedent(text) {
|
|
|
10
10
|
const shared = indents.length === 0 ? 0 : Math.min(...indents);
|
|
11
11
|
return lines.map((line) => line.slice(shared)).join("\n");
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
var STAR = "*";
|
|
14
|
+
/**
|
|
15
|
+
* A headline's runs: the words between asterisks are the accented ones, "Workflows that *ask* before they act."
|
|
16
|
+
* A lone asterisk stays as it is; empty runs are left out.
|
|
17
|
+
*/
|
|
18
|
+
function starredParts(text) {
|
|
19
|
+
const pieces = text.split(STAR);
|
|
20
|
+
return (pieces.length % 2 === 1 ? pieces : [...pieces.slice(0, -2), `${pieces.at(-2)}${STAR}${pieces.at(-1)}`]).map((piece, index) => ({
|
|
21
|
+
accent: index % 2 === 1,
|
|
22
|
+
text: piece
|
|
23
|
+
})).filter((part) => part.text !== "");
|
|
24
|
+
}
|
|
25
|
+
var PLACEHOLDER = /\{(\w+)\}/g;
|
|
26
|
+
/** A text with its `{placeholders}` filled; one without a value stays as it is. */
|
|
27
|
+
var fillText = (template, values) => template.replace(PLACEHOLDER, (whole, key) => values[key] ?? whole);
|
|
28
|
+
export { dedent, fillText, starredParts };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { JlTextKey, JlTexts } from '../types/texts.types';
|
|
2
|
+
/**
|
|
3
|
+
* Every text jlib's components show or say on their own, in English: labels for screen readers, buttons, states,
|
|
4
|
+
* the defaults of their text inputs. `{placeholders}` are filled when the text is shown.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_TEXTS: {
|
|
7
|
+
readonly addTag: 'Add at least one tag.';
|
|
8
|
+
readonly addValue: 'Add a value';
|
|
9
|
+
readonly back: 'Back';
|
|
10
|
+
readonly breadcrumb: 'Breadcrumb';
|
|
11
|
+
readonly browse: 'Browse';
|
|
12
|
+
readonly chooseDate: 'Choose a date';
|
|
13
|
+
readonly chooseDateMissing: 'Choose a date.';
|
|
14
|
+
readonly chooseFile: 'Choose a file.';
|
|
15
|
+
readonly chooseOption: 'Choose an option.';
|
|
16
|
+
readonly chooseRating: 'Choose a rating.';
|
|
17
|
+
readonly chosenFiles: 'Chosen files';
|
|
18
|
+
readonly clear: 'Clear';
|
|
19
|
+
readonly clearSearch: 'Clear the search';
|
|
20
|
+
readonly close: 'Close';
|
|
21
|
+
readonly closeMenu: 'Close the menu';
|
|
22
|
+
readonly collapseMenu: 'Collapse the menu';
|
|
23
|
+
readonly commandPalette: 'Command palette';
|
|
24
|
+
readonly commandPlaceholder: 'Type a command or search…';
|
|
25
|
+
readonly commands: 'Commands';
|
|
26
|
+
readonly copied: 'Copied';
|
|
27
|
+
readonly copy: 'Copy';
|
|
28
|
+
readonly decrease: 'Decrease';
|
|
29
|
+
readonly dismiss: 'Dismiss';
|
|
30
|
+
readonly downloadCancel: 'Cancel';
|
|
31
|
+
readonly downloadFailed: 'Not downloaded: {error}';
|
|
32
|
+
readonly downloadPreparing: 'Preparing the file… {time}';
|
|
33
|
+
readonly downloadReceived: '{received} received';
|
|
34
|
+
readonly downloadReceivedOf: '{received} of {total}';
|
|
35
|
+
readonly downloadSaved: 'Saved as {name}, {size}';
|
|
36
|
+
readonly dragToConnect: 'Drag to another node to connect';
|
|
37
|
+
readonly dropFile: 'Drop a file here';
|
|
38
|
+
readonly dropFiles: 'Drop files here';
|
|
39
|
+
readonly expandMenu: 'Expand the menu';
|
|
40
|
+
readonly failedStep: ', failed';
|
|
41
|
+
readonly fileTooLarge: 'is larger than {size}';
|
|
42
|
+
readonly fileTooMany: 'is one too many: the field takes a single file';
|
|
43
|
+
readonly fileUpTo: 'up to {size}';
|
|
44
|
+
readonly fileWrongType: 'is not an accepted type ({types})';
|
|
45
|
+
readonly fillField: 'Please fill in this field.';
|
|
46
|
+
readonly fitView: 'Show every node';
|
|
47
|
+
readonly flow: 'Flow';
|
|
48
|
+
readonly hidePassword: 'Hide the password';
|
|
49
|
+
readonly increase: 'Increase';
|
|
50
|
+
readonly leave: 'Leave';
|
|
51
|
+
readonly leaveText: 'Your changes are lost.';
|
|
52
|
+
readonly loadError: 'This could not be loaded.';
|
|
53
|
+
readonly loading: 'Loading';
|
|
54
|
+
readonly loadingMore: 'Loading…';
|
|
55
|
+
readonly loadStateError: 'Check the connection, then try again. Nothing was changed.';
|
|
56
|
+
readonly mainNavigation: 'Main';
|
|
57
|
+
readonly menu: 'Menu';
|
|
58
|
+
readonly navigation: 'Navigation';
|
|
59
|
+
readonly nextMonth: 'Next month';
|
|
60
|
+
readonly nextPage: 'Next page';
|
|
61
|
+
readonly noCommands: 'No matching commands';
|
|
62
|
+
readonly noData: 'No data';
|
|
63
|
+
readonly noResults: 'No results';
|
|
64
|
+
readonly notifications: 'Notifications';
|
|
65
|
+
readonly pagination: 'Pagination';
|
|
66
|
+
readonly previousMonth: 'Previous month';
|
|
67
|
+
readonly previousPage: 'Previous page';
|
|
68
|
+
readonly progress: 'Progress';
|
|
69
|
+
readonly rating: 'Rating';
|
|
70
|
+
readonly remove: 'Remove';
|
|
71
|
+
readonly removeEdge: 'Remove this edge';
|
|
72
|
+
readonly removeNamed: 'Remove {name}';
|
|
73
|
+
readonly removeValue: 'Remove this value';
|
|
74
|
+
readonly resizePanes: 'Resize the panes';
|
|
75
|
+
readonly retry: 'Try again';
|
|
76
|
+
readonly search: 'Search';
|
|
77
|
+
readonly selectOption: 'Please select one of these options.';
|
|
78
|
+
readonly shortcuts: 'Shortcuts';
|
|
79
|
+
readonly showMore: 'Show more';
|
|
80
|
+
readonly showMoreCount: 'Show {count} more';
|
|
81
|
+
readonly shownOfTotal: '{shown} of {total}';
|
|
82
|
+
readonly showOptions: 'Show the options';
|
|
83
|
+
readonly showPassword: 'Show the password';
|
|
84
|
+
readonly stay: 'Keep editing';
|
|
85
|
+
readonly stepCurrent: ', current step';
|
|
86
|
+
readonly stepDone: ', completed';
|
|
87
|
+
readonly tidyLayout: 'Tidy up the layout';
|
|
88
|
+
readonly zoomIn: 'Zoom in';
|
|
89
|
+
readonly zoomOut: 'Zoom out';
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Gives jlib's components their texts in the page's language, before they are drawn: once at start, as a page
|
|
93
|
+
* reads its own messages. A text left out stays English.
|
|
94
|
+
*/
|
|
95
|
+
export declare function setTexts(texts: JlTexts): void;
|
|
96
|
+
/** One of jlib's texts, in the language the page set, its `{placeholders}` filled. */
|
|
97
|
+
export declare const jlText: (key: JlTextKey, values?: Readonly<Record<string, string>>) => string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { fillText } from "./text.js";
|
|
2
|
+
// src/lib/texts.ts
|
|
3
|
+
/**
|
|
4
|
+
* Every text jlib's components show or say on their own, in English: labels for screen readers, buttons, states,
|
|
5
|
+
* the defaults of their text inputs. `{placeholders}` are filled when the text is shown.
|
|
6
|
+
*/
|
|
7
|
+
var DEFAULT_TEXTS = {
|
|
8
|
+
addTag: "Add at least one tag.",
|
|
9
|
+
addValue: "Add a value",
|
|
10
|
+
back: "Back",
|
|
11
|
+
breadcrumb: "Breadcrumb",
|
|
12
|
+
browse: "Browse",
|
|
13
|
+
chooseDate: "Choose a date",
|
|
14
|
+
chooseDateMissing: "Choose a date.",
|
|
15
|
+
chooseFile: "Choose a file.",
|
|
16
|
+
chooseOption: "Choose an option.",
|
|
17
|
+
chooseRating: "Choose a rating.",
|
|
18
|
+
chosenFiles: "Chosen files",
|
|
19
|
+
clear: "Clear",
|
|
20
|
+
clearSearch: "Clear the search",
|
|
21
|
+
close: "Close",
|
|
22
|
+
closeMenu: "Close the menu",
|
|
23
|
+
collapseMenu: "Collapse the menu",
|
|
24
|
+
commandPalette: "Command palette",
|
|
25
|
+
commandPlaceholder: "Type a command or search…",
|
|
26
|
+
commands: "Commands",
|
|
27
|
+
copied: "Copied",
|
|
28
|
+
copy: "Copy",
|
|
29
|
+
decrease: "Decrease",
|
|
30
|
+
dismiss: "Dismiss",
|
|
31
|
+
downloadCancel: "Cancel",
|
|
32
|
+
downloadFailed: "Not downloaded: {error}",
|
|
33
|
+
downloadPreparing: "Preparing the file… {time}",
|
|
34
|
+
downloadReceived: "{received} received",
|
|
35
|
+
downloadReceivedOf: "{received} of {total}",
|
|
36
|
+
downloadSaved: "Saved as {name}, {size}",
|
|
37
|
+
dragToConnect: "Drag to another node to connect",
|
|
38
|
+
dropFile: "Drop a file here",
|
|
39
|
+
dropFiles: "Drop files here",
|
|
40
|
+
expandMenu: "Expand the menu",
|
|
41
|
+
failedStep: ", failed",
|
|
42
|
+
fileTooLarge: "is larger than {size}",
|
|
43
|
+
fileTooMany: "is one too many: the field takes a single file",
|
|
44
|
+
fileUpTo: "up to {size}",
|
|
45
|
+
fileWrongType: "is not an accepted type ({types})",
|
|
46
|
+
fillField: "Please fill in this field.",
|
|
47
|
+
fitView: "Show every node",
|
|
48
|
+
flow: "Flow",
|
|
49
|
+
hidePassword: "Hide the password",
|
|
50
|
+
increase: "Increase",
|
|
51
|
+
leave: "Leave",
|
|
52
|
+
leaveText: "Your changes are lost.",
|
|
53
|
+
loadError: "This could not be loaded.",
|
|
54
|
+
loading: "Loading",
|
|
55
|
+
loadingMore: "Loading…",
|
|
56
|
+
loadStateError: "Check the connection, then try again. Nothing was changed.",
|
|
57
|
+
mainNavigation: "Main",
|
|
58
|
+
menu: "Menu",
|
|
59
|
+
navigation: "Navigation",
|
|
60
|
+
nextMonth: "Next month",
|
|
61
|
+
nextPage: "Next page",
|
|
62
|
+
noCommands: "No matching commands",
|
|
63
|
+
noData: "No data",
|
|
64
|
+
noResults: "No results",
|
|
65
|
+
notifications: "Notifications",
|
|
66
|
+
pagination: "Pagination",
|
|
67
|
+
previousMonth: "Previous month",
|
|
68
|
+
previousPage: "Previous page",
|
|
69
|
+
progress: "Progress",
|
|
70
|
+
rating: "Rating",
|
|
71
|
+
remove: "Remove",
|
|
72
|
+
removeEdge: "Remove this edge",
|
|
73
|
+
removeNamed: "Remove {name}",
|
|
74
|
+
removeValue: "Remove this value",
|
|
75
|
+
resizePanes: "Resize the panes",
|
|
76
|
+
retry: "Try again",
|
|
77
|
+
search: "Search",
|
|
78
|
+
selectOption: "Please select one of these options.",
|
|
79
|
+
shortcuts: "Shortcuts",
|
|
80
|
+
showMore: "Show more",
|
|
81
|
+
showMoreCount: "Show {count} more",
|
|
82
|
+
shownOfTotal: "{shown} of {total}",
|
|
83
|
+
showOptions: "Show the options",
|
|
84
|
+
showPassword: "Show the password",
|
|
85
|
+
stay: "Keep editing",
|
|
86
|
+
stepCurrent: ", current step",
|
|
87
|
+
stepDone: ", completed",
|
|
88
|
+
tidyLayout: "Tidy up the layout",
|
|
89
|
+
zoomIn: "Zoom in",
|
|
90
|
+
zoomOut: "Zoom out"
|
|
91
|
+
};
|
|
92
|
+
var current = { ...DEFAULT_TEXTS };
|
|
93
|
+
/**
|
|
94
|
+
* Gives jlib's components their texts in the page's language, before they are drawn: once at start, as a page
|
|
95
|
+
* reads its own messages. A text left out stays English.
|
|
96
|
+
*/
|
|
97
|
+
function setTexts(texts) {
|
|
98
|
+
Object.assign(current, texts);
|
|
99
|
+
}
|
|
100
|
+
/** One of jlib's texts, in the language the page set, its `{placeholders}` filled. */
|
|
101
|
+
var jlText = (key, values = {}) => fillText(current[key], values);
|
|
102
|
+
export { DEFAULT_TEXTS, jlText, setTexts };
|
package/dist/lib/time.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A duration in the units that read best: "< 1 sec" under a second (never "0.0 s"), then seconds, minutes, hours
|
|
3
|
-
* and minutes, days and hours.
|
|
3
|
+
* and minutes, days and hours. The unit is chosen once rounded: 59.6 seconds is "1 min", not "60 secs". Not a
|
|
4
|
+
* number gives "".
|
|
4
5
|
*/
|
|
5
6
|
export declare function formatDuration(milliseconds: number, locale?: string): string;
|
|
6
7
|
/**
|
|
7
|
-
* When something happened, from `now`: "now", "5 minutes ago", "3 hours ago", "yesterday"
|
|
8
|
-
* date ("12 Mar", with the year when it is not this year's). Times to
|
|
8
|
+
* When something happened, from `now`: "now", "5 minutes ago", "3 hours ago" the same day, "yesterday" or "3 days
|
|
9
|
+
* ago" by the calendar within a week, then the date ("12 Mar", with the year when it is not this year's). Times to
|
|
10
|
+
* come read the same way: "in 5 minutes". A date that is not one gives "".
|
|
9
11
|
*/
|
|
10
12
|
export declare function formatRelativeTime(date: Date | number | string, now: Date | number, locale?: string): string;
|