@maple-kit/ui 0.14.0 → 0.15.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.
@@ -83,28 +83,33 @@ function filled(scope, held) {
83
83
  src: scope.pending?.preview.url,
84
84
  alt: ATTACH_WORDS.alt
85
85
  }),
86
- said(taking(held), captured && keeps, "said"),
86
+ createElement("span", {
87
+ key: "body",
88
+ className: "mk-shot-body"
89
+ }, said(taking(held), captured && keeps, "said", "mk-shot-said mk-shot-line"), failed ? createElement("span", {
90
+ key: "bad",
91
+ className: "mk-chip"
92
+ }, ATTACH_WORDS.failed) : null),
87
93
  createElement("button", {
88
94
  key: "off",
89
95
  type: "button",
90
96
  className: "mk-btn mk-btn-quiet mk-press",
91
97
  onClick: scope.clear
92
- }, ATTACH_WORDS.remove),
93
- failed ? createElement("span", {
94
- key: "bad",
95
- className: "mk-chip"
96
- }, ATTACH_WORDS.failed) : null
98
+ }, ATTACH_WORDS.remove)
97
99
  ];
98
100
  }
99
- function said(words, byMaple, key) {
101
+ function said(words, byMaple, key, className = "mk-shot-said") {
100
102
  return createElement("span", {
101
103
  key,
102
- className: "mk-shot-said",
104
+ className,
103
105
  ...byMaple ? { "data-mk-maple": "true" } : {}
104
106
  }, byMaple ? createElement(SparkleIcon, {
105
107
  key: "spark",
106
108
  size: 12
107
- }) : null, words);
109
+ }) : null, createElement("span", {
110
+ key: "words",
111
+ className: "mk-shot-words"
112
+ }, words));
108
113
  }
109
114
  function taking(held) {
110
115
  if (!held.keeps) return ATTACH_WORDS.notKept;
@@ -8,12 +8,12 @@ const CONTEXT_LABELS = {
8
8
  open: "Hide what was captured"
9
9
  };
10
10
  const MapleContextBadge = forwardRef(function MapleContextBadge(props, ref) {
11
- const { composer, detail } = useMaple();
11
+ const { composer } = useMaple();
12
12
  const client = useMapleClient();
13
13
  const context = props.context ?? composer.target?.context;
14
14
  const Element = props.asChild ? Slot : "section";
15
15
  if (!context) return null;
16
- const rows = contextRows(context, detail);
16
+ const rows = contextRows(context);
17
17
  const collapsible = props.collapsible !== false && composer.viewing === void 0;
18
18
  const open = !collapsible || composer.contextOpen;
19
19
  return createElement(Element, {
@@ -239,6 +239,42 @@ function scoreCard() {
239
239
  background: var(--mk-line);
240
240
  }
241
241
 
242
+ `.trim();
243
+ }
244
+ function shotBodyCss() {
245
+ return `
246
+ /* The held image's caption and its failure share one column between the
247
+ thumbnail and Remove, so a failure adds a line rather than a layout. */
248
+ .mk-shot-body {
249
+ flex: 1 1 auto;
250
+ min-width: 0;
251
+ display: flex;
252
+ flex-direction: column;
253
+ align-items: flex-start;
254
+ gap: 4px;
255
+ }
256
+
257
+ .mk-shot-body .mk-shot-said {
258
+ flex: none;
259
+ max-width: 100%;
260
+ }
261
+
262
+ .mk-shot-line .mk-shot-words {
263
+ min-width: 0;
264
+ overflow: hidden;
265
+ white-space: nowrap;
266
+ text-overflow: ellipsis;
267
+ }
268
+
269
+ .mk-shot-body .mk-chip {
270
+ max-width: 100%;
271
+ white-space: normal;
272
+ border-radius: var(--mk-r-xs);
273
+ }
274
+
275
+ .mk-shots > .mk-btn {
276
+ flex: none;
277
+ }
242
278
  `.trim();
243
279
  }
244
280
  function kindChip() {
@@ -561,6 +597,8 @@ ${scoreCard()}
561
597
 
562
598
  ${kindChip()}
563
599
 
600
+ ${shotBodyCss()}
601
+
564
602
  .mk-leave {
565
603
  position: absolute;
566
604
  top: 12px;
@@ -12,6 +12,7 @@ function islandCss() {
12
12
  rowDetail(),
13
13
  developer(),
14
14
  ledger(),
15
+ transfer(),
15
16
  newComment(),
16
17
  keyframes(),
17
18
  sheet()
@@ -936,6 +937,78 @@ function developer() {
936
937
 
937
938
  `.trim();
938
939
  }
940
+ function transfer() {
941
+ return `
942
+ .mk-transfer,
943
+ .mk-transfer-other {
944
+ flex: none;
945
+ display: flex;
946
+ flex-wrap: wrap;
947
+ align-items: center;
948
+ gap: 6px 8px;
949
+ padding: 6px 10px;
950
+ border-top: 1px solid var(--mk-line);
951
+ background: var(--mk-sunk);
952
+ color: var(--mk-faint);
953
+ font-size: 11px;
954
+ }
955
+
956
+ .mk-transfer-said {
957
+ flex: 1 1 auto;
958
+ min-width: 0;
959
+ }
960
+
961
+ .mk-transfer-said code {
962
+ color: var(--mk-muted);
963
+ font-family: inherit;
964
+ font-weight: 600;
965
+ }
966
+
967
+ .mk-transfer-panel,
968
+ .mk-transfer-note {
969
+ flex: 1 1 100%;
970
+ margin: 0;
971
+ }
972
+
973
+ .mk-transfer-panel {
974
+ display: flex;
975
+ flex-direction: column;
976
+ gap: 6px;
977
+ }
978
+
979
+ .mk-transfer-box {
980
+ box-sizing: border-box;
981
+ width: 100%;
982
+ resize: vertical;
983
+ padding: 6px 8px;
984
+ border: 1px solid var(--mk-line);
985
+ border-radius: 6px;
986
+ background: var(--mk-bg);
987
+ color: var(--mk-fg);
988
+ font: inherit;
989
+ font-size: 11px;
990
+ }
991
+
992
+ .mk-transfer-row,
993
+ .mk-transfer-ask {
994
+ display: flex;
995
+ align-items: center;
996
+ gap: 8px;
997
+ }
998
+
999
+ .mk-transfer-file {
1000
+ flex: 1 1 auto;
1001
+ min-width: 0;
1002
+ color: var(--mk-muted);
1003
+ font-size: 11px;
1004
+ }
1005
+
1006
+ .mk-unsent-copy:disabled {
1007
+ opacity: 0.5;
1008
+ cursor: default;
1009
+ }
1010
+ `;
1011
+ }
939
1012
  function ledger() {
940
1013
  return `
941
1014
  .mk-unsent {
@@ -1017,6 +1090,22 @@ function ledger() {
1017
1090
  cursor: pointer;
1018
1091
  }
1019
1092
 
1093
+ .mk-unsent-where {
1094
+ flex: none;
1095
+ max-width: 45%;
1096
+ color: var(--mk-faint);
1097
+ font-size: 11px;
1098
+ text-decoration: underline;
1099
+ text-underline-offset: 2px;
1100
+ white-space: nowrap;
1101
+ overflow: hidden;
1102
+ text-overflow: ellipsis;
1103
+ }
1104
+
1105
+ .mk-unsent-where:hover {
1106
+ color: var(--mk-fg);
1107
+ }
1108
+
1020
1109
  .mk-unsent-drop {
1021
1110
  flex: none;
1022
1111
  padding: 2px 7px;
@@ -15,7 +15,8 @@ import { NewComment, NewCommentProps, PickButton, PickButtonProps } from "./new-
15
15
  import { Settings, SettingsProps } from "./settings.js";
16
16
  import { STAGGER_ROWS } from "./stagger.js";
17
17
  import { absoluteTime, relativeTime } from "./time.js";
18
+ import { ImportDrafts, ImportDraftsProps, OtherDrafts, OtherDraftsProps } from "./transfer.js";
18
19
  import { IslandTrigger, IslandTriggerProps } from "./trigger.js";
19
20
  import { Unsent, UnsentProps } from "./unsent.js";
20
21
  import { WORDMARK_SIZE_PX, WORDMARK_WORD_SCALE, Wordmark, WordmarkProps } from "./wordmark.js";
21
- export { APPROVE_COPY, Account, type AccountProps, Approve, type ApproveProps, Branch, type BranchProps, DRAGGING_ATTRIBUTE, DRAG_THRESHOLD_PX, type DragHandlers, FILTER_LABELS, type FilterCounts, Filters, type FiltersProps, Header, type HeaderProps, ISLAND_COPY, Island, IslandContent, type IslandContentProps, IslandContextError, type IslandContextValue, type IslandPhase, type IslandProps, IslandTrigger, type IslandTriggerProps, Item, type ItemProps, List, type ListProps, Logo, type LogoProps, NewComment, type NewCommentProps, PickButton, type PickButtonProps, type PointerEventLike, SETTINGS_COPY, STAGGER_ROWS, Settings, type SettingsProps, TIME_DELAY_MS, UNSENT_COPY, Unsent, type UnsentProps, WORDMARK_SIZE_PX, WORDMARK_WORD_SCALE, Wordmark, type WordmarkProps, absoluteTime, countsFor, islandCss, kindPhrase, numbersFor, openLabel, orphanReason, reasonOf, relativeTime, resolutionsFor, useDrag, useIsland };
22
+ export { APPROVE_COPY, Account, type AccountProps, Approve, type ApproveProps, Branch, type BranchProps, DRAGGING_ATTRIBUTE, DRAG_THRESHOLD_PX, type DragHandlers, FILTER_LABELS, type FilterCounts, Filters, type FiltersProps, Header, type HeaderProps, ISLAND_COPY, ImportDrafts, type ImportDraftsProps, Island, IslandContent, type IslandContentProps, IslandContextError, type IslandContextValue, type IslandPhase, type IslandProps, IslandTrigger, type IslandTriggerProps, Item, type ItemProps, List, type ListProps, Logo, type LogoProps, NewComment, type NewCommentProps, OtherDrafts, type OtherDraftsProps, PickButton, type PickButtonProps, type PointerEventLike, SETTINGS_COPY, STAGGER_ROWS, Settings, type SettingsProps, TIME_DELAY_MS, UNSENT_COPY, Unsent, type UnsentProps, WORDMARK_SIZE_PX, WORDMARK_WORD_SCALE, Wordmark, type WordmarkProps, absoluteTime, countsFor, islandCss, kindPhrase, numbersFor, openLabel, orphanReason, reasonOf, relativeTime, resolutionsFor, useDrag, useIsland };
@@ -16,6 +16,7 @@ import { Item, TIME_DELAY_MS } from "./item.js";
16
16
  import { List } from "./list.js";
17
17
  import { NewComment, PickButton } from "./new-comment.js";
18
18
  import { Settings } from "./settings.js";
19
+ import { ImportDrafts, OtherDrafts } from "./transfer.js";
19
20
  import { IslandTrigger } from "./trigger.js";
20
21
  import { Unsent } from "./unsent.js";
21
- export { APPROVE_COPY, Account, Approve, Branch, DRAGGING_ATTRIBUTE, DRAG_THRESHOLD_PX, FILTER_LABELS, Filters, Header, ISLAND_COPY, Island, IslandContent, IslandContextError, IslandTrigger, Item, List, Logo, NewComment, PickButton, SETTINGS_COPY, STAGGER_ROWS, Settings, TIME_DELAY_MS, UNSENT_COPY, Unsent, WORDMARK_SIZE_PX, WORDMARK_WORD_SCALE, Wordmark, absoluteTime, countsFor, islandCss, kindPhrase, numbersFor, openLabel, orphanReason, reasonOf, relativeTime, resolutionsFor, useDrag, useIsland };
22
+ export { APPROVE_COPY, Account, Approve, Branch, DRAGGING_ATTRIBUTE, DRAG_THRESHOLD_PX, FILTER_LABELS, Filters, Header, ISLAND_COPY, ImportDrafts, Island, IslandContent, IslandContextError, IslandTrigger, Item, List, Logo, NewComment, OtherDrafts, PickButton, SETTINGS_COPY, STAGGER_ROWS, Settings, TIME_DELAY_MS, UNSENT_COPY, Unsent, WORDMARK_SIZE_PX, WORDMARK_WORD_SCALE, Wordmark, absoluteTime, countsFor, islandCss, kindPhrase, numbersFor, openLabel, orphanReason, reasonOf, relativeTime, resolutionsFor, useDrag, useIsland };
@@ -53,10 +53,13 @@ export declare const UNSENT_COPY: {
53
53
  readonly copy: "Copy";
54
54
  readonly copied: "Copied";
55
55
  readonly copyHint: "Every unsent comment as markdown, to paste into an agent or a ticket.";
56
+ readonly export: "Export";
57
+ readonly exportHint: "Every unsent comment as a file another browser can import.";
56
58
  readonly resumeHint: "Open it again";
57
59
  readonly discard: "Throw this one away";
58
60
  readonly discardGlyph: "✕";
59
61
  readonly blank: "(nothing written yet)";
62
+ readonly elsewhere: (path: string) => string;
60
63
  };
61
64
  /** Copy with no better home than a name. */
62
65
  export declare const ISLAND_COPY: {
@@ -104,10 +104,35 @@ const UNSENT_COPY = {
104
104
  copy: "Copy",
105
105
  copied: "Copied",
106
106
  copyHint: "Every unsent comment as markdown, to paste into an agent or a ticket.",
107
+ export: "Export",
108
+ exportHint: "Every unsent comment as a file another browser can import.",
107
109
  resumeHint: "Open it again",
108
110
  discard: "Throw this one away",
109
111
  discardGlyph: "✕",
110
- blank: "(nothing written yet)"
112
+ blank: "(nothing written yet)",
113
+ elsewhere: (path) => `On another page · ${path}`
114
+ };
115
+ const TRANSFER_COPY = {
116
+ open: "Import drafts",
117
+ close: "Close import",
118
+ boxLabel: "An exported drafts file",
119
+ boxHint: "Paste an export here, or drop its file",
120
+ fileLabel: "Choose an exported drafts file",
121
+ add: "Add",
122
+ addHere: "Add here",
123
+ cancel: "Cancel",
124
+ unreadable: "That is not a Maple drafts export.",
125
+ unsupported: "That export is from a newer Maple than this page.",
126
+ ask: (branch, count) => `${String(count)} ${count === 1 ? "draft was" : "drafts were"} written on ${branch}. Add ${count === 1 ? "it" : "them"} here?`,
127
+ result: (result) => [
128
+ `Added ${String(result.added)}`,
129
+ result.skipped > 0 ? `${String(result.skipped)} already here` : "",
130
+ result.expired > 0 ? `${String(result.expired)} too old` : "",
131
+ result.invalid > 0 ? `${String(result.invalid)} not drafts` : ""
132
+ ].filter(Boolean).join(" · "),
133
+ found: (count) => `${String(count)} ${count === 1 ? "draft" : "drafts"} saved under`,
134
+ move: "Move here",
135
+ dismiss: "Dismiss"
111
136
  };
112
137
  const ISLAND_COPY = {
113
138
  title: "Comments",
@@ -140,4 +165,4 @@ function kindPhrase(kind, human) {
140
165
  return kind === "region" ? `an area of ${human}` : human;
141
166
  }
142
167
  //#endregion
143
- export { ACCOUNT_COPY, APPROVE_COPY, CORNER_LABELS, CORNER_ORDER, FILTERS_LABEL, FILTER_LABELS, FILTER_ORDER, ISLAND_COPY, PICK_LABELS, SETTINGS_COPY, TALLY_ORDER, THEME_LABELS, THEME_TITLES, UNSENT_COPY, kindPhrase, openLabel, pickTitle, tallyTitle, triggerLabel };
168
+ export { ACCOUNT_COPY, APPROVE_COPY, CORNER_LABELS, CORNER_ORDER, FILTERS_LABEL, FILTER_LABELS, FILTER_ORDER, ISLAND_COPY, PICK_LABELS, SETTINGS_COPY, TALLY_ORDER, THEME_LABELS, THEME_TITLES, TRANSFER_COPY, UNSENT_COPY, kindPhrase, openLabel, pickTitle, tallyTitle, triggerLabel };
@@ -3,6 +3,7 @@ import { renderPart } from "../part.js";
3
3
  import { FILTER_LABELS, ISLAND_COPY } from "./language.js";
4
4
  import { byReason } from "./comments.js";
5
5
  import { listId, reasonOf, useIsland } from "./context.js";
6
+ import { isSetupGap } from "../notice/language.js";
6
7
  import { Fragment, createElement, forwardRef } from "react";
7
8
  import { useComments, useMaple } from "@maple-kit/react";
8
9
  //#region src/island/list.ts
@@ -10,9 +11,9 @@ const PART = "<Maple.List>";
10
11
  const List = forwardRef(function List(props, ref) {
11
12
  const { asChild, children, className, ...rest } = props;
12
13
  const island = useIsland(PART);
13
- const { error, filter, phase } = useMaple();
14
+ const { error, filter, github, phase } = useMaple();
14
15
  const comments = useComments();
15
- const failed = error?.during === "load";
16
+ const failed = error?.during === "load" && !isSetupGap(error.kind, github);
16
17
  const rows = filter === "unpinned" ? byReason(comments, (comment) => reasonOf(island.resolutions, comment.id)) : comments;
17
18
  const body = rows.length === 0 ? renderPart("p", false, { className: "mk-empty" }, nothing(phase, failed)) : rows.map((comment) => createElement(Fragment, { key: comment.id }, children?.(comment)));
18
19
  return renderPart("div", asChild, {
@@ -0,0 +1,16 @@
1
+ import { PartProps } from "../part.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/island/transfer.d.ts
4
+ /** The section. Its children replace everything inside it. */
5
+ export interface ImportDraftsProps extends PartProps {
6
+ readonly children?: ReactNode;
7
+ }
8
+ /** The row. Its children replace everything inside it. */
9
+ export interface OtherDraftsProps extends PartProps {
10
+ readonly children?: ReactNode;
11
+ }
12
+ /** Import from a paste or a file, drawn as one quiet button until it is used. */
13
+ export declare const ImportDrafts: import("react").ForwardRefExoticComponent<ImportDraftsProps & import("react").RefAttributes<HTMLDivElement>>;
14
+ /** A row that says drafts exist under another branch here, and offers to bring them. */
15
+ export declare const OtherDrafts: import("react").ForwardRefExoticComponent<OtherDraftsProps & import("react").RefAttributes<HTMLDivElement>>;
16
+ //#endregion
@@ -0,0 +1,116 @@
1
+ import { cx } from "../cx.js";
2
+ import { renderPart } from "../part.js";
3
+ import { TRANSFER_COPY } from "./language.js";
4
+ import { createElement, forwardRef, useState } from "react";
5
+ import { useMapleClient } from "@maple-kit/react";
6
+ //#region src/island/transfer.ts
7
+ function said(result) {
8
+ return TRANSFER_COPY.result(result);
9
+ }
10
+ const ImportDrafts = forwardRef(function ImportDrafts(props, ref) {
11
+ const { asChild, children, className, ...rest } = props;
12
+ const client = useMapleClient();
13
+ const [open, setOpen] = useState(false);
14
+ const [text, setText] = useState("");
15
+ const [note, setNote] = useState();
16
+ const [asking, setAsking] = useState();
17
+ const settle = (outcome) => {
18
+ setNote(outcome.ok ? said(outcome) : TRANSFER_COPY[outcome.reason]);
19
+ if (outcome.ok) setText("");
20
+ setAsking(void 0);
21
+ };
22
+ const add = () => {
23
+ const preview = client.previewDraftImport(text);
24
+ if (!preview.ok) return settle(preview);
25
+ if (preview.sameBranch) return settle(client.importDrafts(text));
26
+ setNote(void 0);
27
+ setAsking(preview);
28
+ };
29
+ const load = (file) => {
30
+ file?.text().then(setText, () => setNote(TRANSFER_COPY.unreadable));
31
+ };
32
+ const body = asking ? createElement("div", {
33
+ key: "ask",
34
+ className: "mk-transfer-ask",
35
+ role: "alert"
36
+ }, createElement("span", null, TRANSFER_COPY.ask(asking.branch, asking.count)), button(TRANSFER_COPY.addHere, () => settle(client.importDrafts(text))), button(TRANSFER_COPY.cancel, () => setAsking(void 0))) : [createElement("textarea", {
37
+ key: "box",
38
+ className: "mk-transfer-box",
39
+ "aria-label": TRANSFER_COPY.boxLabel,
40
+ placeholder: TRANSFER_COPY.boxHint,
41
+ value: text,
42
+ rows: 3,
43
+ onChange: (event) => setText(event.target.value),
44
+ onDragOver: (event) => event.preventDefault(),
45
+ onDrop: (event) => {
46
+ event.preventDefault();
47
+ load(event.dataTransfer.files[0]);
48
+ }
49
+ }), createElement("div", {
50
+ key: "row",
51
+ className: "mk-transfer-row"
52
+ }, createElement("input", {
53
+ type: "file",
54
+ accept: "application/json,.json",
55
+ className: "mk-transfer-file",
56
+ "aria-label": TRANSFER_COPY.fileLabel,
57
+ onChange: (event) => load(event.target.files?.[0])
58
+ }), button(TRANSFER_COPY.add, add, text.trim() === ""))];
59
+ return renderPart("div", asChild, {
60
+ ...rest,
61
+ className: cx("mk-transfer", className),
62
+ ref
63
+ }, children ?? [
64
+ button(open ? TRANSFER_COPY.close : TRANSFER_COPY.open, () => setOpen(!open), false, {
65
+ key: "toggle",
66
+ "aria-expanded": open
67
+ }),
68
+ open ? createElement("div", {
69
+ key: "panel",
70
+ className: "mk-transfer-panel"
71
+ }, body) : null,
72
+ note === void 0 ? null : createElement("p", {
73
+ key: "note",
74
+ className: "mk-transfer-note",
75
+ role: "status"
76
+ }, note)
77
+ ]);
78
+ });
79
+ const OtherDrafts = forwardRef(function OtherDrafts(props, ref) {
80
+ const { asChild, children, className, ...rest } = props;
81
+ const client = useMapleClient();
82
+ const [found, setFound] = useState(() => client.foreignDrafts());
83
+ const [first] = found;
84
+ if (first === void 0) return null;
85
+ const refresh = () => setFound(client.foreignDrafts());
86
+ return renderPart("div", asChild, {
87
+ ...rest,
88
+ className: cx("mk-transfer-other", className),
89
+ ref
90
+ }, children ?? [
91
+ createElement("span", {
92
+ key: "said",
93
+ className: "mk-transfer-said"
94
+ }, `${TRANSFER_COPY.found(first.count)} `, createElement("code", null, first.branch)),
95
+ button(TRANSFER_COPY.move, () => {
96
+ client.moveDrafts(first.branch);
97
+ refresh();
98
+ }),
99
+ button(TRANSFER_COPY.dismiss, () => {
100
+ client.dismissDrafts(first.branch);
101
+ refresh();
102
+ })
103
+ ]);
104
+ });
105
+ function button(label, onClick, disabled = false, more = {}) {
106
+ return createElement("button", {
107
+ type: "button",
108
+ className: "mk-unsent-copy",
109
+ key: label,
110
+ onClick,
111
+ disabled,
112
+ ...more
113
+ }, label);
114
+ }
115
+ //#endregion
116
+ export { ImportDrafts, OtherDrafts };
@@ -1,6 +1,7 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { renderPart } from "../part.js";
3
3
  import { UNSENT_COPY } from "./language.js";
4
+ import { draftRoute, isOnPage, pathOf, usePathname } from "../marks/route.js";
4
5
  import { createElement, forwardRef, useState } from "react";
5
6
  import { useMaple, useMapleClient } from "@maple-kit/react";
6
7
  //#region src/island/unsent.ts
@@ -10,6 +11,7 @@ const Unsent = forwardRef(function Unsent(props, ref) {
10
11
  const { drafts, publishing } = useMaple();
11
12
  const client = useMapleClient();
12
13
  const [copied, setCopied] = useState(false);
14
+ const pathname = usePathname(typeof window === "undefined" ? void 0 : window);
13
15
  if (drafts.length === 0) return null;
14
16
  const copy = () => {
15
17
  navigator.clipboard.writeText(client.draftsAsMarkdown()).then(() => {
@@ -17,6 +19,7 @@ const Unsent = forwardRef(function Unsent(props, ref) {
17
19
  setTimeout(() => setCopied(false), COPIED_MS);
18
20
  }, () => void 0);
19
21
  };
22
+ const save = (event) => download(event.currentTarget.ownerDocument, "maple-drafts.json", client.draftsAsJson());
20
23
  return renderPart("div", asChild, {
21
24
  ...rest,
22
25
  className: cx("mk-unsent", className),
@@ -30,6 +33,11 @@ const Unsent = forwardRef(function Unsent(props, ref) {
30
33
  title: UNSENT_COPY.copyHint,
31
34
  onClick: copy
32
35
  }, copied ? UNSENT_COPY.copied : UNSENT_COPY.copy), createElement("button", {
36
+ type: "button",
37
+ className: "mk-unsent-copy",
38
+ title: UNSENT_COPY.exportHint,
39
+ onClick: save
40
+ }, UNSENT_COPY.export), createElement("button", {
33
41
  type: "button",
34
42
  className: "mk-btn mk-btn-primary mk-press mk-unsent-publish",
35
43
  disabled: publishing,
@@ -37,9 +45,18 @@ const Unsent = forwardRef(function Unsent(props, ref) {
37
45
  }, publishing ? UNSENT_COPY.publishing : UNSENT_COPY.publish(drafts.length))), createElement("ul", {
38
46
  key: "rows",
39
47
  className: "mk-unsent-rows"
40
- }, ...drafts.map((draft) => row(draft, client)))]);
48
+ }, ...drafts.map((draft) => row(draft, client, pathname)))]);
41
49
  });
42
- function row(draft, client) {
50
+ function download(page, name, text) {
51
+ const url = URL.createObjectURL(new Blob([text], { type: "application/json" }));
52
+ const link = page.createElement("a");
53
+ link.href = url;
54
+ link.download = name;
55
+ link.click();
56
+ URL.revokeObjectURL(url);
57
+ }
58
+ function row(draft, client, pathname) {
59
+ const route = draftRoute(draft);
43
60
  return createElement("li", {
44
61
  key: draft.id,
45
62
  className: "mk-unsent-row"
@@ -48,7 +65,10 @@ function row(draft, client) {
48
65
  className: "mk-unsent-body",
49
66
  title: UNSENT_COPY.resumeHint,
50
67
  onClick: () => client.resumeDraft(draft.id)
51
- }, draft.body.trim() === "" ? UNSENT_COPY.blank : draft.body), createElement("button", {
68
+ }, draft.body.trim() === "" ? UNSENT_COPY.blank : draft.body), route === void 0 || isOnPage(draft, pathname) ? null : createElement("a", {
69
+ className: "mk-unsent-where",
70
+ href: route
71
+ }, UNSENT_COPY.elsewhere(pathOf(route) ?? route)), createElement("button", {
52
72
  type: "button",
53
73
  className: "mk-unsent-drop",
54
74
  "aria-label": UNSENT_COPY.discard,
package/dist/maple.js CHANGED
@@ -16,6 +16,7 @@ import { Item } from "./island/item.js";
16
16
  import { List } from "./island/list.js";
17
17
  import { NewComment, PickButton } from "./island/new-comment.js";
18
18
  import { Settings } from "./island/settings.js";
19
+ import { ImportDrafts, OtherDrafts } from "./island/transfer.js";
19
20
  import { IslandTrigger } from "./island/trigger.js";
20
21
  import { Unsent } from "./island/unsent.js";
21
22
  import { MapleMarkLayer } from "./marks/layer.js";
@@ -48,7 +49,7 @@ function inventory(branch, label, defaultOpen) {
48
49
  }, createElement(IslandTrigger), createElement(IslandContent, null, createElement(Header, null, createElement(Logo), createElement(Branch, {
49
50
  branch,
50
51
  ...label === void 0 ? {} : { label }
51
- }), createElement(Settings)), createElement(MapleNotice, { during: LOAD_CALLS }), createElement(Filters), createElement(List, { children: (comment) => createElement(Item, { comment }) }), createElement(Unsent), createElement(Approve), createElement(NewComment, null, ...PICK_ORDER.map((kind) => createElement(PickButton, {
52
+ }), createElement(Settings)), createElement(MapleNotice, { during: LOAD_CALLS }), createElement(Filters), createElement(List, { children: (comment) => createElement(Item, { comment }) }), createElement(OtherDrafts), createElement(Unsent), createElement(ImportDrafts), createElement(Approve), createElement(NewComment, null, ...PICK_ORDER.map((kind) => createElement(PickButton, {
52
53
  key: kind,
53
54
  kind
54
55
  })))));
@@ -1,16 +1,17 @@
1
1
  import { useMapleUi } from "../context.js";
2
2
  import { hoverHandlers } from "../hover.js";
3
3
  import { culled, markSpot, placeMark } from "./geometry.js";
4
+ import { usePathname } from "./route.js";
4
5
  import { useFrameLoop, viewportHeight } from "./frame.js";
5
6
  import { ringLabel } from "./label.js";
6
7
  import { MapleMark } from "./mark.js";
7
8
  import { useNudges } from "./nudge.js";
8
9
  import { OFF_ATTRIBUTE, flag, place } from "./paint.js";
9
- import { addresses, draftPlacements, placements } from "./placement.js";
10
+ import { addresses, draftPlacements, placements, rectangleOf } from "./placement.js";
10
11
  import { MapleTargetRing } from "./ring.js";
11
12
  import { createElement, forwardRef, useCallback, useEffect, useMemo, useRef } from "react";
12
13
  import { useMaple, useMapleClient } from "@maple-kit/react";
13
- import { kindOf, regionBox, resolveAnchor, sourceFor } from "@maple-kit/core/anchor";
14
+ import { kindOf, membersBox, regionBox, resolveAnchor, sourceFor } from "@maple-kit/core/anchor";
14
15
  //#region src/marks/layer.ts
15
16
  const PART = "Maple.MarkLayer";
16
17
  const MapleMarkLayer = forwardRef(function MapleMarkLayer(props, ref) {
@@ -34,7 +35,12 @@ const MapleMarkLayer = forwardRef(function MapleMarkLayer(props, ref) {
34
35
  container
35
36
  ]);
36
37
  const drafts = useWaiting(state);
37
- const drafted = useMemo(() => draftPlacements(drafts, container.ownerDocument), [drafts, container]);
38
+ const pathname = usePathname(container.ownerDocument.defaultView);
39
+ const drafted = useMemo(() => draftPlacements(drafts, container.ownerDocument, pathname), [
40
+ drafts,
41
+ container,
42
+ pathname
43
+ ]);
38
44
  const nudges = useNudges();
39
45
  const nodes = useRef(/* @__PURE__ */ new Map());
40
46
  const paint = useCallback(() => {
@@ -132,6 +138,7 @@ function pointing(client, id) {
132
138
  };
133
139
  }
134
140
  function boxOf(placement) {
141
+ if (placement.members) return membersBox(placement.members);
135
142
  const box = placement.range?.getBoundingClientRect() ?? placement.element.getBoundingClientRect();
136
143
  return placement.region === void 0 ? box : regionBox(box, placement.region);
137
144
  }
@@ -163,6 +170,7 @@ function markProps(placement, view) {
163
170
  author: comment.author.name,
164
171
  on: ringLabel({
165
172
  kind: kindOf(comment.anchor),
173
+ anchor: comment.anchor,
166
174
  element: placement.element
167
175
  }),
168
176
  selected: comment.id === view.selectedId,
@@ -179,6 +187,7 @@ function draftProps(placement, view) {
179
187
  confidence: placement.confidence,
180
188
  on: ringLabel({
181
189
  kind: kindOf(draft.anchor),
190
+ anchor: draft.anchor,
182
191
  element: placement.element
183
192
  }),
184
193
  peeked: draft.id === view.peeked,
@@ -199,8 +208,10 @@ function marked(input, id, state) {
199
208
  return {
200
209
  target: hit.range ?? hit.element,
201
210
  ...hit.region === void 0 ? {} : { region: hit.region },
211
+ ...hit.members === void 0 ? {} : { members: hit.members },
202
212
  label: ringLabel({
203
213
  kind: kindOf(hit.anchor),
214
+ anchor: hit.anchor,
204
215
  element: hit.element
205
216
  }),
206
217
  ...noteFor({
@@ -237,7 +248,7 @@ function composing(target, input) {
237
248
  if (found.status !== "resolved") return {};
238
249
  return {
239
250
  target: found.range ?? found.element,
240
- ...target.anchor.region === void 0 ? {} : { region: target.anchor.region },
251
+ ...rectangleOf(target.anchor, found),
241
252
  label,
242
253
  ...noteFor({
243
254
  anchor: target.anchor,
@@ -1,5 +1,5 @@
1
1
  import { Draft } from "@maple-kit/core/overlay";
2
- import { AnchorRegion } from "@maple-kit/core/anchor";
2
+ import { Anchor, AnchorRegion, PlacedMember } from "@maple-kit/core/anchor";
3
3
  import { Comment } from "@maple-kit/core";
4
4
  //#region src/marks/placement.d.ts
5
5
  /** Where on the page something is, whether it was published or not. */
@@ -9,6 +9,8 @@ export interface Located {
9
9
  readonly range?: Range;
10
10
  /** The rectangle, when the comment is on a region rather than an element. */
11
11
  readonly region?: AnchorRegion;
12
+ /** The elements a region covers, when they placed it: the rectangle follows them. */
13
+ readonly members?: readonly PlacedMember[];
12
14
  readonly confidence: number;
13
15
  }
14
16
  /** A comment the page still has somewhere to put. */
@@ -1,3 +1,4 @@
1
+ import { isOnPage } from "./route.js";
1
2
  import { kindOf, resolveAnchor } from "@maple-kit/core/anchor";
2
3
  //#region src/marks/placement.ts
3
4
  function addresses(comments) {
@@ -16,8 +17,9 @@ function placements(comments, address, root) {
16
17
  }
17
18
  return placed;
18
19
  }
19
- function draftPlacements(drafts, root) {
20
+ function draftPlacements(drafts, root, pathname) {
20
21
  return drafts.flatMap((draft) => {
22
+ if (!isOnPage(draft, pathname)) return [];
21
23
  const found = locate(draft.anchor, root);
22
24
  return found ? [{
23
25
  ...found,
@@ -35,9 +37,13 @@ function locate(anchor, root) {
35
37
  return {
36
38
  element: found.element,
37
39
  ...found.range === void 0 ? {} : { range: found.range },
38
- ...anchor.region === void 0 ? {} : { region: anchor.region },
40
+ ...rectangleOf(anchor, found),
39
41
  confidence: found.confidence
40
42
  };
41
43
  }
44
+ function rectangleOf(anchor, found) {
45
+ if (found.members) return { members: found.members };
46
+ return anchor.region === void 0 ? {} : { region: anchor.region };
47
+ }
42
48
  //#endregion
43
- export { addresses, draftPlacements, placements };
49
+ export { addresses, draftPlacements, placements, rectangleOf };
@@ -1,4 +1,4 @@
1
- import { AnchorRegion } from "@maple-kit/core/anchor";
1
+ import { AnchorRegion, PlacedMember } from "@maple-kit/core/anchor";
2
2
  //#region src/marks/ring.d.ts
3
3
  /** Set on the label when it was moved under the anchor to clear page text. */
4
4
  export declare const BELOW_ATTRIBUTE = "data-mk-below";
@@ -18,6 +18,11 @@ export interface TargetRingProps {
18
18
  * ring is that rectangle rather than the element it was measured in.
19
19
  */
20
20
  readonly region?: AnchorRegion;
21
+ /**
22
+ * The elements a region covers. When they are known the ring is the
23
+ * rectangle they grow into, and `region` is not used.
24
+ */
25
+ readonly members?: readonly PlacedMember[];
21
26
  /** The label's words. `ringLabel` builds them; the ring never invents them. */
22
27
  readonly label?: string;
23
28
  /** A second line under the label. Developer detail: where this is written. */
@@ -5,7 +5,7 @@ import { labelSpotFor } from "./clearance.js";
5
5
  import { useFrameLoop, viewportHeight } from "./frame.js";
6
6
  import { MOVING_ATTRIBUTE, OFF_ATTRIBUTE, flag, place } from "./paint.js";
7
7
  import { createElement, forwardRef, useCallback, useRef, useState } from "react";
8
- import { regionBox } from "@maple-kit/core/anchor";
8
+ import { membersBox, regionBox } from "@maple-kit/core/anchor";
9
9
  //#region src/marks/ring.ts
10
10
  const BELOW_ATTRIBUTE = "data-mk-below";
11
11
  const END_ATTRIBUTE = "data-mk-end";
@@ -16,18 +16,23 @@ function linesOf(target) {
16
16
  function empty(rect) {
17
17
  return rect.width === 0 && rect.height === 0;
18
18
  }
19
+ function rectOf(target, region, members) {
20
+ if (members) return membersBox(members);
21
+ const box = target.getBoundingClientRect();
22
+ return region === void 0 ? box : regionBox(box, region);
23
+ }
19
24
  const MapleTargetRing = forwardRef(function MapleTargetRing(props, ref) {
20
- const { className, label, note, region, state = "composing", target } = props;
25
+ const { className, label, members, note, region, state = "composing", target } = props;
21
26
  const { container } = useMapleUi(PART);
22
27
  const ring = useRef(null);
23
28
  const cap = useRef(null);
24
29
  const runs = useRef([]);
25
30
  const [lines, setLines] = useState(0);
31
+ const drawsRegion = region !== void 0 || members !== void 0;
26
32
  const paint = useCallback((moving) => {
27
33
  const node = ring.current;
28
34
  if (!node || !target) return;
29
- const box = target.getBoundingClientRect();
30
- const rect = region === void 0 ? box : regionBox(box, region);
35
+ const rect = rectOf(target, region, members);
31
36
  const away = empty(rect) || culled(rect, viewportHeight(container));
32
37
  flag(node, OFF_ATTRIBUTE, away);
33
38
  flag(node, MOVING_ATTRIBUTE, moving);
@@ -35,7 +40,7 @@ const MapleTargetRing = forwardRef(function MapleTargetRing(props, ref) {
35
40
  const drawn = ringBox(rect);
36
41
  place(node, drawn);
37
42
  if (cap.current && !moving) corner(cap.current, drawn, container);
38
- const boxes = region === void 0 ? linesOf(target) : [];
43
+ const boxes = drawsRegion ? [] : linesOf(target);
39
44
  if (boxes.length !== lines) setLines(boxes.length);
40
45
  boxes.forEach((box, index) => {
41
46
  const run = runs.current[index];
@@ -43,7 +48,9 @@ const MapleTargetRing = forwardRef(function MapleTargetRing(props, ref) {
43
48
  });
44
49
  }, [
45
50
  container,
51
+ drawsRegion,
46
52
  lines,
53
+ members,
47
54
  region,
48
55
  target
49
56
  ]);
@@ -52,8 +59,8 @@ const MapleTargetRing = forwardRef(function MapleTargetRing(props, ref) {
52
59
  return createElement("div", {
53
60
  className: className ? `mk-ring ${className}` : "mk-ring",
54
61
  "data-mk-state": state,
55
- "data-mk-passage": String(region === void 0 && "startContainer" in target),
56
- "data-mk-region": String(region !== void 0),
62
+ "data-mk-passage": String(!drawsRegion && "startContainer" in target),
63
+ "data-mk-region": String(drawsRegion),
57
64
  ref: composeRefs(ref, (node) => {
58
65
  ring.current = node;
59
66
  })
@@ -0,0 +1,37 @@
1
+ import { useSyncExternalStore } from "react";
2
+ //#region src/marks/route.ts
3
+ const ROUTE_POLL_MS = 400;
4
+ function pathOf(url) {
5
+ if (url === void 0 || url === "") return void 0;
6
+ try {
7
+ const { pathname } = new URL(url, "https://route.invalid");
8
+ return trimmed(pathname);
9
+ } catch {
10
+ return;
11
+ }
12
+ }
13
+ function trimmed(pathname) {
14
+ let end = pathname.length;
15
+ while (end > 1 && pathname[end - 1] === "/") end -= 1;
16
+ return pathname.slice(0, end);
17
+ }
18
+ function draftRoute(draft) {
19
+ const url = draft.context?.url;
20
+ return pathOf(url) === void 0 ? void 0 : url?.replace(/^\w+:\/\/[^/]*/, "");
21
+ }
22
+ function isOnPage(draft, pathname) {
23
+ const path = pathOf(draft.context?.url);
24
+ return path === void 0 || path === pathOf(pathname);
25
+ }
26
+ function usePathname(view) {
27
+ return useSyncExternalStore((notify) => {
28
+ view?.addEventListener("popstate", notify);
29
+ const timer = setInterval(notify, ROUTE_POLL_MS);
30
+ return () => {
31
+ view?.removeEventListener("popstate", notify);
32
+ clearInterval(timer);
33
+ };
34
+ }, () => view?.location.pathname ?? "/", () => "/");
35
+ }
36
+ //#endregion
37
+ export { draftRoute, isOnPage, pathOf, usePathname };
@@ -11,6 +11,8 @@ export declare const MOCK_COPY: {
11
11
  readonly label: "Mock this page";
12
12
  readonly field: "Find a call";
13
13
  readonly sentence: "Say a state, like “no items yet”";
14
+ readonly failed: "Couldn't read that. Try again, or set calls below.";
15
+ readonly refused: "The server refused that. Set calls below.";
14
16
  readonly unnamed: "That doesn't name a state this page's data can be in.";
15
17
  readonly real: "Real";
16
18
  readonly escape: "Esc";
@@ -32,6 +32,8 @@ const MOCK_COPY = {
32
32
  label: "Mock this page",
33
33
  field: "Find a call",
34
34
  sentence: "Say a state, like “no items yet”",
35
+ failed: "Couldn't read that. Try again, or set calls below.",
36
+ refused: "The server refused that. Set calls below.",
35
37
  unnamed: "That doesn't name a state this page's data can be in.",
36
38
  real: "Real",
37
39
  escape: "Esc",
@@ -13,7 +13,8 @@ const LAYER_COPY = {
13
13
  off: "Off",
14
14
  realValue: "Real value",
15
15
  notEvaluated: "Named by the mock, not evaluated on this page",
16
- serverActs: "The server still acts as you."
16
+ serverActs: "The server still acts as you.",
17
+ reloads: "reloads the page"
17
18
  };
18
19
  function Layers(props) {
19
20
  const { client, state } = props;
@@ -24,6 +25,8 @@ function Layers(props) {
24
25
  const grants = [];
25
26
  const real = state.realAs;
26
27
  const roles = identity?.role?.values ?? [];
28
+ const reloading = identity?.server !== void 0;
29
+ const titled = (title) => reloading ? `${title} · ${LAYER_COPY.reloads}` : title;
27
30
  if (roles.length > 0) {
28
31
  const role = state.draftAs?.role;
29
32
  rows.push(row({
@@ -60,8 +63,8 @@ function Layers(props) {
60
63
  return createElement("div", {
61
64
  className: "mk-mock-layers",
62
65
  ref: panel
63
- }, rows.length === 0 ? null : section(LAYER_COPY.shownAs, rows), grants.length === 0 ? null : createElement(Fold, {
64
- title: LAYER_COPY.permissions,
66
+ }, rows.length === 0 ? null : section(titled(LAYER_COPY.shownAs), rows), grants.length === 0 ? null : createElement(Fold, {
67
+ title: titled(LAYER_COPY.permissions),
65
68
  rows: grants
66
69
  }), flags.length === 0 ? null : createElement(Fold, {
67
70
  title: LAYER_COPY.flags,
package/dist/mock/mock.js CHANGED
@@ -101,7 +101,7 @@ function Box(props) {
101
101
  value: state.query,
102
102
  ref: field,
103
103
  onChange: (event) => client.setQuery(event.currentTarget.value)
104
- }), createElement("kbd", { className: "mk-mock-key-hint" }, MOCK_COPY.escape)), state.unnamed ? createElement("p", { className: "mk-mock-unnamed" }, MOCK_COPY.unnamed) : null, createElement("div", { className: "mk-mock-body" }, createElement("p", { className: "mk-mock-route" }, `${MOCK_COPY.routePrefix} `, createElement("span", { className: "mk-mono" }, state.route)), calls(state, client), layers && createElement(layers.Layers, {
104
+ }), createElement("kbd", { className: "mk-mock-key-hint" }, MOCK_COPY.escape)), aside(state) && createElement("p", { className: "mk-mock-unnamed" }, aside(state)), createElement("div", { className: "mk-mock-body" }, createElement("p", { className: "mk-mock-route" }, `${MOCK_COPY.routePrefix} `, createElement("span", { className: "mk-mono" }, state.route)), calls(state, client), layers && createElement(layers.Layers, {
105
105
  state,
106
106
  client
107
107
  })), createElement(Foot, {
@@ -109,6 +109,10 @@ function Box(props) {
109
109
  state
110
110
  }));
111
111
  }
112
+ function aside({ planFailure, unnamed }) {
113
+ if (planFailure !== void 0) return MOCK_COPY[planFailure];
114
+ return unnamed ? MOCK_COPY.unnamed : void 0;
115
+ }
112
116
  function calls(state, client) {
113
117
  if (state.calls.length === 0) {
114
118
  const said = state.query === "" ? MOCK_COPY.nothingRecorded : MOCK_COPY.nothingMatches;
@@ -1,4 +1,4 @@
1
1
  import { noticeCss } from "./css.js";
2
- import { NOTICE_COPY, offersSignIn } from "./language.js";
2
+ import { NOTICE_COPY, isSetupGap, offersSignIn } from "./language.js";
3
3
  import { MapleNotice, MapleNoticeProps } from "./notice.js";
4
- export { MapleNotice, type MapleNoticeProps, NOTICE_COPY, MapleNotice as Notice, noticeCss, offersSignIn };
4
+ export { MapleNotice, type MapleNoticeProps, NOTICE_COPY, MapleNotice as Notice, isSetupGap, noticeCss, offersSignIn };
@@ -1,4 +1,4 @@
1
1
  import { noticeCss } from "./css.js";
2
- import { NOTICE_COPY, offersSignIn } from "./language.js";
2
+ import { NOTICE_COPY, isSetupGap, offersSignIn } from "./language.js";
3
3
  import { MapleNotice } from "./notice.js";
4
- export { MapleNotice, NOTICE_COPY, MapleNotice as Notice, noticeCss, offersSignIn };
4
+ export { MapleNotice, NOTICE_COPY, MapleNotice as Notice, isSetupGap, noticeCss, offersSignIn };
@@ -1,4 +1,4 @@
1
- import { FailureKind } from "@maple-kit/core/client";
1
+ import { FailureKind, GitHubLink } from "@maple-kit/core/client";
2
2
  //#region src/notice/language.d.ts
3
3
  /** The notice's own words. */
4
4
  export declare const NOTICE_COPY: {
@@ -12,4 +12,7 @@ export declare const NOTICE_COPY: {
12
12
  };
13
13
  /** Whether the offer is a sign-in or a retry. A 401 is nobody's to retry. */
14
14
  export declare function offersSignIn(kind: FailureKind, linkable: boolean): boolean;
15
+ /** A 401 with no sign-in on offer is a setup gap, not the reviewer's doing:
16
+ * "sign in" would point at a door that is not there. */
17
+ export declare function isSetupGap(kind: FailureKind, github: GitHubLink): boolean;
15
18
  //#endregion
@@ -10,5 +10,8 @@ const NOTICE_COPY = {
10
10
  function offersSignIn(kind, linkable) {
11
11
  return kind === "unauthorized" && linkable;
12
12
  }
13
+ function isSetupGap(kind, github) {
14
+ return kind === "unauthorized" && github.state === "unsupported";
15
+ }
13
16
  //#endregion
14
- export { NOTICE_COPY, offersSignIn };
17
+ export { NOTICE_COPY, isSetupGap, offersSignIn };
@@ -1,6 +1,6 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { renderPart } from "../part.js";
3
- import { NOTICE_COPY, offersSignIn } from "./language.js";
3
+ import { NOTICE_COPY, isSetupGap, offersSignIn } from "./language.js";
4
4
  import { createElement, forwardRef } from "react";
5
5
  import { useMaple, useMapleClient } from "@maple-kit/react";
6
6
  //#region src/notice/notice.ts
@@ -31,7 +31,7 @@ const MapleNotice = forwardRef(function MapleNotice(props, ref) {
31
31
  ]);
32
32
  });
33
33
  function said(error, github) {
34
- return error.kind === "unauthorized" && github.state === "unsupported" ? NOTICE_COPY.noSignIn : error.message;
34
+ return isSetupGap(error.kind, github) ? NOTICE_COPY.noSignIn : error.message;
35
35
  }
36
36
  function offer(error, github, client) {
37
37
  if (offersSignIn(error.kind, github.state === "failed" || github.state === "unlinked")) return button(NOTICE_COPY.signIn, () => void client.linkGitHub());
@@ -1,5 +1,5 @@
1
1
  import { toCommentContext } from "@maple-kit/core/overlay";
2
- import { describeElement, describeRange, labelFor, regionOf } from "@maple-kit/core/anchor";
2
+ import { captureMembers, describeElement, describeRange, labelFor, regionOf } from "@maple-kit/core/anchor";
3
3
  //#region src/picker/target.ts
4
4
  function elementOf(pick) {
5
5
  return pick.kind === "text" ? rangeElement(pick.range) : pick.element;
@@ -24,9 +24,11 @@ function anchorFor(pick, element, root) {
24
24
  if (pick.kind === "text") return describeRange(pick.range, options);
25
25
  const described = describeElement(element, options);
26
26
  if (pick.kind !== "region") return described;
27
+ const members = captureMembers(pick.rect, options);
27
28
  return {
28
29
  ...described,
29
- region: regionOf(pick.rect, element.getBoundingClientRect())
30
+ region: regionOf(pick.rect, element.getBoundingClientRect()),
31
+ ...members.length === 0 ? {} : { members }
30
32
  };
31
33
  }
32
34
  function rangeElement(range) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maple-kit/ui",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Maple's composed reviewer parts: the marks, the island and the composer",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -33,9 +33,9 @@
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "dependencies": {
36
- "@maple-kit/react": "0.14.0",
37
- "@maple-kit/core": "0.14.0",
38
- "@maple-kit/mock": "0.14.0"
36
+ "@maple-kit/core": "0.15.0",
37
+ "@maple-kit/mock": "0.15.0",
38
+ "@maple-kit/react": "0.15.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": "^18.2.0 || ^19.0.0",