@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,119 @@
|
|
|
1
|
+
import { Slot, composeRefs } from "../slot.js";
|
|
2
|
+
import { searchEmoji, shortcodeAt, writeAt } from "./emoji.js";
|
|
3
|
+
import { EmojiGrid, MapleEmoji } from "./picker-emoji.js";
|
|
4
|
+
import { createElement, forwardRef, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { useMaple, useMapleClient } from "@maple-kit/react";
|
|
6
|
+
//#region src/composer/body.ts
|
|
7
|
+
const COMPOSER_PLACEHOLDER = "What is wrong with this?";
|
|
8
|
+
const MapleBody = forwardRef(function MapleBody(props, ref) {
|
|
9
|
+
const { composer } = useMaple();
|
|
10
|
+
const client = useMapleClient();
|
|
11
|
+
const field = useRef(null);
|
|
12
|
+
const [open, setOpen] = useState(false);
|
|
13
|
+
const suggest = useSuggestions(composer.body);
|
|
14
|
+
const reading = composer.viewing !== void 0;
|
|
15
|
+
const Element = props.asChild ? Slot : "textarea";
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (composer.open && !reading) field.current?.focus();
|
|
18
|
+
}, [composer.open, reading]);
|
|
19
|
+
if (reading) return createElement("p", { className: [
|
|
20
|
+
"mk-composer-row",
|
|
21
|
+
"mk-read",
|
|
22
|
+
"mk-body",
|
|
23
|
+
props.className
|
|
24
|
+
].filter(Boolean).join(" ") }, composer.body);
|
|
25
|
+
const write = (glyph, over) => {
|
|
26
|
+
const caret = field.current?.selectionStart ?? composer.body.length;
|
|
27
|
+
const from = over?.start ?? caret;
|
|
28
|
+
const next = writeAt(composer.body, from, caret, glyph);
|
|
29
|
+
client.setBody(next.body);
|
|
30
|
+
suggest.clear();
|
|
31
|
+
setOpen(false);
|
|
32
|
+
restore(field, next.caret);
|
|
33
|
+
};
|
|
34
|
+
const area = createElement(Element, {
|
|
35
|
+
ref: composeRefs(ref, field),
|
|
36
|
+
className: [
|
|
37
|
+
"mk-field",
|
|
38
|
+
"mk-body",
|
|
39
|
+
props.className
|
|
40
|
+
].filter(Boolean).join(" "),
|
|
41
|
+
value: composer.body,
|
|
42
|
+
placeholder: COMPOSER_PLACEHOLDER,
|
|
43
|
+
"aria-label": COMPOSER_PLACEHOLDER,
|
|
44
|
+
onChange: (event) => {
|
|
45
|
+
client.setBody(event.target.value);
|
|
46
|
+
suggest.read(event.target.value, event.target.selectionStart);
|
|
47
|
+
},
|
|
48
|
+
onKeyDown: (event) => suggest.onKeyDown(event, write),
|
|
49
|
+
onBlur: () => suggest.clear()
|
|
50
|
+
});
|
|
51
|
+
return createElement("div", { className: "mk-composer-row mk-field-wrap" }, area, props.emoji === false ? null : createElement(MapleEmoji, {
|
|
52
|
+
open,
|
|
53
|
+
onToggle: setOpen,
|
|
54
|
+
onPick: (glyph) => write(glyph)
|
|
55
|
+
}), menu(suggest, write));
|
|
56
|
+
});
|
|
57
|
+
function menu(suggest, write) {
|
|
58
|
+
if (!suggest.at || suggest.found.length === 0) return null;
|
|
59
|
+
return createElement(EmojiGrid, {
|
|
60
|
+
className: "mk-emoji-menu",
|
|
61
|
+
emoji: suggest.found,
|
|
62
|
+
active: suggest.active,
|
|
63
|
+
onPick: (glyph) => write(glyph, suggest.at)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function useSuggestions(body) {
|
|
67
|
+
const [caret, setCaret] = useState();
|
|
68
|
+
const [dismissed, setDismissed] = useState(false);
|
|
69
|
+
const [active, setActive] = useState(0);
|
|
70
|
+
const at = dismissed || caret === void 0 ? void 0 : shortcodeAt(body, caret);
|
|
71
|
+
const found = at ? searchEmoji(at.query) : [];
|
|
72
|
+
const clear = () => {
|
|
73
|
+
setDismissed(true);
|
|
74
|
+
setActive(0);
|
|
75
|
+
};
|
|
76
|
+
const read = (next, where) => {
|
|
77
|
+
setCaret(where ?? void 0);
|
|
78
|
+
setDismissed(false);
|
|
79
|
+
setActive(0);
|
|
80
|
+
};
|
|
81
|
+
const onKeyDown = (event, write) => {
|
|
82
|
+
if (!at || found.length === 0) return;
|
|
83
|
+
if (event.key === "Escape") {
|
|
84
|
+
event.stopPropagation();
|
|
85
|
+
return clear();
|
|
86
|
+
}
|
|
87
|
+
const step = stepFor(event.key);
|
|
88
|
+
if (step !== 0) {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
return setActive((was) => (was + step + found.length) % found.length);
|
|
91
|
+
}
|
|
92
|
+
if (event.key !== "Enter" && event.key !== "Tab") return;
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
const glyph = found[active]?.glyph;
|
|
95
|
+
if (glyph) write(glyph, at);
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
at,
|
|
99
|
+
found,
|
|
100
|
+
active,
|
|
101
|
+
read,
|
|
102
|
+
clear,
|
|
103
|
+
onKeyDown
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function stepFor(key) {
|
|
107
|
+
if (key === "ArrowDown" || key === "ArrowRight") return 1;
|
|
108
|
+
return key === "ArrowUp" || key === "ArrowLeft" ? -1 : 0;
|
|
109
|
+
}
|
|
110
|
+
function restore(field, caret) {
|
|
111
|
+
requestAnimationFrame(() => {
|
|
112
|
+
const node = field.current;
|
|
113
|
+
if (!node) return;
|
|
114
|
+
node.focus();
|
|
115
|
+
node.setSelectionRange(caret, caret);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
export { COMPOSER_PLACEHOLDER, MapleBody };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AsChildProps } from "../slot.js";
|
|
2
|
+
import { LeaveAsk } from "./leave.js";
|
|
3
|
+
import { SheetDetent } from "./scope.js";
|
|
4
|
+
import { ReactNode } from "react";
|
|
5
|
+
//#region src/composer/composer.d.ts
|
|
6
|
+
/** How the composer is mounted. No prop here changes its look. */
|
|
7
|
+
export interface MapleComposerProps extends AsChildProps {
|
|
8
|
+
readonly className?: string;
|
|
9
|
+
readonly children?: ReactNode;
|
|
10
|
+
/** The frame to inset while the panel is open, rather than cover. */
|
|
11
|
+
readonly inset?: ElementCSSInlineStyle | null;
|
|
12
|
+
/** Hold Space to see through the panel. On unless an application says otherwise. */
|
|
13
|
+
readonly peek?: boolean;
|
|
14
|
+
/** The same `LeaveAsk` the controller was given, so the confirm is drawn here. */
|
|
15
|
+
readonly leave?: LeaveAsk;
|
|
16
|
+
}
|
|
17
|
+
/** What a screen reader calls the panel. */
|
|
18
|
+
export declare const COMPOSER_LABEL = "New comment";
|
|
19
|
+
/** Expand and collapse, in the words the two detents actually do. */
|
|
20
|
+
export declare const DETENT_LABELS: Readonly<Record<SheetDetent, string>>;
|
|
21
|
+
/** The panel. Every part below it reads the controller, never a prop. */
|
|
22
|
+
export declare const MapleComposer: import("react").ForwardRefExoticComponent<MapleComposerProps & import("react").RefAttributes<HTMLElement>>;
|
|
23
|
+
//#endregion
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useMapleUi } from "../context.js";
|
|
2
|
+
import { dataAttributes } from "../data.js";
|
|
3
|
+
import { Slot } from "../slot.js";
|
|
4
|
+
import { ComposerScope } from "./scope.js";
|
|
5
|
+
import { useInset } from "./inset.js";
|
|
6
|
+
import { useHoldToPeek } from "./peek.js";
|
|
7
|
+
import { LeavePrompt } from "./prompt.js";
|
|
8
|
+
import { Fragment, createElement, forwardRef, useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { useMaple, useMapleClient } from "@maple-kit/react";
|
|
10
|
+
import { imageFrom, previewOf } from "@maple-kit/core/screenshot";
|
|
11
|
+
//#region src/composer/composer.ts
|
|
12
|
+
const COMPOSER_LABEL = "New comment";
|
|
13
|
+
const DETENT_LABELS = {
|
|
14
|
+
full: "Show less of the comment",
|
|
15
|
+
half: "Show more of the comment"
|
|
16
|
+
};
|
|
17
|
+
const MapleComposer = forwardRef(function MapleComposer(props, ref) {
|
|
18
|
+
const { composer, user } = useMaple();
|
|
19
|
+
const client = useMapleClient();
|
|
20
|
+
const { container } = useMapleUi("Maple.Composer");
|
|
21
|
+
const view = container.ownerDocument.defaultView;
|
|
22
|
+
const [panel, setPanel] = useState(null);
|
|
23
|
+
const [motion, setMotion] = useState({
|
|
24
|
+
open: composer.open,
|
|
25
|
+
moving: false
|
|
26
|
+
});
|
|
27
|
+
const scope = useScopeValue(composer.open, view, props.peek !== false);
|
|
28
|
+
if (motion.open !== composer.open) setMotion({
|
|
29
|
+
open: composer.open,
|
|
30
|
+
moving: true
|
|
31
|
+
});
|
|
32
|
+
useInset(props.inset, composer.open ? panel : null, view);
|
|
33
|
+
const Element = props.asChild ? Slot : "section";
|
|
34
|
+
const grabber = createElement(Grabber, {
|
|
35
|
+
detent: scope.detent,
|
|
36
|
+
toggle: scope.toggleDetent
|
|
37
|
+
});
|
|
38
|
+
return createElement(Fragment, null, createElement(ComposerScope.Provider, { value: scope }, createElement(Element, {
|
|
39
|
+
...shellAttributes(composer.open, scope, motion),
|
|
40
|
+
...dataAttributes({ provenance: user ? "server" : "guest" }),
|
|
41
|
+
className: props.className ? `mk-composer ${props.className}` : "mk-composer",
|
|
42
|
+
role: "dialog",
|
|
43
|
+
"aria-label": COMPOSER_LABEL,
|
|
44
|
+
ref: composeInto(ref, setPanel),
|
|
45
|
+
onKeyDown: (event) => onEscape(event, client),
|
|
46
|
+
onPaste: (event) => onPaste(event, scope),
|
|
47
|
+
onDragOver: (event) => event.preventDefault(),
|
|
48
|
+
onDrop: (event) => onDrop(event, scope),
|
|
49
|
+
onTransitionEnd: (event) => {
|
|
50
|
+
if (event.target === event.currentTarget) setMotion({
|
|
51
|
+
open: composer.open,
|
|
52
|
+
moving: false
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}, props.asChild ? props.children : createElement(Fragment, null, grabber, props.children))), props.leave ? createElement(LeavePrompt, { ask: props.leave }) : null);
|
|
56
|
+
});
|
|
57
|
+
function shellAttributes(open, scope, motion) {
|
|
58
|
+
return {
|
|
59
|
+
"data-mk-open": String(open),
|
|
60
|
+
"data-mk-peek": String(scope.peeking),
|
|
61
|
+
"data-mk-detent": scope.detent,
|
|
62
|
+
"data-mk-moving": String(motion.moving),
|
|
63
|
+
inert: !open
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function Grabber(props) {
|
|
67
|
+
return createElement("button", {
|
|
68
|
+
type: "button",
|
|
69
|
+
className: "mk-grab mk-hit mk-press",
|
|
70
|
+
"aria-label": DETENT_LABELS[props.detent],
|
|
71
|
+
"aria-expanded": props.detent === "full",
|
|
72
|
+
onClick: props.toggle
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function onEscape(event, client) {
|
|
76
|
+
if (event.key !== "Escape") return;
|
|
77
|
+
event.stopPropagation();
|
|
78
|
+
client.closeComposer();
|
|
79
|
+
}
|
|
80
|
+
function onPaste(event, scope) {
|
|
81
|
+
const image = imageFrom(event.clipboardData);
|
|
82
|
+
if (!image) return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
scope.offer(image);
|
|
85
|
+
}
|
|
86
|
+
function onDrop(event, scope) {
|
|
87
|
+
const image = imageFrom(event.dataTransfer);
|
|
88
|
+
if (!image) return;
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
scope.offer(image);
|
|
91
|
+
}
|
|
92
|
+
function composeInto(ref, set) {
|
|
93
|
+
return (node) => {
|
|
94
|
+
set(node);
|
|
95
|
+
if (typeof ref === "function") ref(node);
|
|
96
|
+
else if (ref) ref.current = node;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function useScopeValue(open, view, peekable) {
|
|
100
|
+
const [detent, setDetent] = useState("half");
|
|
101
|
+
const [pending, setPending] = useState();
|
|
102
|
+
const peeking = useHoldToPeek(view, open && peekable);
|
|
103
|
+
useEffect(() => pending ? () => pending.preview.revoke() : void 0, [pending]);
|
|
104
|
+
return useMemo(() => ({
|
|
105
|
+
peeking,
|
|
106
|
+
detent,
|
|
107
|
+
toggleDetent: () => setDetent((was) => was === "half" ? "full" : "half"),
|
|
108
|
+
pending,
|
|
109
|
+
offer: (image, source) => setPending({
|
|
110
|
+
image,
|
|
111
|
+
preview: previewOf(image),
|
|
112
|
+
source: source ?? "offered"
|
|
113
|
+
}),
|
|
114
|
+
clear: () => setPending(void 0)
|
|
115
|
+
}), [
|
|
116
|
+
peeking,
|
|
117
|
+
detent,
|
|
118
|
+
pending
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { COMPOSER_LABEL, DETENT_LABELS, MapleComposer };
|