@maple-kit/ui 0.1.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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +39 -0
- package/dist/composer/actions.d.ts +15 -0
- package/dist/composer/actions.js +59 -0
- package/dist/composer/attachments.d.ts +33 -0
- package/dist/composer/attachments.js +166 -0
- package/dist/composer/badge.d.ts +13 -0
- package/dist/composer/badge.js +25 -0
- package/dist/composer/body.d.ts +13 -0
- package/dist/composer/body.js +119 -0
- package/dist/composer/composer.d.ts +23 -0
- package/dist/composer/composer.js +122 -0
- package/dist/composer/css.js +559 -0
- package/dist/composer/detail.d.ts +32 -0
- package/dist/composer/detail.js +102 -0
- package/dist/composer/emoji.d.ts +54 -0
- package/dist/composer/emoji.js +131 -0
- package/dist/composer/index.d.ts +15 -0
- package/dist/composer/index.js +15 -0
- package/dist/composer/inset.js +20 -0
- package/dist/composer/leave.d.ts +19 -0
- package/dist/composer/leave.js +34 -0
- package/dist/composer/peek.d.ts +21 -0
- package/dist/composer/peek.js +29 -0
- package/dist/composer/phrase.d.ts +27 -0
- package/dist/composer/phrase.js +30 -0
- package/dist/composer/picker-emoji.d.ts +34 -0
- package/dist/composer/picker-emoji.js +48 -0
- package/dist/composer/prompt.js +31 -0
- package/dist/composer/scope.d.ts +11 -0
- package/dist/composer/scope.js +16 -0
- package/dist/composer/target.d.ts +13 -0
- package/dist/composer/target.js +45 -0
- package/dist/context.d.ts +21 -0
- package/dist/context.js +16 -0
- package/dist/data.d.ts +52 -0
- package/dist/data.js +30 -0
- package/dist/icons/branch.d.ts +4 -0
- package/dist/icons/branch.js +8 -0
- package/dist/icons/cog.d.ts +4 -0
- package/dist/icons/cog.js +8 -0
- package/dist/icons/commit.d.ts +4 -0
- package/dist/icons/commit.js +5 -0
- package/dist/icons/crossfade.d.ts +15 -0
- package/dist/icons/crossfade.js +27 -0
- package/dist/icons/element.d.ts +4 -0
- package/dist/icons/element.js +5 -0
- package/dist/icons/icon.d.ts +24 -0
- package/dist/icons/icon.js +27 -0
- package/dist/icons/index.d.ts +13 -0
- package/dist/icons/index.js +13 -0
- package/dist/icons/noplace.d.ts +4 -0
- package/dist/icons/noplace.js +5 -0
- package/dist/icons/region.d.ts +4 -0
- package/dist/icons/region.js +8 -0
- package/dist/icons/smiley.d.ts +4 -0
- package/dist/icons/smiley.js +8 -0
- package/dist/icons/sparkle.d.ts +4 -0
- package/dist/icons/sparkle.js +8 -0
- package/dist/icons/target.d.ts +4 -0
- package/dist/icons/target.js +5 -0
- package/dist/icons/text.d.ts +4 -0
- package/dist/icons/text.js +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/island/account.d.ts +10 -0
- package/dist/island/account.js +52 -0
- package/dist/island/comments.d.ts +24 -0
- package/dist/island/comments.js +40 -0
- package/dist/island/content.d.ts +10 -0
- package/dist/island/content.js +26 -0
- package/dist/island/context.d.ts +58 -0
- package/dist/island/context.js +23 -0
- package/dist/island/css.d.ts +13 -0
- package/dist/island/css.js +1020 -0
- package/dist/island/drag.d.ts +10 -0
- package/dist/island/drag.js +77 -0
- package/dist/island/filters.d.ts +10 -0
- package/dist/island/filters.js +63 -0
- package/dist/island/header.d.ts +32 -0
- package/dist/island/header.js +47 -0
- package/dist/island/index.d.ts +18 -0
- package/dist/island/index.js +18 -0
- package/dist/island/island.d.ts +12 -0
- package/dist/island/island.js +111 -0
- package/dist/island/item.d.ts +12 -0
- package/dist/island/item.js +158 -0
- package/dist/island/language.d.ts +55 -0
- package/dist/island/language.js +125 -0
- package/dist/island/leaf.js +17 -0
- package/dist/island/list.d.ts +12 -0
- package/dist/island/list.js +31 -0
- package/dist/island/new-comment.d.ts +18 -0
- package/dist/island/new-comment.js +56 -0
- package/dist/island/settings.d.ts +10 -0
- package/dist/island/settings.js +134 -0
- package/dist/island/stagger.d.ts +12 -0
- package/dist/island/stagger.js +4 -0
- package/dist/island/time.d.ts +17 -0
- package/dist/island/time.js +29 -0
- package/dist/island/trigger.d.ts +10 -0
- package/dist/island/trigger.js +39 -0
- package/dist/language.d.ts +45 -0
- package/dist/language.js +66 -0
- package/dist/maple.d.ts +25 -0
- package/dist/maple.js +65 -0
- package/dist/marks/avatar.d.ts +16 -0
- package/dist/marks/avatar.js +36 -0
- package/dist/marks/css.d.ts +12 -0
- package/dist/marks/css.js +371 -0
- package/dist/marks/frame.d.ts +25 -0
- package/dist/marks/frame.js +53 -0
- package/dist/marks/geometry.d.ts +55 -0
- package/dist/marks/geometry.js +61 -0
- package/dist/marks/index.d.ts +14 -0
- package/dist/marks/index.js +14 -0
- package/dist/marks/label.d.ts +23 -0
- package/dist/marks/label.js +31 -0
- package/dist/marks/layer.d.ts +16 -0
- package/dist/marks/layer.js +194 -0
- package/dist/marks/leaf.d.ts +18 -0
- package/dist/marks/leaf.js +7 -0
- package/dist/marks/mark.d.ts +28 -0
- package/dist/marks/mark.js +43 -0
- package/dist/marks/nudge.d.ts +50 -0
- package/dist/marks/nudge.js +76 -0
- package/dist/marks/paint.d.ts +11 -0
- package/dist/marks/paint.js +19 -0
- package/dist/marks/placement.d.ts +24 -0
- package/dist/marks/placement.js +28 -0
- package/dist/marks/ring.d.ts +28 -0
- package/dist/marks/ring.js +85 -0
- package/dist/marks/shape.d.ts +21 -0
- package/dist/marks/shape.js +51 -0
- package/dist/notice/css.d.ts +11 -0
- package/dist/notice/css.js +74 -0
- package/dist/notice/index.d.ts +4 -0
- package/dist/notice/index.js +4 -0
- package/dist/notice/language.d.ts +15 -0
- package/dist/notice/language.js +14 -0
- package/dist/notice/notice.d.ts +14 -0
- package/dist/notice/notice.js +49 -0
- package/dist/part.d.ts +9 -0
- package/dist/part.js +11 -0
- package/dist/picker/css.d.ts +13 -0
- package/dist/picker/css.js +141 -0
- package/dist/picker/index.d.ts +6 -0
- package/dist/picker/index.js +6 -0
- package/dist/picker/language.d.ts +14 -0
- package/dist/picker/language.js +17 -0
- package/dist/picker/picker.d.ts +18 -0
- package/dist/picker/picker.js +164 -0
- package/dist/picker/session.d.ts +16 -0
- package/dist/picker/session.js +35 -0
- package/dist/picker/target.d.ts +23 -0
- package/dist/picker/target.js +37 -0
- package/dist/root.d.ts +42 -0
- package/dist/root.js +75 -0
- package/dist/shots.js +32 -0
- package/dist/slot.d.ts +28 -0
- package/dist/slot.js +52 -0
- package/dist/slots.d.ts +24 -0
- package/dist/slots.js +75 -0
- package/dist/stylesheet.d.ts +15 -0
- package/dist/stylesheet.js +188 -0
- package/dist/theme.d.ts +20 -0
- package/dist/theme.js +13 -0
- package/dist/tip.d.ts +40 -0
- package/dist/tip.js +82 -0
- package/dist/tokens.d.ts +28 -0
- package/dist/tokens.js +180 -0
- package/package.json +56 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useMapleUi } from "../context.js";
|
|
2
|
+
import { useShots } from "../shots.js";
|
|
3
|
+
import { PICK_ORDER } from "../island/language.js";
|
|
4
|
+
import { ringLabel } from "../marks/label.js";
|
|
5
|
+
import { MapleTargetRing } from "../marks/ring.js";
|
|
6
|
+
import { PICKER_COPY, PICK_HINTS, pickerLabel } from "./language.js";
|
|
7
|
+
import { startSession } from "./session.js";
|
|
8
|
+
import { elementOf, targetFor } from "./target.js";
|
|
9
|
+
import { Fragment, createElement, useCallback, useEffect, useRef, useState } from "react";
|
|
10
|
+
import { captureContext, watchPickKeys } from "@maple-kit/core/overlay";
|
|
11
|
+
import { useMaple, useMapleClient } from "@maple-kit/react";
|
|
12
|
+
import { captureElement } from "@maple-kit/core/screenshot";
|
|
13
|
+
import { sourceFor } from "@maple-kit/core/anchor";
|
|
14
|
+
//#region src/picker/picker.ts
|
|
15
|
+
const PART = "Maple.Picker";
|
|
16
|
+
function MaplePicker(props) {
|
|
17
|
+
const { detail, pick } = useMaple();
|
|
18
|
+
const client = useMapleClient();
|
|
19
|
+
const { container, root } = useMapleUi(PART);
|
|
20
|
+
const [hovered, setHovered] = useState();
|
|
21
|
+
const [band, setBand] = useState();
|
|
22
|
+
const kind = pick.kind;
|
|
23
|
+
const commit = useCommit();
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!pick.armed || !kind) return;
|
|
26
|
+
const stop = new AbortController();
|
|
27
|
+
startSession(kind, stop.signal, {
|
|
28
|
+
ignore: (event) => onOwnControl(event, root),
|
|
29
|
+
onHover: setHovered,
|
|
30
|
+
onDraw: setBand,
|
|
31
|
+
onPick: (found) => commit(found)
|
|
32
|
+
});
|
|
33
|
+
return () => {
|
|
34
|
+
stop.abort();
|
|
35
|
+
setHovered(void 0);
|
|
36
|
+
setBand(void 0);
|
|
37
|
+
};
|
|
38
|
+
}, [
|
|
39
|
+
commit,
|
|
40
|
+
kind,
|
|
41
|
+
pick.armed,
|
|
42
|
+
root
|
|
43
|
+
]);
|
|
44
|
+
useKeys(pick.armed, kind, client);
|
|
45
|
+
if (!pick.armed || !kind) return null;
|
|
46
|
+
return createElement(Fragment, null, kind === "text" ? null : createElement("div", { className: shieldClass(props, kind) }), band ? createElement("div", {
|
|
47
|
+
className: "mk-band",
|
|
48
|
+
style: bandStyle(band)
|
|
49
|
+
}) : null, createElement(MapleTargetRing, {
|
|
50
|
+
state: "hovered",
|
|
51
|
+
target: targetOf(hovered),
|
|
52
|
+
...named(hovered, container, detail)
|
|
53
|
+
}), props.hint === false ? null : createElement(Hint, { kind }));
|
|
54
|
+
}
|
|
55
|
+
function useCommit() {
|
|
56
|
+
const client = useMapleClient();
|
|
57
|
+
const shots = useShots();
|
|
58
|
+
return useCallback((pick) => {
|
|
59
|
+
const target = targetFor(pick, { page: captureContext() });
|
|
60
|
+
const element = elementOf(pick);
|
|
61
|
+
if (shots && element) capture(element, shots);
|
|
62
|
+
client.disarm();
|
|
63
|
+
if (target) client.openComposer(target);
|
|
64
|
+
}, [client, shots]);
|
|
65
|
+
}
|
|
66
|
+
function capture(element, shots) {
|
|
67
|
+
captureElement(element, { page: true }).then((blob) => shots.put({
|
|
68
|
+
status: "taken",
|
|
69
|
+
image: {
|
|
70
|
+
blob,
|
|
71
|
+
type: blob.type
|
|
72
|
+
}
|
|
73
|
+
}), (error) => shots.put({
|
|
74
|
+
status: "failed",
|
|
75
|
+
reason: reasonOf(error)
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
function reasonOf(error) {
|
|
79
|
+
return error instanceof Error ? error.message : String(error);
|
|
80
|
+
}
|
|
81
|
+
function useKeys(armed, kind, client) {
|
|
82
|
+
const latest = useRef(kind);
|
|
83
|
+
latest.current = kind;
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!armed) return;
|
|
86
|
+
const stop = new AbortController();
|
|
87
|
+
watchPickKeys({
|
|
88
|
+
signal: stop.signal,
|
|
89
|
+
onCancel: () => client.disarm(),
|
|
90
|
+
onCycle: () => client.arm(next(latest.current))
|
|
91
|
+
});
|
|
92
|
+
return () => stop.abort();
|
|
93
|
+
}, [armed, client]);
|
|
94
|
+
}
|
|
95
|
+
function onOwnControl(event, root) {
|
|
96
|
+
const target = event.composedPath()[0];
|
|
97
|
+
if (!(target instanceof Element) || target.getRootNode() !== root) return false;
|
|
98
|
+
return !target.classList.contains("mk-shield");
|
|
99
|
+
}
|
|
100
|
+
function next(kind) {
|
|
101
|
+
const at = kind === void 0 ? -1 : PICK_ORDER.indexOf(kind);
|
|
102
|
+
return PICK_ORDER[(at + 1) % PICK_ORDER.length] ?? "element";
|
|
103
|
+
}
|
|
104
|
+
function Hint(props) {
|
|
105
|
+
const client = useMapleClient();
|
|
106
|
+
return createElement("div", {
|
|
107
|
+
className: "mk-pick-bar",
|
|
108
|
+
role: "status",
|
|
109
|
+
"aria-label": pickerLabel(props.kind)
|
|
110
|
+
}, createElement("span", { className: "mk-pick-say" }, PICK_HINTS[props.kind]), createElement("span", { className: "mk-pick-kinds" }, ...PICK_ORDER.map((kind) => createElement("button", {
|
|
111
|
+
key: kind,
|
|
112
|
+
type: "button",
|
|
113
|
+
className: "mk-pick-kind mk-hit",
|
|
114
|
+
"aria-pressed": kind === props.kind,
|
|
115
|
+
onClick: () => client.arm(kind)
|
|
116
|
+
}, kind))), createElement("button", {
|
|
117
|
+
type: "button",
|
|
118
|
+
className: "mk-pick-stop mk-hit",
|
|
119
|
+
onClick: () => client.disarm()
|
|
120
|
+
}, PICKER_COPY.cancel, createElement("kbd", { className: "mk-kbd" }, PICKER_COPY.cancelHint)));
|
|
121
|
+
}
|
|
122
|
+
function shieldClass(props, kind) {
|
|
123
|
+
return [
|
|
124
|
+
"mk-shield",
|
|
125
|
+
kind === "region" && "mk-shield-drag",
|
|
126
|
+
props.className
|
|
127
|
+
].filter(Boolean).join(" ");
|
|
128
|
+
}
|
|
129
|
+
function targetOf(pick) {
|
|
130
|
+
if (!pick) return null;
|
|
131
|
+
if (pick.kind === "element") return pick.element;
|
|
132
|
+
return pick.kind === "text" ? pick.range : null;
|
|
133
|
+
}
|
|
134
|
+
function named(pick, container, detail) {
|
|
135
|
+
if (!pick) return {};
|
|
136
|
+
const found = targetFor(pick, { root: container.ownerDocument });
|
|
137
|
+
const on = {
|
|
138
|
+
...found ? { anchor: found.anchor } : {},
|
|
139
|
+
element: elementOf(pick) ?? null
|
|
140
|
+
};
|
|
141
|
+
const note = detail === "developer" ? sourceFor(on) : void 0;
|
|
142
|
+
const label = found === void 0 ? void 0 : ringLabel({
|
|
143
|
+
kind: pick.kind,
|
|
144
|
+
element: on.element,
|
|
145
|
+
...found.label === void 0 ? {} : { named: found.label }
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
...label === void 0 ? {} : { label },
|
|
149
|
+
...note === void 0 ? {} : { note }
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function bandStyle(rect) {
|
|
153
|
+
return {
|
|
154
|
+
"--mk-x": px(rect.x),
|
|
155
|
+
"--mk-y": px(rect.y),
|
|
156
|
+
"--mk-w": px(rect.width),
|
|
157
|
+
"--mk-h": px(rect.height)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function px(value) {
|
|
161
|
+
return `${String(value)}px`;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { MaplePicker, onOwnControl };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PickKind } from "@maple-kit/core/client";
|
|
2
|
+
import { Pick, Rect } from "@maple-kit/core/overlay";
|
|
3
|
+
//#region src/picker/session.d.ts
|
|
4
|
+
/** What a running session reports back. */
|
|
5
|
+
export interface SessionHandlers {
|
|
6
|
+
/** True for an event on the overlay's own controls, which is never a pick. */
|
|
7
|
+
readonly ignore: (event: Event) => boolean;
|
|
8
|
+
/** The pointer moved over something pickable, or off everything. */
|
|
9
|
+
readonly onHover: (pick: Pick | undefined) => void;
|
|
10
|
+
/** A rectangle being dragged, so the band can be drawn as it grows. */
|
|
11
|
+
readonly onDraw: (rect: Rect | undefined) => void;
|
|
12
|
+
readonly onPick: (pick: Pick) => void;
|
|
13
|
+
}
|
|
14
|
+
/** Starts the listeners for one kind. Aborting the signal removes every one. */
|
|
15
|
+
export declare function startSession(kind: PickKind, signal: AbortSignal, handlers: SessionHandlers): void;
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { startElementPicking, startRegionPicking, startTextPicking } from "@maple-kit/core/overlay";
|
|
2
|
+
//#region src/picker/session.ts
|
|
3
|
+
function startSession(kind, signal, handlers) {
|
|
4
|
+
if (kind === "element") return element(signal, handlers);
|
|
5
|
+
return kind === "region" ? region(signal, handlers) : text(signal, handlers);
|
|
6
|
+
}
|
|
7
|
+
function element(signal, handlers) {
|
|
8
|
+
startElementPicking({
|
|
9
|
+
signal,
|
|
10
|
+
ignore: handlers.ignore,
|
|
11
|
+
onHover: handlers.onHover,
|
|
12
|
+
onPick: handlers.onPick
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function region(signal, handlers) {
|
|
16
|
+
startRegionPicking({
|
|
17
|
+
signal,
|
|
18
|
+
ignore: handlers.ignore,
|
|
19
|
+
onDraw: handlers.onDraw,
|
|
20
|
+
onPick: (pick) => {
|
|
21
|
+
handlers.onDraw(void 0);
|
|
22
|
+
handlers.onPick(pick);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function text(signal, handlers) {
|
|
27
|
+
startTextPicking({
|
|
28
|
+
signal,
|
|
29
|
+
ignore: handlers.ignore,
|
|
30
|
+
onHover: handlers.onHover,
|
|
31
|
+
onPick: handlers.onPick
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { startSession };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComposerTarget } from "@maple-kit/core/client";
|
|
2
|
+
import { PageContext, Pick } from "@maple-kit/core/overlay";
|
|
3
|
+
//#region src/picker/target.d.ts
|
|
4
|
+
/** What the anchor is described against, and the page as it was at pick time. */
|
|
5
|
+
export interface TargetOptions {
|
|
6
|
+
/** Defaults to the picked element's own document. */
|
|
7
|
+
readonly root?: ParentNode;
|
|
8
|
+
/** Recorded with the comment. Captured by the caller, so a test can hand one in. */
|
|
9
|
+
readonly page?: PageContext;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The element a pick is about. For a region it is the box the rectangle was
|
|
13
|
+
* drawn inside, which is what its fractions are measured against and not what
|
|
14
|
+
* the comment is about.
|
|
15
|
+
*/
|
|
16
|
+
export declare function elementOf(pick: Pick): Element | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The target, or nothing when a passage sat in no element at all. A region
|
|
19
|
+
* always has one: `containerFor` walks up to `documentElement` if it must, and
|
|
20
|
+
* a rectangle over the page's own background is still a rectangle.
|
|
21
|
+
*/
|
|
22
|
+
export declare function targetFor(pick: Pick, options?: TargetOptions): ComposerTarget | undefined;
|
|
23
|
+
//#endregion
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { toCommentContext } from "@maple-kit/core/overlay";
|
|
2
|
+
import { describeElement, describeRange, labelFor, regionOf } from "@maple-kit/core/anchor";
|
|
3
|
+
//#region src/picker/target.ts
|
|
4
|
+
function elementOf(pick) {
|
|
5
|
+
return pick.kind === "text" ? rangeElement(pick.range) : pick.element;
|
|
6
|
+
}
|
|
7
|
+
function targetFor(pick, options = {}) {
|
|
8
|
+
const element = elementOf(pick);
|
|
9
|
+
if (!element) return void 0;
|
|
10
|
+
const anchor = anchorFor(pick, element, options.root);
|
|
11
|
+
const label = labelFor({
|
|
12
|
+
element,
|
|
13
|
+
anchor
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
kind: pick.kind,
|
|
17
|
+
anchor,
|
|
18
|
+
...label === void 0 ? {} : { label },
|
|
19
|
+
...options.page === void 0 ? {} : { context: toCommentContext(options.page) }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function anchorFor(pick, element, root) {
|
|
23
|
+
const options = root === void 0 ? {} : { root };
|
|
24
|
+
if (pick.kind === "text") return describeRange(pick.range, options);
|
|
25
|
+
const described = describeElement(element, options);
|
|
26
|
+
if (pick.kind !== "region") return described;
|
|
27
|
+
return {
|
|
28
|
+
...described,
|
|
29
|
+
region: regionOf(pick.rect, element.getBoundingClientRect())
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function rangeElement(range) {
|
|
33
|
+
const node = range.commonAncestorContainer;
|
|
34
|
+
return node instanceof Element ? node : node.parentElement ?? void 0;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { elementOf, targetFor };
|
package/dist/root.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { MapleClient, MapleClientOptions, MapleProps, ThemePreference } from "@maple-kit/core/client";
|
|
3
|
+
//#region src/root.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* `auto` is the opposite of the host's scheme, not the same as it.
|
|
6
|
+
*
|
|
7
|
+
* Re-exported from `@maple-kit/core/client` so a part can name the type
|
|
8
|
+
* without importing the controller's entrypoint for it.
|
|
9
|
+
*/
|
|
10
|
+
type ThemePreference$1 = ThemePreference;
|
|
11
|
+
/** How the overlay is mounted, and what it is pointed at. */
|
|
12
|
+
export interface MapleRootProps extends MapleProps {
|
|
13
|
+
/** The branch the comments belong to. */
|
|
14
|
+
readonly branch: string;
|
|
15
|
+
/**
|
|
16
|
+
* What a reviewer calls this surface — a ticket, or the branch shortened to
|
|
17
|
+
* a hostname label. Read wherever the branch would be; the branch stays.
|
|
18
|
+
*/
|
|
19
|
+
readonly label?: string;
|
|
20
|
+
/** The commit the preview is serving, where the build stamps one. */
|
|
21
|
+
readonly commit?: string;
|
|
22
|
+
readonly children?: ReactNode;
|
|
23
|
+
/** Added to the overlay's own layer, inside the shadow root. */
|
|
24
|
+
readonly className?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The default the viewer starts on, `auto` unless given. Their own choice,
|
|
27
|
+
* made in the island's settings, is remembered per origin and wins.
|
|
28
|
+
*/
|
|
29
|
+
readonly theme?: ThemePreference$1;
|
|
30
|
+
/** Everything else the controller takes. The three above win over it. */
|
|
31
|
+
readonly options?: Omit<MapleClientOptions, "branch" | "label" | "commit">;
|
|
32
|
+
/** A controller the caller built and owns, started and destroyed by them. */
|
|
33
|
+
readonly client?: MapleClient;
|
|
34
|
+
/** For a host that mounts Maple with a script tag rather than a bundle. */
|
|
35
|
+
readonly nonce?: string;
|
|
36
|
+
/** Where the overlay's container is appended. Defaults to `document.body`. */
|
|
37
|
+
readonly parent?: Element;
|
|
38
|
+
}
|
|
39
|
+
/** Mounts the overlay and puts its shadow root and controller in scope. */
|
|
40
|
+
export declare const MapleRoot: import("react").ForwardRefExoticComponent<MapleRootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { ThemePreference$1 as ThemePreference };
|
package/dist/root.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { MapleUiContext } from "./context.js";
|
|
2
|
+
import { ShotContext, createShotStore } from "./shots.js";
|
|
3
|
+
import { OVERLAY_CSS, SCHEME_ATTRIBUTE } from "./stylesheet.js";
|
|
4
|
+
import { useOverlayScheme } from "./theme.js";
|
|
5
|
+
import { createElement, forwardRef, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
6
|
+
import { readMapleConfig } from "@maple-kit/core/client";
|
|
7
|
+
import { createOverlayHost } from "@maple-kit/core/overlay";
|
|
8
|
+
import { MapleProvider } from "@maple-kit/react";
|
|
9
|
+
import { createPortal } from "react-dom";
|
|
10
|
+
//#region src/root.ts
|
|
11
|
+
const MapleRoot = forwardRef(function MapleRoot(props, ref) {
|
|
12
|
+
const [config] = useState(() => readMapleConfig(props));
|
|
13
|
+
const [shots] = useState(createShotStore);
|
|
14
|
+
const host = useOverlayHost(config.enabled, props.nonce, props.parent);
|
|
15
|
+
const options = useMemo(() => ({
|
|
16
|
+
...props.options,
|
|
17
|
+
branch: props.branch,
|
|
18
|
+
...props.label === void 0 ? {} : { label: props.label },
|
|
19
|
+
...props.commit === void 0 ? {} : { commit: props.commit },
|
|
20
|
+
config
|
|
21
|
+
}), [
|
|
22
|
+
props.options,
|
|
23
|
+
props.branch,
|
|
24
|
+
props.label,
|
|
25
|
+
props.commit,
|
|
26
|
+
config
|
|
27
|
+
]);
|
|
28
|
+
if (!host || !config.enabled) return null;
|
|
29
|
+
const className = props.className ? `mk-layer ${props.className}` : "mk-layer";
|
|
30
|
+
const layer = createElement("div", {
|
|
31
|
+
className,
|
|
32
|
+
ref
|
|
33
|
+
}, props.children);
|
|
34
|
+
const scoped = createElement(OverlayLayer, { host }, layer);
|
|
35
|
+
return createElement(MapleProvider, props.client ? { client: props.client } : { options }, createElement(ShotContext.Provider, { value: shots }, createPortal(scoped, host.root)));
|
|
36
|
+
});
|
|
37
|
+
function useOverlayHost(enabled, nonce, parent) {
|
|
38
|
+
const [host, setHost] = useState();
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!enabled) return;
|
|
41
|
+
const mounted = createOverlayHost({
|
|
42
|
+
...nonce === void 0 ? {} : { nonce },
|
|
43
|
+
...parent === void 0 ? {} : { parent }
|
|
44
|
+
});
|
|
45
|
+
mounted.addStyles(OVERLAY_CSS);
|
|
46
|
+
setHost(mounted);
|
|
47
|
+
return () => mounted.destroy();
|
|
48
|
+
}, [
|
|
49
|
+
enabled,
|
|
50
|
+
nonce,
|
|
51
|
+
parent
|
|
52
|
+
]);
|
|
53
|
+
return host;
|
|
54
|
+
}
|
|
55
|
+
function OverlayLayer(props) {
|
|
56
|
+
const theme = useOverlayScheme();
|
|
57
|
+
const { container, root } = props.host;
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
|
+
container.setAttribute(SCHEME_ATTRIBUTE, theme.scheme);
|
|
60
|
+
}, [container, theme.scheme]);
|
|
61
|
+
const value = useMemo(() => ({
|
|
62
|
+
root,
|
|
63
|
+
container,
|
|
64
|
+
scheme: theme.scheme,
|
|
65
|
+
hostScheme: theme.hostScheme
|
|
66
|
+
}), [
|
|
67
|
+
root,
|
|
68
|
+
container,
|
|
69
|
+
theme.scheme,
|
|
70
|
+
theme.hostScheme
|
|
71
|
+
]);
|
|
72
|
+
return createElement(MapleUiContext.Provider, { value }, props.children);
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { MapleRoot };
|
package/dist/shots.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
//#region src/shots.ts
|
|
3
|
+
function createShotStore() {
|
|
4
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
let held;
|
|
6
|
+
const tell = () => {
|
|
7
|
+
for (const listener of listeners) listener();
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
get: () => held,
|
|
11
|
+
put(shot) {
|
|
12
|
+
held = shot;
|
|
13
|
+
tell();
|
|
14
|
+
},
|
|
15
|
+
take() {
|
|
16
|
+
const shot = held;
|
|
17
|
+
held = void 0;
|
|
18
|
+
if (shot) tell();
|
|
19
|
+
return shot;
|
|
20
|
+
},
|
|
21
|
+
subscribe(listener) {
|
|
22
|
+
listeners.add(listener);
|
|
23
|
+
return () => listeners.delete(listener);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const ShotContext = createContext(null);
|
|
28
|
+
function useShots() {
|
|
29
|
+
return useContext(ShotContext);
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { ShotContext, createShotStore, useShots };
|
package/dist/slot.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode, Ref } from "react";
|
|
2
|
+
//#region src/slot.d.ts
|
|
3
|
+
/** Props of unknown shape, which is what a caller's element has. */
|
|
4
|
+
type AnyProps = Record<string, unknown>;
|
|
5
|
+
/** Every part accepts this, and none of them accepts a visual variant. */
|
|
6
|
+
export interface AsChildProps {
|
|
7
|
+
/** Render the single child instead of the part's own element. */
|
|
8
|
+
readonly asChild?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** What `<Slot>` renders into: the part's props, over one element child. */
|
|
11
|
+
export interface SlotProps extends HTMLAttributes<HTMLElement> {
|
|
12
|
+
readonly children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/** Thrown when `asChild` was given something other than one element. */
|
|
15
|
+
export declare class AsChildError extends Error {
|
|
16
|
+
readonly name = "AsChildError";
|
|
17
|
+
constructor();
|
|
18
|
+
}
|
|
19
|
+
/** Calls every ref it was given with the same node. */
|
|
20
|
+
export declare function composeRefs<T>(...refs: readonly (Ref<T> | undefined)[]): (node: T | null) => void;
|
|
21
|
+
/** The part's props first, the child's over them, merging the three that merge. */
|
|
22
|
+
export declare function mergeProps(slotProps: AnyProps, childProps: AnyProps): AnyProps;
|
|
23
|
+
/**
|
|
24
|
+
* Renders its one element child with the part's props merged in. Parts pick it
|
|
25
|
+
* with `const Element = asChild ? Slot : "button"` and change nothing else.
|
|
26
|
+
*/
|
|
27
|
+
export declare const Slot: import("react").ForwardRefExoticComponent<SlotProps & import("react").RefAttributes<HTMLElement>>;
|
|
28
|
+
//#endregion
|
package/dist/slot.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { cloneElement, forwardRef, isValidElement } from "react";
|
|
2
|
+
//#region src/slot.ts
|
|
3
|
+
var AsChildError = class extends Error {
|
|
4
|
+
name = "AsChildError";
|
|
5
|
+
constructor() {
|
|
6
|
+
super("asChild needs exactly one React element as its child.");
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
function composeRefs(...refs) {
|
|
10
|
+
return (node) => {
|
|
11
|
+
for (const ref of refs) if (typeof ref === "function") ref(node);
|
|
12
|
+
else if (ref) ref.current = node;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function isHandler(key) {
|
|
16
|
+
return key.startsWith("on") && key.length > 2 && key[2] === key[2]?.toUpperCase();
|
|
17
|
+
}
|
|
18
|
+
function composeHandlers(slot, child) {
|
|
19
|
+
if (typeof slot !== "function" || typeof child !== "function") return child ?? slot;
|
|
20
|
+
return (...args) => {
|
|
21
|
+
slot(...args);
|
|
22
|
+
child(...args);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function mergeValue(key, slot, child) {
|
|
26
|
+
if (isHandler(key)) return composeHandlers(slot, child);
|
|
27
|
+
if (key === "className") return [slot, child].filter(Boolean).join(" ") || void 0;
|
|
28
|
+
if (key === "style") return {
|
|
29
|
+
...slot,
|
|
30
|
+
...child
|
|
31
|
+
};
|
|
32
|
+
return child ?? slot;
|
|
33
|
+
}
|
|
34
|
+
function mergeProps(slotProps, childProps) {
|
|
35
|
+
const merged = { ...slotProps };
|
|
36
|
+
for (const key of Object.keys(childProps)) merged[key] = mergeValue(key, slotProps[key], childProps[key]);
|
|
37
|
+
return merged;
|
|
38
|
+
}
|
|
39
|
+
function refOf(child) {
|
|
40
|
+
const onElement = child.ref;
|
|
41
|
+
return child.props["ref"] ?? onElement;
|
|
42
|
+
}
|
|
43
|
+
const Slot = forwardRef(function Slot(props, ref) {
|
|
44
|
+
const { children, ...slotProps } = props;
|
|
45
|
+
if (!isValidElement(children)) throw new AsChildError();
|
|
46
|
+
const child = children;
|
|
47
|
+
const merged = mergeProps(slotProps, child.props);
|
|
48
|
+
merged["ref"] = composeRefs(ref, refOf(child));
|
|
49
|
+
return cloneElement(child, merged);
|
|
50
|
+
});
|
|
51
|
+
//#endregion
|
|
52
|
+
export { AsChildError, Slot, composeRefs, mergeProps };
|
package/dist/slots.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/slots.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The ten reviewer colours, and the only supported way to apply one.
|
|
4
|
+
*
|
|
5
|
+
* Hue is constant across schemes and lightness alternates between neighbours,
|
|
6
|
+
* so two adjacent slots stay apart under deuteranopia. A slot reaches an
|
|
7
|
+
* element as two custom properties set with `setProperty`; a generated rule per
|
|
8
|
+
* reviewer would grow the stylesheet with the team and break the CSP claim.
|
|
9
|
+
*/
|
|
10
|
+
/** How many slots there are. `CommentAuthor.colorSlot` is a number under this. */
|
|
11
|
+
export declare const REVIEWER_SLOT_COUNT: number;
|
|
12
|
+
/** The reviewer's colour. Out-of-range and negative slots wrap rather than throw. */
|
|
13
|
+
export declare function slotColor(slot: number): string;
|
|
14
|
+
/** What reads on top of {@link slotColor} at the same hue. */
|
|
15
|
+
export declare function slotInk(slot: number): string;
|
|
16
|
+
/** What `applyReviewerSlot` writes, and the only two runtime colour tokens. */
|
|
17
|
+
export declare const SLOT_PROPERTY = "--mk-slot";
|
|
18
|
+
export declare const SLOT_INK_PROPERTY = "--mk-slot-ink";
|
|
19
|
+
/**
|
|
20
|
+
* Paints one element in a reviewer's colour. Two `setProperty` calls, never a
|
|
21
|
+
* rule and never `cssText`, which is the one CSSOM call CSP checks.
|
|
22
|
+
*/
|
|
23
|
+
export declare function applyReviewerSlot(element: ElementCSSInlineStyle, slot: number): void;
|
|
24
|
+
//#endregion
|
package/dist/slots.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/slots.ts
|
|
2
|
+
const PALETTE = [
|
|
3
|
+
{
|
|
4
|
+
l: .63,
|
|
5
|
+
c: .13,
|
|
6
|
+
h: 22
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
l: .77,
|
|
10
|
+
c: .12,
|
|
11
|
+
h: 62
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
l: .62,
|
|
15
|
+
c: .12,
|
|
16
|
+
h: 104
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
l: .76,
|
|
20
|
+
c: .11,
|
|
21
|
+
h: 146
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
l: .61,
|
|
25
|
+
c: .1,
|
|
26
|
+
h: 182
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
l: .75,
|
|
30
|
+
c: .1,
|
|
31
|
+
h: 214
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
l: .62,
|
|
35
|
+
c: .13,
|
|
36
|
+
h: 252
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
l: .76,
|
|
40
|
+
c: .11,
|
|
41
|
+
h: 288
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
l: .61,
|
|
45
|
+
c: .13,
|
|
46
|
+
h: 320
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
l: .75,
|
|
50
|
+
c: .12,
|
|
51
|
+
h: 350
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
const INK_ABOVE = .68;
|
|
55
|
+
const REVIEWER_SLOT_COUNT = PALETTE.length;
|
|
56
|
+
function at(slot) {
|
|
57
|
+
const index = (Math.trunc(slot) % PALETTE.length + PALETTE.length) % PALETTE.length;
|
|
58
|
+
return PALETTE[index] ?? PALETTE[0];
|
|
59
|
+
}
|
|
60
|
+
function slotColor(slot) {
|
|
61
|
+
const { l, c, h } = at(slot);
|
|
62
|
+
return `oklch(${l} ${c} ${h})`;
|
|
63
|
+
}
|
|
64
|
+
function slotInk(slot) {
|
|
65
|
+
const { l, h } = at(slot);
|
|
66
|
+
return l > INK_ABOVE ? `oklch(0.22 0.03 ${h})` : `oklch(0.99 0.01 ${h})`;
|
|
67
|
+
}
|
|
68
|
+
const SLOT_PROPERTY = "--mk-slot";
|
|
69
|
+
const SLOT_INK_PROPERTY = "--mk-slot-ink";
|
|
70
|
+
function applyReviewerSlot(element, slot) {
|
|
71
|
+
element.style.setProperty(SLOT_PROPERTY, slotColor(slot));
|
|
72
|
+
element.style.setProperty(SLOT_INK_PROPERTY, slotInk(slot));
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { REVIEWER_SLOT_COUNT, SLOT_INK_PROPERTY, SLOT_PROPERTY, applyReviewerSlot, slotColor, slotInk };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/stylesheet.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The overlay's stylesheet: one string, built from the token table.
|
|
4
|
+
*
|
|
5
|
+
* It is a constant rather than a CSS file behind a loader, so it needs no
|
|
6
|
+
* bundler plugin, stays tree-shakeable, and leaves the CSP claim in
|
|
7
|
+
* docs/overlay-csp.md checkable by reading `buildOverlayCss` and
|
|
8
|
+
* `createOverlayStyleSheet`. Nothing here constructs a `CSSStyleSheet`, reaches
|
|
9
|
+
* `document` or reads storage; building a string does none of those.
|
|
10
|
+
*/
|
|
11
|
+
/** The attribute the root writes the overlay's resolved scheme to. */
|
|
12
|
+
export declare const SCHEME_ATTRIBUTE = "data-mk-scheme";
|
|
13
|
+
/** The overlay's styles. The only thing `Maple.Root` adopts. */
|
|
14
|
+
export declare const OVERLAY_CSS: string;
|
|
15
|
+
//#endregion
|