@mdbase-dev/ui 0.1.0-beta.119 → 0.1.0-beta.121

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/Dialog.d.ts CHANGED
@@ -3,10 +3,11 @@ import { type JSX, type ReactNode } from "react";
3
3
  * A modal dialog on the native <dialog>: the browser keeps focus inside it and the page
4
4
  * behind it inert. Escape, the close button or a press on the backdrop closes it.
5
5
  */
6
- export declare function Dialog({ open, onClose, title, className, children }: {
6
+ export declare function Dialog({ open, onClose, title, className, closeDisabled, children }: {
7
7
  readonly open: boolean;
8
8
  readonly onClose: () => void;
9
9
  readonly title: string;
10
10
  readonly className?: string | undefined;
11
+ readonly closeDisabled?: boolean;
11
12
  readonly children: ReactNode;
12
13
  }): JSX.Element;
package/dist/Dialog.js CHANGED
@@ -4,8 +4,12 @@ import { useLayoutEffect, useRef } from "react";
4
4
  * A modal dialog on the native <dialog>: the browser keeps focus inside it and the page
5
5
  * behind it inert. Escape, the close button or a press on the backdrop closes it.
6
6
  */
7
- export function Dialog({ open, onClose, title, className, children }) {
7
+ export function Dialog({ open, onClose, title, className, closeDisabled = false, children }) {
8
8
  const ref = useRef(null);
9
+ useLayoutEffect(() => {
10
+ const previous = document.activeElement instanceof HTMLElement ? document.activeElement : undefined;
11
+ return () => { ref.current?.close(); previous?.focus(); };
12
+ }, []);
9
13
  // A layout effect, so the dialog is open before fields inside it ask to be focused.
10
14
  useLayoutEffect(() => {
11
15
  const dialog = ref.current;
@@ -16,8 +20,13 @@ export function Dialog({ open, onClose, title, className, children }) {
16
20
  else if (!open && dialog.open)
17
21
  dialog.close();
18
22
  }, [open]);
19
- return _jsx("dialog", { ref: ref, className: ["mdbase-dialog", className].filter(Boolean).join(" "), "aria-label": title, onClose: onClose, onMouseDown: (event) => {
20
- if (event.target === event.currentTarget)
23
+ return _jsx("dialog", { ref: ref, className: ["mdbase-dialog", className].filter(Boolean).join(" "), "aria-label": title, onCancel: (event) => {
24
+ event.preventDefault();
25
+ if (!closeDisabled)
26
+ onClose();
27
+ }, onClose: () => { if (!closeDisabled)
28
+ onClose(); }, onMouseDown: (event) => {
29
+ if (!closeDisabled && event.target === event.currentTarget)
21
30
  onClose();
22
- }, children: open && _jsxs("div", { className: "mdbase-dialog-body", children: [_jsxs("header", { className: "mdbase-dialog-header", children: [_jsx("h2", { children: title }), _jsx("button", { type: "button", className: "mdbase-icon-button", onClick: onClose, "aria-label": "Close", children: _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) }) })] }), children] }) });
31
+ }, children: open && _jsxs("div", { className: "mdbase-dialog-body", children: [_jsxs("header", { className: "mdbase-dialog-header", children: [_jsx("h2", { children: title }), _jsx("button", { type: "button", className: "mdbase-icon-button", disabled: closeDisabled, onClick: onClose, "aria-label": "Close", children: _jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", children: _jsx("path", { d: "m6 6 12 12M18 6 6 18" }) }) })] }), children] }) });
23
32
  }
@@ -0,0 +1,7 @@
1
+ /** A permission review survives polling, but never silently rebases onto changed access. */
2
+ export declare function usePermissionSelection(id: string, allowed: readonly string[]): {
3
+ selected: string[];
4
+ needsReview: boolean;
5
+ setSelected: (selected: string[] | ((current: string[]) => string[])) => void;
6
+ acknowledge: () => void;
7
+ };
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from "react";
2
+ /** A permission review survives polling, but never silently rebases onto changed access. */
3
+ export function usePermissionSelection(id, allowed) {
4
+ const key = JSON.stringify([id, [...allowed].sort()]);
5
+ const [review, setReview] = useState(() => ({ key, selected: [...allowed], needsReview: false }));
6
+ const changedExternally = review.key !== key;
7
+ const needsReview = changedExternally && JSON.stringify([id, [...review.selected].sort()]) !== key;
8
+ useEffect(() => {
9
+ if (changedExternally)
10
+ setReview({ key, selected: [...allowed], needsReview });
11
+ }, [key, changedExternally, needsReview]);
12
+ return {
13
+ selected: changedExternally ? [...allowed] : review.selected,
14
+ needsReview: changedExternally ? needsReview : review.needsReview,
15
+ setSelected: (selected) => setReview((current) => ({
16
+ ...current,
17
+ selected: typeof selected === "function" ? selected(current.selected) : selected
18
+ })),
19
+ acknowledge: () => setReview((current) => ({ ...current, needsReview: false }))
20
+ };
21
+ }
@@ -0,0 +1,8 @@
1
+ export type DiffKind = "same" | "local" | "remote" | "omitted";
2
+ export interface DiffLine {
3
+ kind: DiffKind;
4
+ text: string;
5
+ localLine?: number;
6
+ remoteLine?: number;
7
+ }
8
+ export declare function compareLines(localText: string, remoteText: string): DiffLine[];
@@ -0,0 +1,89 @@
1
+ const MAX_MATRIX_LINES = 240;
2
+ const MAX_VISIBLE_CHANGES = 80;
3
+ const CONTEXT_LINES = 3;
4
+ export function compareLines(localText, remoteText) {
5
+ const local = localText.split("\n");
6
+ const remote = remoteText.split("\n");
7
+ const compared = local.length <= MAX_MATRIX_LINES && remote.length <= MAX_MATRIX_LINES
8
+ ? lcsDiff(local, remote)
9
+ : boundedDiff(local, remote);
10
+ return compactDiff(compared);
11
+ }
12
+ function lcsDiff(local, remote) {
13
+ const lengths = Array.from({ length: local.length + 1 }, () => new Uint16Array(remote.length + 1));
14
+ for (let localIndex = local.length - 1; localIndex >= 0; localIndex -= 1) {
15
+ for (let remoteIndex = remote.length - 1; remoteIndex >= 0; remoteIndex -= 1) {
16
+ lengths[localIndex][remoteIndex] = local[localIndex] === remote[remoteIndex]
17
+ ? lengths[localIndex + 1][remoteIndex + 1] + 1
18
+ : Math.max(lengths[localIndex + 1][remoteIndex], lengths[localIndex][remoteIndex + 1]);
19
+ }
20
+ }
21
+ const lines = [];
22
+ let localIndex = 0;
23
+ let remoteIndex = 0;
24
+ while (localIndex < local.length || remoteIndex < remote.length) {
25
+ if (localIndex < local.length && remoteIndex < remote.length && local[localIndex] === remote[remoteIndex]) {
26
+ lines.push({ kind: "same", text: local[localIndex], localLine: localIndex + 1, remoteLine: remoteIndex + 1 });
27
+ localIndex += 1;
28
+ remoteIndex += 1;
29
+ }
30
+ else if (remoteIndex >= remote.length || (localIndex < local.length && lengths[localIndex + 1][remoteIndex] >= lengths[localIndex][remoteIndex + 1])) {
31
+ lines.push({ kind: "local", text: local[localIndex], localLine: localIndex + 1 });
32
+ localIndex += 1;
33
+ }
34
+ else {
35
+ lines.push({ kind: "remote", text: remote[remoteIndex], remoteLine: remoteIndex + 1 });
36
+ remoteIndex += 1;
37
+ }
38
+ }
39
+ return lines;
40
+ }
41
+ function boundedDiff(local, remote) {
42
+ let prefix = 0;
43
+ while (prefix < local.length && prefix < remote.length && local[prefix] === remote[prefix])
44
+ prefix += 1;
45
+ let suffix = 0;
46
+ while (suffix < local.length - prefix
47
+ && suffix < remote.length - prefix
48
+ && local[local.length - suffix - 1] === remote[remote.length - suffix - 1])
49
+ suffix += 1;
50
+ return [
51
+ ...local.slice(0, prefix).map((text, index) => ({ kind: "same", text, localLine: index + 1, remoteLine: index + 1 })),
52
+ ...local.slice(prefix, local.length - suffix).map((text, index) => ({ kind: "local", text, localLine: prefix + index + 1 })),
53
+ ...remote.slice(prefix, remote.length - suffix).map((text, index) => ({ kind: "remote", text, remoteLine: prefix + index + 1 })),
54
+ ...local.slice(local.length - suffix).map((text, index) => ({
55
+ kind: "same",
56
+ text,
57
+ localLine: local.length - suffix + index + 1,
58
+ remoteLine: remote.length - suffix + index + 1
59
+ }))
60
+ ];
61
+ }
62
+ function compactDiff(lines) {
63
+ const changes = lines.reduce((count, line) => count + (line.kind === "same" ? 0 : 1), 0);
64
+ if (changes === 0)
65
+ return [];
66
+ const visible = [];
67
+ let shownChanges = 0;
68
+ for (let index = 0; index < lines.length; index += 1) {
69
+ const line = lines[index];
70
+ if (line.kind !== "same") {
71
+ if (shownChanges >= MAX_VISIBLE_CHANGES) {
72
+ visible.push({ kind: "omitted", text: `${changes - shownChanges} more changed lines` });
73
+ break;
74
+ }
75
+ visible.push(line);
76
+ shownChanges += 1;
77
+ continue;
78
+ }
79
+ const previousChange = lines.slice(Math.max(0, index - CONTEXT_LINES), index).some((candidate) => candidate.kind !== "same");
80
+ const nextChange = lines.slice(index + 1, index + CONTEXT_LINES + 1).some((candidate) => candidate.kind !== "same");
81
+ if (previousChange || nextChange) {
82
+ visible.push(line);
83
+ }
84
+ else if (visible.at(-1)?.kind !== "omitted") {
85
+ visible.push({ kind: "omitted", text: "Unchanged lines" });
86
+ }
87
+ }
88
+ return visible;
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/ui",
3
- "version": "0.1.0-beta.119",
3
+ "version": "0.1.0-beta.121",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,6 +38,10 @@
38
38
  "types": "./dist/Dialog.d.ts",
39
39
  "import": "./dist/Dialog.js"
40
40
  },
41
+ "./permission-selection": {
42
+ "types": "./dist/permission-selection.d.ts",
43
+ "import": "./dist/permission-selection.js"
44
+ },
41
45
  "./popover": {
42
46
  "types": "./dist/popover.d.ts",
43
47
  "import": "./dist/popover.js"
@@ -54,6 +58,10 @@
54
58
  "types": "./dist/Select.d.ts",
55
59
  "import": "./dist/Select.js"
56
60
  },
61
+ "./text-diff": {
62
+ "types": "./dist/text-diff.d.ts",
63
+ "import": "./dist/text-diff.js"
64
+ },
57
65
  "./theme": {
58
66
  "types": "./dist/theme.d.ts",
59
67
  "import": "./dist/theme.js"