@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.
Files changed (173) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +39 -0
  4. package/dist/composer/actions.d.ts +15 -0
  5. package/dist/composer/actions.js +59 -0
  6. package/dist/composer/attachments.d.ts +33 -0
  7. package/dist/composer/attachments.js +166 -0
  8. package/dist/composer/badge.d.ts +13 -0
  9. package/dist/composer/badge.js +25 -0
  10. package/dist/composer/body.d.ts +13 -0
  11. package/dist/composer/body.js +119 -0
  12. package/dist/composer/composer.d.ts +23 -0
  13. package/dist/composer/composer.js +122 -0
  14. package/dist/composer/css.js +559 -0
  15. package/dist/composer/detail.d.ts +32 -0
  16. package/dist/composer/detail.js +102 -0
  17. package/dist/composer/emoji.d.ts +54 -0
  18. package/dist/composer/emoji.js +131 -0
  19. package/dist/composer/index.d.ts +15 -0
  20. package/dist/composer/index.js +15 -0
  21. package/dist/composer/inset.js +20 -0
  22. package/dist/composer/leave.d.ts +19 -0
  23. package/dist/composer/leave.js +34 -0
  24. package/dist/composer/peek.d.ts +21 -0
  25. package/dist/composer/peek.js +29 -0
  26. package/dist/composer/phrase.d.ts +27 -0
  27. package/dist/composer/phrase.js +30 -0
  28. package/dist/composer/picker-emoji.d.ts +34 -0
  29. package/dist/composer/picker-emoji.js +48 -0
  30. package/dist/composer/prompt.js +31 -0
  31. package/dist/composer/scope.d.ts +11 -0
  32. package/dist/composer/scope.js +16 -0
  33. package/dist/composer/target.d.ts +13 -0
  34. package/dist/composer/target.js +45 -0
  35. package/dist/context.d.ts +21 -0
  36. package/dist/context.js +16 -0
  37. package/dist/data.d.ts +52 -0
  38. package/dist/data.js +30 -0
  39. package/dist/icons/branch.d.ts +4 -0
  40. package/dist/icons/branch.js +8 -0
  41. package/dist/icons/cog.d.ts +4 -0
  42. package/dist/icons/cog.js +8 -0
  43. package/dist/icons/commit.d.ts +4 -0
  44. package/dist/icons/commit.js +5 -0
  45. package/dist/icons/crossfade.d.ts +15 -0
  46. package/dist/icons/crossfade.js +27 -0
  47. package/dist/icons/element.d.ts +4 -0
  48. package/dist/icons/element.js +5 -0
  49. package/dist/icons/icon.d.ts +24 -0
  50. package/dist/icons/icon.js +27 -0
  51. package/dist/icons/index.d.ts +13 -0
  52. package/dist/icons/index.js +13 -0
  53. package/dist/icons/noplace.d.ts +4 -0
  54. package/dist/icons/noplace.js +5 -0
  55. package/dist/icons/region.d.ts +4 -0
  56. package/dist/icons/region.js +8 -0
  57. package/dist/icons/smiley.d.ts +4 -0
  58. package/dist/icons/smiley.js +8 -0
  59. package/dist/icons/sparkle.d.ts +4 -0
  60. package/dist/icons/sparkle.js +8 -0
  61. package/dist/icons/target.d.ts +4 -0
  62. package/dist/icons/target.js +5 -0
  63. package/dist/icons/text.d.ts +4 -0
  64. package/dist/icons/text.js +5 -0
  65. package/dist/index.d.ts +11 -0
  66. package/dist/index.js +11 -0
  67. package/dist/island/account.d.ts +10 -0
  68. package/dist/island/account.js +52 -0
  69. package/dist/island/comments.d.ts +24 -0
  70. package/dist/island/comments.js +40 -0
  71. package/dist/island/content.d.ts +10 -0
  72. package/dist/island/content.js +26 -0
  73. package/dist/island/context.d.ts +58 -0
  74. package/dist/island/context.js +23 -0
  75. package/dist/island/css.d.ts +13 -0
  76. package/dist/island/css.js +1020 -0
  77. package/dist/island/drag.d.ts +10 -0
  78. package/dist/island/drag.js +77 -0
  79. package/dist/island/filters.d.ts +10 -0
  80. package/dist/island/filters.js +63 -0
  81. package/dist/island/header.d.ts +32 -0
  82. package/dist/island/header.js +47 -0
  83. package/dist/island/index.d.ts +18 -0
  84. package/dist/island/index.js +18 -0
  85. package/dist/island/island.d.ts +12 -0
  86. package/dist/island/island.js +111 -0
  87. package/dist/island/item.d.ts +12 -0
  88. package/dist/island/item.js +158 -0
  89. package/dist/island/language.d.ts +55 -0
  90. package/dist/island/language.js +125 -0
  91. package/dist/island/leaf.js +17 -0
  92. package/dist/island/list.d.ts +12 -0
  93. package/dist/island/list.js +31 -0
  94. package/dist/island/new-comment.d.ts +18 -0
  95. package/dist/island/new-comment.js +56 -0
  96. package/dist/island/settings.d.ts +10 -0
  97. package/dist/island/settings.js +134 -0
  98. package/dist/island/stagger.d.ts +12 -0
  99. package/dist/island/stagger.js +4 -0
  100. package/dist/island/time.d.ts +17 -0
  101. package/dist/island/time.js +29 -0
  102. package/dist/island/trigger.d.ts +10 -0
  103. package/dist/island/trigger.js +39 -0
  104. package/dist/language.d.ts +45 -0
  105. package/dist/language.js +66 -0
  106. package/dist/maple.d.ts +25 -0
  107. package/dist/maple.js +65 -0
  108. package/dist/marks/avatar.d.ts +16 -0
  109. package/dist/marks/avatar.js +36 -0
  110. package/dist/marks/css.d.ts +12 -0
  111. package/dist/marks/css.js +371 -0
  112. package/dist/marks/frame.d.ts +25 -0
  113. package/dist/marks/frame.js +53 -0
  114. package/dist/marks/geometry.d.ts +55 -0
  115. package/dist/marks/geometry.js +61 -0
  116. package/dist/marks/index.d.ts +14 -0
  117. package/dist/marks/index.js +14 -0
  118. package/dist/marks/label.d.ts +23 -0
  119. package/dist/marks/label.js +31 -0
  120. package/dist/marks/layer.d.ts +16 -0
  121. package/dist/marks/layer.js +194 -0
  122. package/dist/marks/leaf.d.ts +18 -0
  123. package/dist/marks/leaf.js +7 -0
  124. package/dist/marks/mark.d.ts +28 -0
  125. package/dist/marks/mark.js +43 -0
  126. package/dist/marks/nudge.d.ts +50 -0
  127. package/dist/marks/nudge.js +76 -0
  128. package/dist/marks/paint.d.ts +11 -0
  129. package/dist/marks/paint.js +19 -0
  130. package/dist/marks/placement.d.ts +24 -0
  131. package/dist/marks/placement.js +28 -0
  132. package/dist/marks/ring.d.ts +28 -0
  133. package/dist/marks/ring.js +85 -0
  134. package/dist/marks/shape.d.ts +21 -0
  135. package/dist/marks/shape.js +51 -0
  136. package/dist/notice/css.d.ts +11 -0
  137. package/dist/notice/css.js +74 -0
  138. package/dist/notice/index.d.ts +4 -0
  139. package/dist/notice/index.js +4 -0
  140. package/dist/notice/language.d.ts +15 -0
  141. package/dist/notice/language.js +14 -0
  142. package/dist/notice/notice.d.ts +14 -0
  143. package/dist/notice/notice.js +49 -0
  144. package/dist/part.d.ts +9 -0
  145. package/dist/part.js +11 -0
  146. package/dist/picker/css.d.ts +13 -0
  147. package/dist/picker/css.js +141 -0
  148. package/dist/picker/index.d.ts +6 -0
  149. package/dist/picker/index.js +6 -0
  150. package/dist/picker/language.d.ts +14 -0
  151. package/dist/picker/language.js +17 -0
  152. package/dist/picker/picker.d.ts +18 -0
  153. package/dist/picker/picker.js +164 -0
  154. package/dist/picker/session.d.ts +16 -0
  155. package/dist/picker/session.js +35 -0
  156. package/dist/picker/target.d.ts +23 -0
  157. package/dist/picker/target.js +37 -0
  158. package/dist/root.d.ts +42 -0
  159. package/dist/root.js +75 -0
  160. package/dist/shots.js +32 -0
  161. package/dist/slot.d.ts +28 -0
  162. package/dist/slot.js +52 -0
  163. package/dist/slots.d.ts +24 -0
  164. package/dist/slots.js +75 -0
  165. package/dist/stylesheet.d.ts +15 -0
  166. package/dist/stylesheet.js +188 -0
  167. package/dist/theme.d.ts +20 -0
  168. package/dist/theme.js +13 -0
  169. package/dist/tip.d.ts +40 -0
  170. package/dist/tip.js +82 -0
  171. package/dist/tokens.d.ts +28 -0
  172. package/dist/tokens.js +180 -0
  173. 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 };
@@ -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