@tldraw/commenting 5.4.0-canary.e10ff229bb14 → 5.4.0-canary.ea38889d4bb7
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.md +1 -0
- package/dist-cjs/canvas/license.js +1 -1
- package/dist-cjs/canvas/license.js.map +2 -2
- package/dist-cjs/index.d.ts +24 -17
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/ui/emoji-picker.js +1 -1
- package/dist-cjs/ui/emoji-picker.js.map +2 -2
- package/dist-cjs/ui/reaction-picker.js +9 -3
- package/dist-cjs/ui/reaction-picker.js.map +2 -2
- package/dist-esm/canvas/license.mjs +2 -2
- package/dist-esm/canvas/license.mjs.map +2 -2
- package/dist-esm/index.d.mts +24 -17
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/ui/emoji-picker.mjs +1 -1
- package/dist-esm/ui/emoji-picker.mjs.map +2 -2
- package/dist-esm/ui/reaction-picker.mjs +9 -3
- package/dist-esm/ui/reaction-picker.mjs.map +2 -2
- package/package.json +13 -5
- package/src/canvas/license.ts +5 -3
- package/src/ui/emoji-picker.tsx +7 -3
- package/src/ui/reaction-picker.test.ts +36 -0
- package/src/ui/reaction-picker.tsx +15 -4
package/LICENSE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This code is licensed under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md)
|
|
@@ -23,6 +23,6 @@ __export(license_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(license_exports);
|
|
24
24
|
var import_tldraw = require("tldraw");
|
|
25
25
|
function useCommentingEnabled() {
|
|
26
|
-
return (0, import_tldraw.useLicenseFeatureFlag)((0, import_tldraw.
|
|
26
|
+
return (0, import_tldraw.useLicenseFeatureFlag)((0, import_tldraw.useMaybeLicenseManager)(), "commenting");
|
|
27
27
|
}
|
|
28
28
|
//# sourceMappingURL=license.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/license.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { useLicenseFeatureFlag, useMaybeLicenseManager } from 'tldraw'\n\n/**\n * Whether commenting is licensed for this editor. Enabled in development; in production it requires\n * a tldraw license that includes the commenting feature (or the collaboration umbrella that grants\n * it). Reactive: re-reads when license validation resolves, and returns `false` while validation is\n * pending, so gated UI stays hidden until the license is confirmed. Works outside `<Tldraw />` too:\n * UI mounted via `EditorProvider` resolves the license through the editor, and with no editor at\n * all this is `false`.\n *\n * The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment\n * tool's Quick Action) gate on this. Use it to gate any custom commenting UI the same way.\n * @public\n */\nexport function useCommentingEnabled(): boolean {\n\treturn useLicenseFeatureFlag(useMaybeLicenseManager(), 'commenting')\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA8D;AAcvD,SAAS,uBAAgC;AAC/C,aAAO,yCAAsB,sCAAuB,GAAG,YAAY;AACpE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Editor } from 'tldraw';
|
|
|
7
7
|
import { EditorAtom } from 'tldraw';
|
|
8
8
|
import { filterMentionMembers } from '@tldraw/mentions';
|
|
9
9
|
import { JSX } from 'react/jsx-runtime';
|
|
10
|
+
import { KeyboardEvent as KeyboardEvent_2 } from 'react';
|
|
10
11
|
import { Mention } from '@tldraw/mentions';
|
|
11
12
|
import { MentionList } from '@tldraw/mentions';
|
|
12
13
|
import { MentionListProps } from '@tldraw/mentions';
|
|
@@ -128,7 +129,7 @@ export declare interface CommentBodyProps {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
/** A single comment: Avatar, Byline, and a body slot the consumer renders. @public @react */
|
|
131
|
-
export declare function CommentCard({ author, body, date, you, edited, actions, footer }: CommentCardProps): JSX.Element;
|
|
132
|
+
export declare function CommentCard({ author, body, date, you, edited, actions, footer, }: CommentCardProps): JSX.Element;
|
|
132
133
|
|
|
133
134
|
/** @public */
|
|
134
135
|
export declare interface CommentCardProps {
|
|
@@ -154,7 +155,7 @@ export declare interface CommentCardProps {
|
|
|
154
155
|
* floating toolbar. Presentational by default; pass value/onChange/onSubmit to drive it as a form.
|
|
155
156
|
* @public @react
|
|
156
157
|
*/
|
|
157
|
-
export declare function CommentComposer({ author, placeholder, value, onChange, onSubmit, sendLabel, onArrowUpWhenEmpty, disabled, autoFocus, leading, getMentionSuggestions, renderMentionSuggestion }: CommentComposerProps): JSX.Element;
|
|
158
|
+
export declare function CommentComposer({ author, placeholder, value, onChange, onSubmit, sendLabel, onArrowUpWhenEmpty, disabled, autoFocus, leading, getMentionSuggestions, renderMentionSuggestion, }: CommentComposerProps): JSX.Element;
|
|
158
159
|
|
|
159
160
|
/** @public */
|
|
160
161
|
export declare interface CommentComposerProps {
|
|
@@ -398,7 +399,7 @@ export declare interface CommentingOptions {
|
|
|
398
399
|
}
|
|
399
400
|
|
|
400
401
|
/** One thread's row in a {@link CommentsList}. @public @react */
|
|
401
|
-
export declare function CommentListItem({ id, author, preview, date, resolved, page, count, selected, reactions, href, resolvedLabel, onSelect }: CommentListItemRenderProps): JSX.Element;
|
|
402
|
+
export declare function CommentListItem({ id, author, preview, date, resolved, page, count, selected, reactions, href, resolvedLabel, onSelect, }: CommentListItemRenderProps): JSX.Element;
|
|
402
403
|
|
|
403
404
|
/** @public */
|
|
404
405
|
export declare interface CommentListItemProps {
|
|
@@ -490,7 +491,7 @@ export declare interface CommentPinProps {
|
|
|
490
491
|
* than in the reaction row, so opening it doesn't chase the row as reactions are added.
|
|
491
492
|
* @public @react
|
|
492
493
|
*/
|
|
493
|
-
export declare function CommentReactionPicker({ comment, currentUserId, emoji }: CommentReactionPickerProps): JSX.Element | null;
|
|
494
|
+
export declare function CommentReactionPicker({ comment, currentUserId, emoji, }: CommentReactionPickerProps): JSX.Element | null;
|
|
494
495
|
|
|
495
496
|
/** @public */
|
|
496
497
|
export declare interface CommentReactionPickerProps {
|
|
@@ -520,7 +521,7 @@ export declare interface CommentReactionsProps {
|
|
|
520
521
|
|
|
521
522
|
/** The funnel dropdown in the sidebar header: toggles for which threads the list shows.
|
|
522
523
|
* @public @react */
|
|
523
|
-
export declare function CommentsFilterMenu({ canFilterByAuthor, canFilterByUnread }: CommentsFilterMenuProps): JSX.Element;
|
|
524
|
+
export declare function CommentsFilterMenu({ canFilterByAuthor, canFilterByUnread, }: CommentsFilterMenuProps): JSX.Element;
|
|
524
525
|
|
|
525
526
|
/** @public */
|
|
526
527
|
export declare interface CommentsFilterMenuProps {
|
|
@@ -543,7 +544,7 @@ export declare const commentsHidden: EditorAtom<boolean>;
|
|
|
543
544
|
* `CanvasCommentsSidebar` wires it to the store, but a consumer can build their own list from this.
|
|
544
545
|
* @public @react
|
|
545
546
|
*/
|
|
546
|
-
export declare function CommentsList({ items, onSelect, header, headerAction, empty, resolvedLabel, renderItem }: CommentsListProps): JSX.Element;
|
|
547
|
+
export declare function CommentsList({ items, onSelect, header, headerAction, empty, resolvedLabel, renderItem, }: CommentsListProps): JSX.Element;
|
|
547
548
|
|
|
548
549
|
/** @public */
|
|
549
550
|
export declare interface CommentsListProps {
|
|
@@ -598,7 +599,7 @@ export declare function CommentsVisibilityToggle(): JSX.Element;
|
|
|
598
599
|
* composer's handlers; how each comment renders is overridable via `renderComment`.
|
|
599
600
|
* @public @react
|
|
600
601
|
*/
|
|
601
|
-
export declare function CommentThread({ comments, header, headerActions, resolvedBanner, composer, footer, renderComment }: CommentThreadProps): JSX.Element;
|
|
602
|
+
export declare function CommentThread({ comments, header, headerActions, resolvedBanner, composer, footer, renderComment, }: CommentThreadProps): JSX.Element;
|
|
602
603
|
|
|
603
604
|
/** @public */
|
|
604
605
|
export declare interface CommentThreadProps {
|
|
@@ -661,7 +662,7 @@ export declare class CommentTool extends StateNode {
|
|
|
661
662
|
export declare const commentToolOverrides: TLUiOverrides;
|
|
662
663
|
|
|
663
664
|
/** @public */
|
|
664
|
-
export declare const commentTools: typeof CommentTool[];
|
|
665
|
+
export declare const commentTools: (typeof CommentTool)[];
|
|
665
666
|
|
|
666
667
|
/** @public @react */
|
|
667
668
|
export declare function CountBadge({ count, open }: CountBadgeProps): JSX.Element;
|
|
@@ -716,7 +717,7 @@ export declare const defaultCommentingOptions: {
|
|
|
716
717
|
readonly dragHistory: undefined;
|
|
717
718
|
readonly enableClustering: true;
|
|
718
719
|
readonly enableRegions: false;
|
|
719
|
-
readonly history:
|
|
720
|
+
readonly history: 'ignore';
|
|
720
721
|
readonly impreciseShapeAnchor: {
|
|
721
722
|
readonly x: 1;
|
|
722
723
|
readonly y: 0;
|
|
@@ -803,7 +804,7 @@ export declare function editComment(editor: Editor, comment: TLComment, body: TL
|
|
|
803
804
|
* dismissal; `ReactionPicker` renders it in a dropdown under the add-reaction button.
|
|
804
805
|
* @public @react
|
|
805
806
|
*/
|
|
806
|
-
export declare function EmojiPicker({ emoji, selected, onSelect, renderReaction }: EmojiPickerProps): JSX.Element;
|
|
807
|
+
export declare function EmojiPicker({ emoji, selected, onSelect, renderReaction, }: EmojiPickerProps): JSX.Element;
|
|
807
808
|
|
|
808
809
|
/** @public */
|
|
809
810
|
export declare interface EmojiPickerProps {
|
|
@@ -811,8 +812,11 @@ export declare interface EmojiPickerProps {
|
|
|
811
812
|
emoji?: string[];
|
|
812
813
|
/** Emoji the current user has already reacted with; shown as pressed. */
|
|
813
814
|
selected?: string[];
|
|
814
|
-
/**
|
|
815
|
-
|
|
815
|
+
/**
|
|
816
|
+
* Called when an emoji is chosen. Custom palettes should forward their click or keydown event
|
|
817
|
+
* so `ReactionPicker` can keep the palette open on shift-picks.
|
|
818
|
+
*/
|
|
819
|
+
onSelect?(emoji: string, event?: KeyboardEvent_2 | MouseEvent_2): void;
|
|
816
820
|
/** How to draw each emoji token. Defaults to the token string (OS emoji font). */
|
|
817
821
|
renderReaction?: RenderReaction;
|
|
818
822
|
}
|
|
@@ -982,7 +986,7 @@ export declare function putCommentRecords(editor: Editor, records: TLCommentReco
|
|
|
982
986
|
* tooltip naming who reacted (the current user included), when `enableHoverList` is set.
|
|
983
987
|
* @public @react
|
|
984
988
|
*/
|
|
985
|
-
export declare function Reaction({ emoji, count, active, reactors, enableHoverList, renderReaction, ReactionTooltip, onClick }: ReactionProps): JSX.Element;
|
|
989
|
+
export declare function Reaction({ emoji, count, active, reactors, enableHoverList, renderReaction, ReactionTooltip, onClick, }: ReactionProps): JSX.Element;
|
|
986
990
|
|
|
987
991
|
/**
|
|
988
992
|
* The add-reaction affordance: a smiley button that opens an emoji grid.
|
|
@@ -991,10 +995,11 @@ export declare function Reaction({ emoji, count, active, reactors, enableHoverLi
|
|
|
991
995
|
* reactions are added and the row reflows.
|
|
992
996
|
*
|
|
993
997
|
* Picking an emoji dismisses the grid — adding and removing alike, since either way the picker has
|
|
994
|
-
* done its job — and hands focus back to the trigger.
|
|
998
|
+
* done its job — and hands focus back to the trigger. Shift-picking keeps the grid open, for
|
|
999
|
+
* choosing several reactions in a row.
|
|
995
1000
|
* @public @react
|
|
996
1001
|
*/
|
|
997
|
-
export declare function ReactionPicker({ emoji, selected, onSelect, renderReaction, palette: Palette, menuId, className }: ReactionPickerProps): JSX.Element;
|
|
1002
|
+
export declare function ReactionPicker({ emoji, selected, onSelect, renderReaction, palette: Palette, menuId, className, }: ReactionPickerProps): JSX.Element;
|
|
998
1003
|
|
|
999
1004
|
/** @public */
|
|
1000
1005
|
export declare interface ReactionPickerProps {
|
|
@@ -1062,7 +1067,7 @@ export declare interface ReactionReactor {
|
|
|
1062
1067
|
* fixed as reactions are added here.
|
|
1063
1068
|
* @public @react
|
|
1064
1069
|
*/
|
|
1065
|
-
export declare function Reactions({ reactions, onToggle, canReact, enableHoverList, renderReaction, ReactionTooltip }: ReactionsProps): JSX.Element | null;
|
|
1070
|
+
export declare function Reactions({ reactions, onToggle, canReact, enableHoverList, renderReaction, ReactionTooltip, }: ReactionsProps): JSX.Element | null;
|
|
1066
1071
|
|
|
1067
1072
|
/** @public */
|
|
1068
1073
|
export declare interface ReactionsProps {
|
|
@@ -1352,7 +1357,9 @@ export declare function useCanModifyComment(currentUserId: null | string | undef
|
|
|
1352
1357
|
* Whether commenting is licensed for this editor. Enabled in development; in production it requires
|
|
1353
1358
|
* a tldraw license that includes the commenting feature (or the collaboration umbrella that grants
|
|
1354
1359
|
* it). Reactive: re-reads when license validation resolves, and returns `false` while validation is
|
|
1355
|
-
* pending, so gated UI stays hidden until the license is confirmed.
|
|
1360
|
+
* pending, so gated UI stays hidden until the license is confirmed. Works outside `<Tldraw />` too:
|
|
1361
|
+
* UI mounted via `EditorProvider` resolves the license through the editor, and with no editor at
|
|
1362
|
+
* all this is `false`.
|
|
1356
1363
|
*
|
|
1357
1364
|
* The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment
|
|
1358
1365
|
* tool's Quick Action) gate on this. Use it to gate any custom commenting UI the same way.
|
package/dist-cjs/index.js
CHANGED
|
@@ -144,7 +144,7 @@ var import_state = require("./canvas/state");
|
|
|
144
144
|
var import_thread_state = require("./canvas/thread-state");
|
|
145
145
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
146
146
|
"@tldraw/commenting",
|
|
147
|
-
"5.4.0-canary.
|
|
147
|
+
"5.4.0-canary.ea38889d4bb7",
|
|
148
148
|
"cjs"
|
|
149
149
|
);
|
|
150
150
|
//# sourceMappingURL=index.js.map
|
|
@@ -44,7 +44,7 @@ function EmojiPicker({
|
|
|
44
44
|
className: active ? "tlui-cmt-emoji-picker__item tlui-cmt-emoji-picker__item--active" : "tlui-cmt-emoji-picker__item",
|
|
45
45
|
"aria-label": value,
|
|
46
46
|
"aria-pressed": active,
|
|
47
|
-
onClick: () => onSelect?.(value),
|
|
47
|
+
onClick: (e) => onSelect?.(value, e),
|
|
48
48
|
children: renderReaction(value)
|
|
49
49
|
},
|
|
50
50
|
value
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ui/emoji-picker.tsx"],
|
|
4
|
-
"sourcesContent": ["import { RenderReaction, defaultRenderReaction } from './reaction'\n\n/**\n * The reaction emoji palette: the small, opinionated set the picker offers *and* the set a\n * reaction is allowed to use. It's deliberately short \u2014 a quick affordance under a comment, not a\n * full emoji keyboard \u2014 and it's the single source of truth for \"which emoji may a reaction\n * carry\". A reaction's `emoji` field is a free-form `string`, so this palette is what keeps it to\n * real, offered emoji; check membership with {@link isAllowedReactionEmoji}. Pass a custom `emoji`\n * array to `ReactionPicker` to offer a different set \u2014 validate against that same set if you do.\n *\n * @public\n */\nexport const DEFAULT_REACTION_EMOJI = ['\uD83D\uDC4D', '\uD83D\uDC4E', '\u2764\uFE0F', '\uD83C\uDF89', '\uD83D\uDE04', '\uD83D\uDE2E', '\uD83D\uDE22', '\uD83D\uDE4F', '\uD83D\uDC40', '\uD83D\uDD25']\n\n/**\n * Whether `emoji` belongs to a reaction palette (defaults to {@link DEFAULT_REACTION_EMOJI}). Use\n * it to reject arbitrary strings before writing a reaction, so a scripted client can't spam a\n * comment with junk `emoji` values that the picker would never offer. Pass a custom `palette` to\n * match a customized picker.\n *\n * @public\n */\nexport function isAllowedReactionEmoji(\n\temoji: string,\n\tpalette: readonly string[] = DEFAULT_REACTION_EMOJI\n): boolean {\n\treturn palette.includes(emoji)\n}\n\n/** @public */\nexport interface EmojiPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed. */\n\tselected?: string[]\n\t
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import { type KeyboardEvent, type MouseEvent } from 'react'\nimport { RenderReaction, defaultRenderReaction } from './reaction'\n\n/**\n * The reaction emoji palette: the small, opinionated set the picker offers *and* the set a\n * reaction is allowed to use. It's deliberately short \u2014 a quick affordance under a comment, not a\n * full emoji keyboard \u2014 and it's the single source of truth for \"which emoji may a reaction\n * carry\". A reaction's `emoji` field is a free-form `string`, so this palette is what keeps it to\n * real, offered emoji; check membership with {@link isAllowedReactionEmoji}. Pass a custom `emoji`\n * array to `ReactionPicker` to offer a different set \u2014 validate against that same set if you do.\n *\n * @public\n */\nexport const DEFAULT_REACTION_EMOJI = ['\uD83D\uDC4D', '\uD83D\uDC4E', '\u2764\uFE0F', '\uD83C\uDF89', '\uD83D\uDE04', '\uD83D\uDE2E', '\uD83D\uDE22', '\uD83D\uDE4F', '\uD83D\uDC40', '\uD83D\uDD25']\n\n/**\n * Whether `emoji` belongs to a reaction palette (defaults to {@link DEFAULT_REACTION_EMOJI}). Use\n * it to reject arbitrary strings before writing a reaction, so a scripted client can't spam a\n * comment with junk `emoji` values that the picker would never offer. Pass a custom `palette` to\n * match a customized picker.\n *\n * @public\n */\nexport function isAllowedReactionEmoji(\n\temoji: string,\n\tpalette: readonly string[] = DEFAULT_REACTION_EMOJI\n): boolean {\n\treturn palette.includes(emoji)\n}\n\n/** @public */\nexport interface EmojiPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed. */\n\tselected?: string[]\n\t/**\n\t * Called when an emoji is chosen. Custom palettes should forward their click or keydown event\n\t * so `ReactionPicker` can keep the palette open on shift-picks.\n\t */\n\tonSelect?(emoji: string, event?: MouseEvent | KeyboardEvent): void\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n}\n\n/**\n * A grid of emoji to react with. Presentational and unpositioned \u2014 the host owns placement and\n * dismissal; `ReactionPicker` renders it in a dropdown under the add-reaction button.\n * @public @react\n */\nexport function EmojiPicker({\n\temoji = DEFAULT_REACTION_EMOJI,\n\tselected,\n\tonSelect,\n\trenderReaction = defaultRenderReaction,\n}: EmojiPickerProps) {\n\treturn (\n\t\t<div className=\"tlui-cmt-emoji-picker\" role=\"group\">\n\t\t\t{emoji.map((value) => {\n\t\t\t\tconst active = selected?.includes(value) ?? false\n\t\t\t\treturn (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={value}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\tactive\n\t\t\t\t\t\t\t\t? 'tlui-cmt-emoji-picker__item tlui-cmt-emoji-picker__item--active'\n\t\t\t\t\t\t\t\t: 'tlui-cmt-emoji-picker__item'\n\t\t\t\t\t\t}\n\t\t\t\t\t\taria-label={value}\n\t\t\t\t\t\taria-pressed={active}\n\t\t\t\t\t\tonClick={(e) => onSelect?.(value, e)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{renderReaction(value)}\n\t\t\t\t\t</button>\n\t\t\t\t)\n\t\t\t})}\n\t\t</div>\n\t)\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6DK;AA5DL,sBAAsD;AAY/C,MAAM,yBAAyB,CAAC,aAAM,aAAM,gBAAM,aAAM,aAAM,aAAM,aAAM,aAAM,aAAM,WAAI;AAU1F,SAAS,uBACf,OACA,UAA6B,wBACnB;AACV,SAAO,QAAQ,SAAS,KAAK;AAC9B;AAsBO,SAAS,YAAY;AAAA,EAC3B,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,iBAAiB;AAClB,GAAqB;AACpB,SACC,4CAAC,SAAI,WAAU,yBAAwB,MAAK,SAC1C,gBAAM,IAAI,CAAC,UAAU;AACrB,UAAM,SAAS,UAAU,SAAS,KAAK,KAAK;AAC5C,WACC;AAAA,MAAC;AAAA;AAAA,QAEA,MAAK;AAAA,QACL,WACC,SACG,oEACA;AAAA,QAEJ,cAAY;AAAA,QACZ,gBAAc;AAAA,QACd,SAAS,CAAC,MAAM,WAAW,OAAO,CAAC;AAAA,QAElC,yBAAe,KAAK;AAAA;AAAA,MAXhB;AAAA,IAYN;AAAA,EAEF,CAAC,GACF;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -18,7 +18,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var reaction_picker_exports = {};
|
|
20
20
|
__export(reaction_picker_exports, {
|
|
21
|
-
ReactionPicker: () => ReactionPicker
|
|
21
|
+
ReactionPicker: () => ReactionPicker,
|
|
22
|
+
isMultiSelectPick: () => isMultiSelectPick
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(reaction_picker_exports);
|
|
24
25
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -26,6 +27,9 @@ var import_react = require("react");
|
|
|
26
27
|
var import_tldraw = require("tldraw");
|
|
27
28
|
var import_emoji_picker = require("./emoji-picker");
|
|
28
29
|
var import_icons = require("./icons");
|
|
30
|
+
function isMultiSelectPick(event) {
|
|
31
|
+
return !!event?.shiftKey;
|
|
32
|
+
}
|
|
29
33
|
function ReactionPicker({
|
|
30
34
|
emoji,
|
|
31
35
|
selected,
|
|
@@ -40,9 +44,11 @@ function ReactionPicker({
|
|
|
40
44
|
const generatedId = (0, import_react.useId)();
|
|
41
45
|
const id = menuId ?? `comment-reactions-${generatedId}`;
|
|
42
46
|
const handleSelect = (0, import_react.useCallback)(
|
|
43
|
-
(value) => {
|
|
47
|
+
(value, event) => {
|
|
44
48
|
onSelect?.(value);
|
|
45
|
-
|
|
49
|
+
if (!isMultiSelectPick(event)) {
|
|
50
|
+
import_tldraw.tlmenus.deleteOpenMenu(id, editor?.contextId);
|
|
51
|
+
}
|
|
46
52
|
},
|
|
47
53
|
[onSelect, id, editor]
|
|
48
54
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ui/reaction-picker.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useCallback, useId, type ComponentType } from 'react'\nimport {\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\ttlmenus,\n\tuseMaybeEditor,\n\tuseTranslation,\n} from 'tldraw'\nimport { EmojiPicker, type EmojiPickerProps } from './emoji-picker'\nimport { SmileyIcon } from './icons'\nimport { RenderReaction } from './reaction'\n\n/** @public */\nexport interface ReactionPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed in the grid. */\n\tselected?: string[]\n\t/** Called when an emoji is chosen. */\n\tonSelect?(emoji: string): void\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/**\n\t * What the button opens \u2014 the thing that produces a token. Defaults to `EmojiPicker`, the grid of\n\t * emoji. Swap it for any component taking the same props to offer something else entirely (the\n\t * drawn reactions example replaces it with a canvas you draw in); the props are passed straight\n\t * through either way.\n\t */\n\tpalette?: ComponentType<EmojiPickerProps>\n\t/**\n\t * Identifies the menu in tldraw's global menu registry, which keys open/closed state by id.\n\t * A thread renders one picker per comment, so this must differ per comment \u2014 sharing an id\n\t * makes every picker in the thread open at once. Defaults to a per-instance generated id;\n\t * pass one only for a stabler, more debuggable value.\n\t */\n\tmenuId?: string\n\t/** Class for the trigger button. Defaults to the card-action style, matching the \u22EF button. */\n\tclassName?: string\n}\n\n/**\n * The add-reaction affordance: a smiley button that opens an emoji grid.\n *\n * Anchored to its own button rather than to the reactions row, so the menu keeps its position as\n * reactions are added and the row reflows.\n *\n * Picking an emoji dismisses the grid \u2014 adding and removing alike, since either way the picker has\n * done its job \u2014 and hands focus back to the trigger.\n * @public @react\n */\nexport function ReactionPicker({\n\temoji,\n\tselected,\n\tonSelect,\n\trenderReaction,\n\tpalette: Palette = EmojiPicker,\n\tmenuId,\n\tclassName = 'tlui-cmt-thread__action',\n}: ReactionPickerProps) {\n\tconst msg = useTranslation()\n\tconst editor = useMaybeEditor()\n\t// Unique per mounted picker unless the host supplies something stabler \u2014 see `menuId`.\n\tconst generatedId = useId()\n\tconst id = menuId ?? `comment-reactions-${generatedId}`\n\n\t// The dropdown's open state lives in tldraw's global menu registry, so dropping the entry is\n\t// what closes it. The palette's tokens are plain buttons \u2014 a swappable component, not radix\n\t// menu items \u2014 so nothing dismisses the menu on its own.\n\tconst handleSelect = useCallback(\n\t\t(value: string) => {\n\t\t\tonSelect?.(value)\n\t\t\ttlmenus.deleteOpenMenu(id, editor?.contextId)\n\t\t},\n\t\t[onSelect, id, editor]\n\t)\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id={id}>\n\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={msg('comments.add-reaction')}\n\t\t\t\t\ttitle={msg('comments.add-reaction')}\n\t\t\t\t\tclassName={className}\n\t\t\t\t>\n\t\t\t\t\t<SmileyIcon />\n\t\t\t\t</TldrawUiButton>\n\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t{/* left-aligned under the trigger, so the grid opens rightward off the card's edge */}\n\t\t\t<TldrawUiDropdownMenuContent side=\"bottom\" align=\"start\" alignOffset={0} sideOffset={4}>\n\t\t\t\t<Palette\n\t\t\t\t\temoji={emoji}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\tonSelect={handleSelect}\n\t\t\t\t\trenderReaction={renderReaction}\n\t\t\t\t/>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["import { useCallback, useId, type ComponentType, type KeyboardEvent, type MouseEvent } from 'react'\nimport {\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\ttlmenus,\n\tuseMaybeEditor,\n\tuseTranslation,\n} from 'tldraw'\nimport { EmojiPicker, type EmojiPickerProps } from './emoji-picker'\nimport { SmileyIcon } from './icons'\nimport { RenderReaction } from './reaction'\n\n/** @public */\nexport interface ReactionPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed in the grid. */\n\tselected?: string[]\n\t/** Called when an emoji is chosen. */\n\tonSelect?(emoji: string): void\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/**\n\t * What the button opens \u2014 the thing that produces a token. Defaults to `EmojiPicker`, the grid of\n\t * emoji. Swap it for any component taking the same props to offer something else entirely (the\n\t * drawn reactions example replaces it with a canvas you draw in); the props are passed straight\n\t * through either way.\n\t */\n\tpalette?: ComponentType<EmojiPickerProps>\n\t/**\n\t * Identifies the menu in tldraw's global menu registry, which keys open/closed state by id.\n\t * A thread renders one picker per comment, so this must differ per comment \u2014 sharing an id\n\t * makes every picker in the thread open at once. Defaults to a per-instance generated id;\n\t * pass one only for a stabler, more debuggable value.\n\t */\n\tmenuId?: string\n\t/** Class for the trigger button. Defaults to the card-action style, matching the \u22EF button. */\n\tclassName?: string\n}\n\n/**\n * Whether a palette pick asked to keep the palette open (shift held) for multi-select.\n * @internal\n */\nexport function isMultiSelectPick(event?: MouseEvent | KeyboardEvent): boolean {\n\treturn !!event?.shiftKey\n}\n\n/**\n * The add-reaction affordance: a smiley button that opens an emoji grid.\n *\n * Anchored to its own button rather than to the reactions row, so the menu keeps its position as\n * reactions are added and the row reflows.\n *\n * Picking an emoji dismisses the grid \u2014 adding and removing alike, since either way the picker has\n * done its job \u2014 and hands focus back to the trigger. Shift-picking keeps the grid open, for\n * choosing several reactions in a row.\n * @public @react\n */\nexport function ReactionPicker({\n\temoji,\n\tselected,\n\tonSelect,\n\trenderReaction,\n\tpalette: Palette = EmojiPicker,\n\tmenuId,\n\tclassName = 'tlui-cmt-thread__action',\n}: ReactionPickerProps) {\n\tconst msg = useTranslation()\n\tconst editor = useMaybeEditor()\n\t// Unique per mounted picker unless the host supplies something stabler \u2014 see `menuId`.\n\tconst generatedId = useId()\n\tconst id = menuId ?? `comment-reactions-${generatedId}`\n\n\t// The dropdown's open state lives in tldraw's global menu registry, so dropping the entry is\n\t// what closes it. The palette's tokens are plain buttons \u2014 a swappable component, not radix\n\t// menu items \u2014 so nothing dismisses the menu on its own.\n\tconst handleSelect = useCallback(\n\t\t(value: string, event?: MouseEvent | KeyboardEvent) => {\n\t\t\tonSelect?.(value)\n\t\t\tif (!isMultiSelectPick(event)) {\n\t\t\t\ttlmenus.deleteOpenMenu(id, editor?.contextId)\n\t\t\t}\n\t\t},\n\t\t[onSelect, id, editor]\n\t)\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id={id}>\n\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={msg('comments.add-reaction')}\n\t\t\t\t\ttitle={msg('comments.add-reaction')}\n\t\t\t\t\tclassName={className}\n\t\t\t\t>\n\t\t\t\t\t<SmileyIcon />\n\t\t\t\t</TldrawUiButton>\n\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t{/* left-aligned under the trigger, so the grid opens rightward off the card's edge */}\n\t\t\t<TldrawUiDropdownMenuContent side=\"bottom\" align=\"start\" alignOffset={0} sideOffset={4}>\n\t\t\t\t<Palette\n\t\t\t\t\temoji={emoji}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\tonSelect={handleSelect}\n\t\t\t\t\trenderReaction={renderReaction}\n\t\t\t\t/>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0FE;AA1FF,mBAA4F;AAC5F,oBAQO;AACP,0BAAmD;AACnD,mBAA2B;AAmCpB,SAAS,kBAAkB,OAA6C;AAC9E,SAAO,CAAC,CAAC,OAAO;AACjB;AAaO,SAAS,eAAe;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,UAAU;AAAA,EACnB;AAAA,EACA,YAAY;AACb,GAAwB;AACvB,QAAM,UAAM,8BAAe;AAC3B,QAAM,aAAS,8BAAe;AAE9B,QAAM,kBAAc,oBAAM;AAC1B,QAAM,KAAK,UAAU,qBAAqB,WAAW;AAKrD,QAAM,mBAAe;AAAA,IACpB,CAAC,OAAe,UAAuC;AACtD,iBAAW,KAAK;AAChB,UAAI,CAAC,kBAAkB,KAAK,GAAG;AAC9B,8BAAQ,eAAe,IAAI,QAAQ,SAAS;AAAA,MAC7C;AAAA,IACD;AAAA,IACA,CAAC,UAAU,IAAI,MAAM;AAAA,EACtB;AAEA,SACC,6CAAC,0CAAyB,IACzB;AAAA,gDAAC,6CACA;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,SAAS,IAAI,uBAAuB;AAAA,QACpC,OAAO,IAAI,uBAAuB;AAAA,QAClC;AAAA,QAEA,sDAAC,2BAAW;AAAA;AAAA,IACb,GACD;AAAA,IAEA,4CAAC,6CAA4B,MAAK,UAAS,OAAM,SAAQ,aAAa,GAAG,YAAY,GACpF;AAAA,MAAC;AAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV;AAAA;AAAA,IACD,GACD;AAAA,KACD;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useLicenseFeatureFlag, useMaybeLicenseManager } from "tldraw";
|
|
2
2
|
function useCommentingEnabled() {
|
|
3
|
-
return useLicenseFeatureFlag(
|
|
3
|
+
return useLicenseFeatureFlag(useMaybeLicenseManager(), "commenting");
|
|
4
4
|
}
|
|
5
5
|
export {
|
|
6
6
|
useCommentingEnabled
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/canvas/license.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { useLicenseFeatureFlag, useMaybeLicenseManager } from 'tldraw'\n\n/**\n * Whether commenting is licensed for this editor. Enabled in development; in production it requires\n * a tldraw license that includes the commenting feature (or the collaboration umbrella that grants\n * it). Reactive: re-reads when license validation resolves, and returns `false` while validation is\n * pending, so gated UI stays hidden until the license is confirmed. Works outside `<Tldraw />` too:\n * UI mounted via `EditorProvider` resolves the license through the editor, and with no editor at\n * all this is `false`.\n *\n * The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment\n * tool's Quick Action) gate on this. Use it to gate any custom commenting UI the same way.\n * @public\n */\nexport function useCommentingEnabled(): boolean {\n\treturn useLicenseFeatureFlag(useMaybeLicenseManager(), 'commenting')\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,uBAAuB,8BAA8B;AAcvD,SAAS,uBAAgC;AAC/C,SAAO,sBAAsB,uBAAuB,GAAG,YAAY;AACpE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.d.mts
CHANGED
|
@@ -7,6 +7,7 @@ import { Editor } from 'tldraw';
|
|
|
7
7
|
import { EditorAtom } from 'tldraw';
|
|
8
8
|
import { filterMentionMembers } from '@tldraw/mentions';
|
|
9
9
|
import { JSX } from 'react/jsx-runtime';
|
|
10
|
+
import { KeyboardEvent as KeyboardEvent_2 } from 'react';
|
|
10
11
|
import { Mention } from '@tldraw/mentions';
|
|
11
12
|
import { MentionList } from '@tldraw/mentions';
|
|
12
13
|
import { MentionListProps } from '@tldraw/mentions';
|
|
@@ -128,7 +129,7 @@ export declare interface CommentBodyProps {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
/** A single comment: Avatar, Byline, and a body slot the consumer renders. @public @react */
|
|
131
|
-
export declare function CommentCard({ author, body, date, you, edited, actions, footer }: CommentCardProps): JSX.Element;
|
|
132
|
+
export declare function CommentCard({ author, body, date, you, edited, actions, footer, }: CommentCardProps): JSX.Element;
|
|
132
133
|
|
|
133
134
|
/** @public */
|
|
134
135
|
export declare interface CommentCardProps {
|
|
@@ -154,7 +155,7 @@ export declare interface CommentCardProps {
|
|
|
154
155
|
* floating toolbar. Presentational by default; pass value/onChange/onSubmit to drive it as a form.
|
|
155
156
|
* @public @react
|
|
156
157
|
*/
|
|
157
|
-
export declare function CommentComposer({ author, placeholder, value, onChange, onSubmit, sendLabel, onArrowUpWhenEmpty, disabled, autoFocus, leading, getMentionSuggestions, renderMentionSuggestion }: CommentComposerProps): JSX.Element;
|
|
158
|
+
export declare function CommentComposer({ author, placeholder, value, onChange, onSubmit, sendLabel, onArrowUpWhenEmpty, disabled, autoFocus, leading, getMentionSuggestions, renderMentionSuggestion, }: CommentComposerProps): JSX.Element;
|
|
158
159
|
|
|
159
160
|
/** @public */
|
|
160
161
|
export declare interface CommentComposerProps {
|
|
@@ -398,7 +399,7 @@ export declare interface CommentingOptions {
|
|
|
398
399
|
}
|
|
399
400
|
|
|
400
401
|
/** One thread's row in a {@link CommentsList}. @public @react */
|
|
401
|
-
export declare function CommentListItem({ id, author, preview, date, resolved, page, count, selected, reactions, href, resolvedLabel, onSelect }: CommentListItemRenderProps): JSX.Element;
|
|
402
|
+
export declare function CommentListItem({ id, author, preview, date, resolved, page, count, selected, reactions, href, resolvedLabel, onSelect, }: CommentListItemRenderProps): JSX.Element;
|
|
402
403
|
|
|
403
404
|
/** @public */
|
|
404
405
|
export declare interface CommentListItemProps {
|
|
@@ -490,7 +491,7 @@ export declare interface CommentPinProps {
|
|
|
490
491
|
* than in the reaction row, so opening it doesn't chase the row as reactions are added.
|
|
491
492
|
* @public @react
|
|
492
493
|
*/
|
|
493
|
-
export declare function CommentReactionPicker({ comment, currentUserId, emoji }: CommentReactionPickerProps): JSX.Element | null;
|
|
494
|
+
export declare function CommentReactionPicker({ comment, currentUserId, emoji, }: CommentReactionPickerProps): JSX.Element | null;
|
|
494
495
|
|
|
495
496
|
/** @public */
|
|
496
497
|
export declare interface CommentReactionPickerProps {
|
|
@@ -520,7 +521,7 @@ export declare interface CommentReactionsProps {
|
|
|
520
521
|
|
|
521
522
|
/** The funnel dropdown in the sidebar header: toggles for which threads the list shows.
|
|
522
523
|
* @public @react */
|
|
523
|
-
export declare function CommentsFilterMenu({ canFilterByAuthor, canFilterByUnread }: CommentsFilterMenuProps): JSX.Element;
|
|
524
|
+
export declare function CommentsFilterMenu({ canFilterByAuthor, canFilterByUnread, }: CommentsFilterMenuProps): JSX.Element;
|
|
524
525
|
|
|
525
526
|
/** @public */
|
|
526
527
|
export declare interface CommentsFilterMenuProps {
|
|
@@ -543,7 +544,7 @@ export declare const commentsHidden: EditorAtom<boolean>;
|
|
|
543
544
|
* `CanvasCommentsSidebar` wires it to the store, but a consumer can build their own list from this.
|
|
544
545
|
* @public @react
|
|
545
546
|
*/
|
|
546
|
-
export declare function CommentsList({ items, onSelect, header, headerAction, empty, resolvedLabel, renderItem }: CommentsListProps): JSX.Element;
|
|
547
|
+
export declare function CommentsList({ items, onSelect, header, headerAction, empty, resolvedLabel, renderItem, }: CommentsListProps): JSX.Element;
|
|
547
548
|
|
|
548
549
|
/** @public */
|
|
549
550
|
export declare interface CommentsListProps {
|
|
@@ -598,7 +599,7 @@ export declare function CommentsVisibilityToggle(): JSX.Element;
|
|
|
598
599
|
* composer's handlers; how each comment renders is overridable via `renderComment`.
|
|
599
600
|
* @public @react
|
|
600
601
|
*/
|
|
601
|
-
export declare function CommentThread({ comments, header, headerActions, resolvedBanner, composer, footer, renderComment }: CommentThreadProps): JSX.Element;
|
|
602
|
+
export declare function CommentThread({ comments, header, headerActions, resolvedBanner, composer, footer, renderComment, }: CommentThreadProps): JSX.Element;
|
|
602
603
|
|
|
603
604
|
/** @public */
|
|
604
605
|
export declare interface CommentThreadProps {
|
|
@@ -661,7 +662,7 @@ export declare class CommentTool extends StateNode {
|
|
|
661
662
|
export declare const commentToolOverrides: TLUiOverrides;
|
|
662
663
|
|
|
663
664
|
/** @public */
|
|
664
|
-
export declare const commentTools: typeof CommentTool[];
|
|
665
|
+
export declare const commentTools: (typeof CommentTool)[];
|
|
665
666
|
|
|
666
667
|
/** @public @react */
|
|
667
668
|
export declare function CountBadge({ count, open }: CountBadgeProps): JSX.Element;
|
|
@@ -716,7 +717,7 @@ export declare const defaultCommentingOptions: {
|
|
|
716
717
|
readonly dragHistory: undefined;
|
|
717
718
|
readonly enableClustering: true;
|
|
718
719
|
readonly enableRegions: false;
|
|
719
|
-
readonly history:
|
|
720
|
+
readonly history: 'ignore';
|
|
720
721
|
readonly impreciseShapeAnchor: {
|
|
721
722
|
readonly x: 1;
|
|
722
723
|
readonly y: 0;
|
|
@@ -803,7 +804,7 @@ export declare function editComment(editor: Editor, comment: TLComment, body: TL
|
|
|
803
804
|
* dismissal; `ReactionPicker` renders it in a dropdown under the add-reaction button.
|
|
804
805
|
* @public @react
|
|
805
806
|
*/
|
|
806
|
-
export declare function EmojiPicker({ emoji, selected, onSelect, renderReaction }: EmojiPickerProps): JSX.Element;
|
|
807
|
+
export declare function EmojiPicker({ emoji, selected, onSelect, renderReaction, }: EmojiPickerProps): JSX.Element;
|
|
807
808
|
|
|
808
809
|
/** @public */
|
|
809
810
|
export declare interface EmojiPickerProps {
|
|
@@ -811,8 +812,11 @@ export declare interface EmojiPickerProps {
|
|
|
811
812
|
emoji?: string[];
|
|
812
813
|
/** Emoji the current user has already reacted with; shown as pressed. */
|
|
813
814
|
selected?: string[];
|
|
814
|
-
/**
|
|
815
|
-
|
|
815
|
+
/**
|
|
816
|
+
* Called when an emoji is chosen. Custom palettes should forward their click or keydown event
|
|
817
|
+
* so `ReactionPicker` can keep the palette open on shift-picks.
|
|
818
|
+
*/
|
|
819
|
+
onSelect?(emoji: string, event?: KeyboardEvent_2 | MouseEvent_2): void;
|
|
816
820
|
/** How to draw each emoji token. Defaults to the token string (OS emoji font). */
|
|
817
821
|
renderReaction?: RenderReaction;
|
|
818
822
|
}
|
|
@@ -982,7 +986,7 @@ export declare function putCommentRecords(editor: Editor, records: TLCommentReco
|
|
|
982
986
|
* tooltip naming who reacted (the current user included), when `enableHoverList` is set.
|
|
983
987
|
* @public @react
|
|
984
988
|
*/
|
|
985
|
-
export declare function Reaction({ emoji, count, active, reactors, enableHoverList, renderReaction, ReactionTooltip, onClick }: ReactionProps): JSX.Element;
|
|
989
|
+
export declare function Reaction({ emoji, count, active, reactors, enableHoverList, renderReaction, ReactionTooltip, onClick, }: ReactionProps): JSX.Element;
|
|
986
990
|
|
|
987
991
|
/**
|
|
988
992
|
* The add-reaction affordance: a smiley button that opens an emoji grid.
|
|
@@ -991,10 +995,11 @@ export declare function Reaction({ emoji, count, active, reactors, enableHoverLi
|
|
|
991
995
|
* reactions are added and the row reflows.
|
|
992
996
|
*
|
|
993
997
|
* Picking an emoji dismisses the grid — adding and removing alike, since either way the picker has
|
|
994
|
-
* done its job — and hands focus back to the trigger.
|
|
998
|
+
* done its job — and hands focus back to the trigger. Shift-picking keeps the grid open, for
|
|
999
|
+
* choosing several reactions in a row.
|
|
995
1000
|
* @public @react
|
|
996
1001
|
*/
|
|
997
|
-
export declare function ReactionPicker({ emoji, selected, onSelect, renderReaction, palette: Palette, menuId, className }: ReactionPickerProps): JSX.Element;
|
|
1002
|
+
export declare function ReactionPicker({ emoji, selected, onSelect, renderReaction, palette: Palette, menuId, className, }: ReactionPickerProps): JSX.Element;
|
|
998
1003
|
|
|
999
1004
|
/** @public */
|
|
1000
1005
|
export declare interface ReactionPickerProps {
|
|
@@ -1062,7 +1067,7 @@ export declare interface ReactionReactor {
|
|
|
1062
1067
|
* fixed as reactions are added here.
|
|
1063
1068
|
* @public @react
|
|
1064
1069
|
*/
|
|
1065
|
-
export declare function Reactions({ reactions, onToggle, canReact, enableHoverList, renderReaction, ReactionTooltip }: ReactionsProps): JSX.Element | null;
|
|
1070
|
+
export declare function Reactions({ reactions, onToggle, canReact, enableHoverList, renderReaction, ReactionTooltip, }: ReactionsProps): JSX.Element | null;
|
|
1066
1071
|
|
|
1067
1072
|
/** @public */
|
|
1068
1073
|
export declare interface ReactionsProps {
|
|
@@ -1352,7 +1357,9 @@ export declare function useCanModifyComment(currentUserId: null | string | undef
|
|
|
1352
1357
|
* Whether commenting is licensed for this editor. Enabled in development; in production it requires
|
|
1353
1358
|
* a tldraw license that includes the commenting feature (or the collaboration umbrella that grants
|
|
1354
1359
|
* it). Reactive: re-reads when license validation resolves, and returns `false` while validation is
|
|
1355
|
-
* pending, so gated UI stays hidden until the license is confirmed.
|
|
1360
|
+
* pending, so gated UI stays hidden until the license is confirmed. Works outside `<Tldraw />` too:
|
|
1361
|
+
* UI mounted via `EditorProvider` resolves the license through the editor, and with no editor at
|
|
1362
|
+
* all this is `false`.
|
|
1356
1363
|
*
|
|
1357
1364
|
* The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment
|
|
1358
1365
|
* tool's Quick Action) gate on this. Use it to gate any custom commenting UI the same way.
|
package/dist-esm/index.mjs
CHANGED
|
@@ -103,7 +103,7 @@ import {
|
|
|
103
103
|
import { anchorPagePoint, focusThread, shapeAnchorAt } from "./canvas/thread-state.mjs";
|
|
104
104
|
registerTldrawLibraryVersion(
|
|
105
105
|
"@tldraw/commenting",
|
|
106
|
-
"5.4.0-canary.
|
|
106
|
+
"5.4.0-canary.ea38889d4bb7",
|
|
107
107
|
"esm"
|
|
108
108
|
);
|
|
109
109
|
export {
|
|
@@ -19,7 +19,7 @@ function EmojiPicker({
|
|
|
19
19
|
className: active ? "tlui-cmt-emoji-picker__item tlui-cmt-emoji-picker__item--active" : "tlui-cmt-emoji-picker__item",
|
|
20
20
|
"aria-label": value,
|
|
21
21
|
"aria-pressed": active,
|
|
22
|
-
onClick: () => onSelect?.(value),
|
|
22
|
+
onClick: (e) => onSelect?.(value, e),
|
|
23
23
|
children: renderReaction(value)
|
|
24
24
|
},
|
|
25
25
|
value
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ui/emoji-picker.tsx"],
|
|
4
|
-
"sourcesContent": ["import { RenderReaction, defaultRenderReaction } from './reaction'\n\n/**\n * The reaction emoji palette: the small, opinionated set the picker offers *and* the set a\n * reaction is allowed to use. It's deliberately short \u2014 a quick affordance under a comment, not a\n * full emoji keyboard \u2014 and it's the single source of truth for \"which emoji may a reaction\n * carry\". A reaction's `emoji` field is a free-form `string`, so this palette is what keeps it to\n * real, offered emoji; check membership with {@link isAllowedReactionEmoji}. Pass a custom `emoji`\n * array to `ReactionPicker` to offer a different set \u2014 validate against that same set if you do.\n *\n * @public\n */\nexport const DEFAULT_REACTION_EMOJI = ['\uD83D\uDC4D', '\uD83D\uDC4E', '\u2764\uFE0F', '\uD83C\uDF89', '\uD83D\uDE04', '\uD83D\uDE2E', '\uD83D\uDE22', '\uD83D\uDE4F', '\uD83D\uDC40', '\uD83D\uDD25']\n\n/**\n * Whether `emoji` belongs to a reaction palette (defaults to {@link DEFAULT_REACTION_EMOJI}). Use\n * it to reject arbitrary strings before writing a reaction, so a scripted client can't spam a\n * comment with junk `emoji` values that the picker would never offer. Pass a custom `palette` to\n * match a customized picker.\n *\n * @public\n */\nexport function isAllowedReactionEmoji(\n\temoji: string,\n\tpalette: readonly string[] = DEFAULT_REACTION_EMOJI\n): boolean {\n\treturn palette.includes(emoji)\n}\n\n/** @public */\nexport interface EmojiPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed. */\n\tselected?: string[]\n\t
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { type KeyboardEvent, type MouseEvent } from 'react'\nimport { RenderReaction, defaultRenderReaction } from './reaction'\n\n/**\n * The reaction emoji palette: the small, opinionated set the picker offers *and* the set a\n * reaction is allowed to use. It's deliberately short \u2014 a quick affordance under a comment, not a\n * full emoji keyboard \u2014 and it's the single source of truth for \"which emoji may a reaction\n * carry\". A reaction's `emoji` field is a free-form `string`, so this palette is what keeps it to\n * real, offered emoji; check membership with {@link isAllowedReactionEmoji}. Pass a custom `emoji`\n * array to `ReactionPicker` to offer a different set \u2014 validate against that same set if you do.\n *\n * @public\n */\nexport const DEFAULT_REACTION_EMOJI = ['\uD83D\uDC4D', '\uD83D\uDC4E', '\u2764\uFE0F', '\uD83C\uDF89', '\uD83D\uDE04', '\uD83D\uDE2E', '\uD83D\uDE22', '\uD83D\uDE4F', '\uD83D\uDC40', '\uD83D\uDD25']\n\n/**\n * Whether `emoji` belongs to a reaction palette (defaults to {@link DEFAULT_REACTION_EMOJI}). Use\n * it to reject arbitrary strings before writing a reaction, so a scripted client can't spam a\n * comment with junk `emoji` values that the picker would never offer. Pass a custom `palette` to\n * match a customized picker.\n *\n * @public\n */\nexport function isAllowedReactionEmoji(\n\temoji: string,\n\tpalette: readonly string[] = DEFAULT_REACTION_EMOJI\n): boolean {\n\treturn palette.includes(emoji)\n}\n\n/** @public */\nexport interface EmojiPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed. */\n\tselected?: string[]\n\t/**\n\t * Called when an emoji is chosen. Custom palettes should forward their click or keydown event\n\t * so `ReactionPicker` can keep the palette open on shift-picks.\n\t */\n\tonSelect?(emoji: string, event?: MouseEvent | KeyboardEvent): void\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n}\n\n/**\n * A grid of emoji to react with. Presentational and unpositioned \u2014 the host owns placement and\n * dismissal; `ReactionPicker` renders it in a dropdown under the add-reaction button.\n * @public @react\n */\nexport function EmojiPicker({\n\temoji = DEFAULT_REACTION_EMOJI,\n\tselected,\n\tonSelect,\n\trenderReaction = defaultRenderReaction,\n}: EmojiPickerProps) {\n\treturn (\n\t\t<div className=\"tlui-cmt-emoji-picker\" role=\"group\">\n\t\t\t{emoji.map((value) => {\n\t\t\t\tconst active = selected?.includes(value) ?? false\n\t\t\t\treturn (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={value}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\tactive\n\t\t\t\t\t\t\t\t? 'tlui-cmt-emoji-picker__item tlui-cmt-emoji-picker__item--active'\n\t\t\t\t\t\t\t\t: 'tlui-cmt-emoji-picker__item'\n\t\t\t\t\t\t}\n\t\t\t\t\t\taria-label={value}\n\t\t\t\t\t\taria-pressed={active}\n\t\t\t\t\t\tonClick={(e) => onSelect?.(value, e)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{renderReaction(value)}\n\t\t\t\t\t</button>\n\t\t\t\t)\n\t\t\t})}\n\t\t</div>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AA6DK;AA5DL,SAAyB,6BAA6B;AAY/C,MAAM,yBAAyB,CAAC,aAAM,aAAM,gBAAM,aAAM,aAAM,aAAM,aAAM,aAAM,aAAM,WAAI;AAU1F,SAAS,uBACf,OACA,UAA6B,wBACnB;AACV,SAAO,QAAQ,SAAS,KAAK;AAC9B;AAsBO,SAAS,YAAY;AAAA,EAC3B,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,iBAAiB;AAClB,GAAqB;AACpB,SACC,oBAAC,SAAI,WAAU,yBAAwB,MAAK,SAC1C,gBAAM,IAAI,CAAC,UAAU;AACrB,UAAM,SAAS,UAAU,SAAS,KAAK,KAAK;AAC5C,WACC;AAAA,MAAC;AAAA;AAAA,QAEA,MAAK;AAAA,QACL,WACC,SACG,oEACA;AAAA,QAEJ,cAAY;AAAA,QACZ,gBAAc;AAAA,QACd,SAAS,CAAC,MAAM,WAAW,OAAO,CAAC;AAAA,QAElC,yBAAe,KAAK;AAAA;AAAA,MAXhB;AAAA,IAYN;AAAA,EAEF,CAAC,GACF;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
} from "tldraw";
|
|
12
12
|
import { EmojiPicker } from "./emoji-picker.mjs";
|
|
13
13
|
import { SmileyIcon } from "./icons.mjs";
|
|
14
|
+
function isMultiSelectPick(event) {
|
|
15
|
+
return !!event?.shiftKey;
|
|
16
|
+
}
|
|
14
17
|
function ReactionPicker({
|
|
15
18
|
emoji,
|
|
16
19
|
selected,
|
|
@@ -25,9 +28,11 @@ function ReactionPicker({
|
|
|
25
28
|
const generatedId = useId();
|
|
26
29
|
const id = menuId ?? `comment-reactions-${generatedId}`;
|
|
27
30
|
const handleSelect = useCallback(
|
|
28
|
-
(value) => {
|
|
31
|
+
(value, event) => {
|
|
29
32
|
onSelect?.(value);
|
|
30
|
-
|
|
33
|
+
if (!isMultiSelectPick(event)) {
|
|
34
|
+
tlmenus.deleteOpenMenu(id, editor?.contextId);
|
|
35
|
+
}
|
|
31
36
|
},
|
|
32
37
|
[onSelect, id, editor]
|
|
33
38
|
);
|
|
@@ -54,6 +59,7 @@ function ReactionPicker({
|
|
|
54
59
|
] });
|
|
55
60
|
}
|
|
56
61
|
export {
|
|
57
|
-
ReactionPicker
|
|
62
|
+
ReactionPicker,
|
|
63
|
+
isMultiSelectPick
|
|
58
64
|
};
|
|
59
65
|
//# sourceMappingURL=reaction-picker.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ui/reaction-picker.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useCallback, useId, type ComponentType } from 'react'\nimport {\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\ttlmenus,\n\tuseMaybeEditor,\n\tuseTranslation,\n} from 'tldraw'\nimport { EmojiPicker, type EmojiPickerProps } from './emoji-picker'\nimport { SmileyIcon } from './icons'\nimport { RenderReaction } from './reaction'\n\n/** @public */\nexport interface ReactionPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed in the grid. */\n\tselected?: string[]\n\t/** Called when an emoji is chosen. */\n\tonSelect?(emoji: string): void\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/**\n\t * What the button opens \u2014 the thing that produces a token. Defaults to `EmojiPicker`, the grid of\n\t * emoji. Swap it for any component taking the same props to offer something else entirely (the\n\t * drawn reactions example replaces it with a canvas you draw in); the props are passed straight\n\t * through either way.\n\t */\n\tpalette?: ComponentType<EmojiPickerProps>\n\t/**\n\t * Identifies the menu in tldraw's global menu registry, which keys open/closed state by id.\n\t * A thread renders one picker per comment, so this must differ per comment \u2014 sharing an id\n\t * makes every picker in the thread open at once. Defaults to a per-instance generated id;\n\t * pass one only for a stabler, more debuggable value.\n\t */\n\tmenuId?: string\n\t/** Class for the trigger button. Defaults to the card-action style, matching the \u22EF button. */\n\tclassName?: string\n}\n\n/**\n * The add-reaction affordance: a smiley button that opens an emoji grid.\n *\n * Anchored to its own button rather than to the reactions row, so the menu keeps its position as\n * reactions are added and the row reflows.\n *\n * Picking an emoji dismisses the grid \u2014 adding and removing alike, since either way the picker has\n * done its job \u2014 and hands focus back to the trigger.\n * @public @react\n */\nexport function ReactionPicker({\n\temoji,\n\tselected,\n\tonSelect,\n\trenderReaction,\n\tpalette: Palette = EmojiPicker,\n\tmenuId,\n\tclassName = 'tlui-cmt-thread__action',\n}: ReactionPickerProps) {\n\tconst msg = useTranslation()\n\tconst editor = useMaybeEditor()\n\t// Unique per mounted picker unless the host supplies something stabler \u2014 see `menuId`.\n\tconst generatedId = useId()\n\tconst id = menuId ?? `comment-reactions-${generatedId}`\n\n\t// The dropdown's open state lives in tldraw's global menu registry, so dropping the entry is\n\t// what closes it. The palette's tokens are plain buttons \u2014 a swappable component, not radix\n\t// menu items \u2014 so nothing dismisses the menu on its own.\n\tconst handleSelect = useCallback(\n\t\t(value: string) => {\n\t\t\tonSelect?.(value)\n\t\t\ttlmenus.deleteOpenMenu(id, editor?.contextId)\n\t\t},\n\t\t[onSelect, id, editor]\n\t)\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id={id}>\n\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={msg('comments.add-reaction')}\n\t\t\t\t\ttitle={msg('comments.add-reaction')}\n\t\t\t\t\tclassName={className}\n\t\t\t\t>\n\t\t\t\t\t<SmileyIcon />\n\t\t\t\t</TldrawUiButton>\n\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t{/* left-aligned under the trigger, so the grid opens rightward off the card's edge */}\n\t\t\t<TldrawUiDropdownMenuContent side=\"bottom\" align=\"start\" alignOffset={0} sideOffset={4}>\n\t\t\t\t<Palette\n\t\t\t\t\temoji={emoji}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\tonSelect={handleSelect}\n\t\t\t\t\trenderReaction={renderReaction}\n\t\t\t\t/>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { useCallback, useId, type ComponentType, type KeyboardEvent, type MouseEvent } from 'react'\nimport {\n\tTldrawUiButton,\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n\ttlmenus,\n\tuseMaybeEditor,\n\tuseTranslation,\n} from 'tldraw'\nimport { EmojiPicker, type EmojiPickerProps } from './emoji-picker'\nimport { SmileyIcon } from './icons'\nimport { RenderReaction } from './reaction'\n\n/** @public */\nexport interface ReactionPickerProps {\n\t/** The emoji to offer. Defaults to `DEFAULT_REACTION_EMOJI`. */\n\temoji?: string[]\n\t/** Emoji the current user has already reacted with; shown as pressed in the grid. */\n\tselected?: string[]\n\t/** Called when an emoji is chosen. */\n\tonSelect?(emoji: string): void\n\t/** How to draw each emoji token. Defaults to the token string (OS emoji font). */\n\trenderReaction?: RenderReaction\n\t/**\n\t * What the button opens \u2014 the thing that produces a token. Defaults to `EmojiPicker`, the grid of\n\t * emoji. Swap it for any component taking the same props to offer something else entirely (the\n\t * drawn reactions example replaces it with a canvas you draw in); the props are passed straight\n\t * through either way.\n\t */\n\tpalette?: ComponentType<EmojiPickerProps>\n\t/**\n\t * Identifies the menu in tldraw's global menu registry, which keys open/closed state by id.\n\t * A thread renders one picker per comment, so this must differ per comment \u2014 sharing an id\n\t * makes every picker in the thread open at once. Defaults to a per-instance generated id;\n\t * pass one only for a stabler, more debuggable value.\n\t */\n\tmenuId?: string\n\t/** Class for the trigger button. Defaults to the card-action style, matching the \u22EF button. */\n\tclassName?: string\n}\n\n/**\n * Whether a palette pick asked to keep the palette open (shift held) for multi-select.\n * @internal\n */\nexport function isMultiSelectPick(event?: MouseEvent | KeyboardEvent): boolean {\n\treturn !!event?.shiftKey\n}\n\n/**\n * The add-reaction affordance: a smiley button that opens an emoji grid.\n *\n * Anchored to its own button rather than to the reactions row, so the menu keeps its position as\n * reactions are added and the row reflows.\n *\n * Picking an emoji dismisses the grid \u2014 adding and removing alike, since either way the picker has\n * done its job \u2014 and hands focus back to the trigger. Shift-picking keeps the grid open, for\n * choosing several reactions in a row.\n * @public @react\n */\nexport function ReactionPicker({\n\temoji,\n\tselected,\n\tonSelect,\n\trenderReaction,\n\tpalette: Palette = EmojiPicker,\n\tmenuId,\n\tclassName = 'tlui-cmt-thread__action',\n}: ReactionPickerProps) {\n\tconst msg = useTranslation()\n\tconst editor = useMaybeEditor()\n\t// Unique per mounted picker unless the host supplies something stabler \u2014 see `menuId`.\n\tconst generatedId = useId()\n\tconst id = menuId ?? `comment-reactions-${generatedId}`\n\n\t// The dropdown's open state lives in tldraw's global menu registry, so dropping the entry is\n\t// what closes it. The palette's tokens are plain buttons \u2014 a swappable component, not radix\n\t// menu items \u2014 so nothing dismisses the menu on its own.\n\tconst handleSelect = useCallback(\n\t\t(value: string, event?: MouseEvent | KeyboardEvent) => {\n\t\t\tonSelect?.(value)\n\t\t\tif (!isMultiSelectPick(event)) {\n\t\t\t\ttlmenus.deleteOpenMenu(id, editor?.contextId)\n\t\t\t}\n\t\t},\n\t\t[onSelect, id, editor]\n\t)\n\n\treturn (\n\t\t<TldrawUiDropdownMenuRoot id={id}>\n\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t<TldrawUiButton\n\t\t\t\t\ttype=\"icon\"\n\t\t\t\t\ttooltip={msg('comments.add-reaction')}\n\t\t\t\t\ttitle={msg('comments.add-reaction')}\n\t\t\t\t\tclassName={className}\n\t\t\t\t>\n\t\t\t\t\t<SmileyIcon />\n\t\t\t\t</TldrawUiButton>\n\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t{/* left-aligned under the trigger, so the grid opens rightward off the card's edge */}\n\t\t\t<TldrawUiDropdownMenuContent side=\"bottom\" align=\"start\" alignOffset={0} sideOffset={4}>\n\t\t\t\t<Palette\n\t\t\t\t\temoji={emoji}\n\t\t\t\t\tselected={selected}\n\t\t\t\t\tonSelect={handleSelect}\n\t\t\t\t\trenderReaction={renderReaction}\n\t\t\t\t/>\n\t\t\t</TldrawUiDropdownMenuContent>\n\t\t</TldrawUiDropdownMenuRoot>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "AA0FE,SAQG,KARH;AA1FF,SAAS,aAAa,aAAsE;AAC5F;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,mBAA0C;AACnD,SAAS,kBAAkB;AAmCpB,SAAS,kBAAkB,OAA6C;AAC9E,SAAO,CAAC,CAAC,OAAO;AACjB;AAaO,SAAS,eAAe;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,UAAU;AAAA,EACnB;AAAA,EACA,YAAY;AACb,GAAwB;AACvB,QAAM,MAAM,eAAe;AAC3B,QAAM,SAAS,eAAe;AAE9B,QAAM,cAAc,MAAM;AAC1B,QAAM,KAAK,UAAU,qBAAqB,WAAW;AAKrD,QAAM,eAAe;AAAA,IACpB,CAAC,OAAe,UAAuC;AACtD,iBAAW,KAAK;AAChB,UAAI,CAAC,kBAAkB,KAAK,GAAG;AAC9B,gBAAQ,eAAe,IAAI,QAAQ,SAAS;AAAA,MAC7C;AAAA,IACD;AAAA,IACA,CAAC,UAAU,IAAI,MAAM;AAAA,EACtB;AAEA,SACC,qBAAC,4BAAyB,IACzB;AAAA,wBAAC,+BACA;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,SAAS,IAAI,uBAAuB;AAAA,QACpC,OAAO,IAAI,uBAAuB;AAAA,QAClC;AAAA,QAEA,8BAAC,cAAW;AAAA;AAAA,IACb,GACD;AAAA,IAEA,oBAAC,+BAA4B,MAAK,UAAS,OAAM,SAAQ,aAAa,GAAG,YAAY,GACpF;AAAA,MAAC;AAAA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV;AAAA;AAAA,IACD,GACD;AAAA,KACD;AAEF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/commenting",
|
|
3
3
|
"description": "tldraw commenting UI components.",
|
|
4
|
-
"version": "5.4.0-canary.
|
|
4
|
+
"version": "5.4.0-canary.ea38889d4bb7",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
8
8
|
},
|
|
9
9
|
"homepage": "https://tldraw.dev",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
11
|
+
"tldraw_product": {
|
|
12
|
+
"stableId": "tldraw:commenting",
|
|
13
|
+
"name": "Commenting",
|
|
14
|
+
"type": "feature",
|
|
15
|
+
"parent": "tldraw:collaboration",
|
|
16
|
+
"premium": true,
|
|
17
|
+
"licenseFlag": "FEAT_COMMENTING"
|
|
18
|
+
},
|
|
11
19
|
"repository": {
|
|
12
20
|
"type": "git",
|
|
13
21
|
"url": "https://github.com/tldraw/tldraw"
|
|
@@ -38,7 +46,7 @@
|
|
|
38
46
|
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
|
|
39
47
|
},
|
|
40
48
|
"devDependencies": {
|
|
41
|
-
"@tldraw/driver": "5.4.0-canary.
|
|
49
|
+
"@tldraw/driver": "5.4.0-canary.ea38889d4bb7",
|
|
42
50
|
"@types/react": "^19.2.7",
|
|
43
51
|
"@types/react-dom": "^19.2.3",
|
|
44
52
|
"react": "^19.2.1",
|
|
@@ -56,9 +64,9 @@
|
|
|
56
64
|
"@tiptap/core": "^3.12.1",
|
|
57
65
|
"@tiptap/pm": "^3.12.1",
|
|
58
66
|
"@tiptap/react": "^3.12.1",
|
|
59
|
-
"@tldraw/mentions": "5.4.0-canary.
|
|
60
|
-
"@tldraw/utils": "5.4.0-canary.
|
|
61
|
-
"tldraw": "5.4.0-canary.
|
|
67
|
+
"@tldraw/mentions": "5.4.0-canary.ea38889d4bb7",
|
|
68
|
+
"@tldraw/utils": "5.4.0-canary.ea38889d4bb7",
|
|
69
|
+
"tldraw": "5.4.0-canary.ea38889d4bb7"
|
|
62
70
|
},
|
|
63
71
|
"module": "dist-esm/index.mjs",
|
|
64
72
|
"source": "src/index.ts",
|
package/src/canvas/license.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useLicenseFeatureFlag, useMaybeLicenseManager } from 'tldraw'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Whether commenting is licensed for this editor. Enabled in development; in production it requires
|
|
5
5
|
* a tldraw license that includes the commenting feature (or the collaboration umbrella that grants
|
|
6
6
|
* it). Reactive: re-reads when license validation resolves, and returns `false` while validation is
|
|
7
|
-
* pending, so gated UI stays hidden until the license is confirmed.
|
|
7
|
+
* pending, so gated UI stays hidden until the license is confirmed. Works outside `<Tldraw />` too:
|
|
8
|
+
* UI mounted via `EditorProvider` resolves the license through the editor, and with no editor at
|
|
9
|
+
* all this is `false`.
|
|
8
10
|
*
|
|
9
11
|
* The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment
|
|
10
12
|
* tool's Quick Action) gate on this. Use it to gate any custom commenting UI the same way.
|
|
11
13
|
* @public
|
|
12
14
|
*/
|
|
13
15
|
export function useCommentingEnabled(): boolean {
|
|
14
|
-
return useLicenseFeatureFlag(
|
|
16
|
+
return useLicenseFeatureFlag(useMaybeLicenseManager(), 'commenting')
|
|
15
17
|
}
|
package/src/ui/emoji-picker.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type KeyboardEvent, type MouseEvent } from 'react'
|
|
1
2
|
import { RenderReaction, defaultRenderReaction } from './reaction'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -33,8 +34,11 @@ export interface EmojiPickerProps {
|
|
|
33
34
|
emoji?: string[]
|
|
34
35
|
/** Emoji the current user has already reacted with; shown as pressed. */
|
|
35
36
|
selected?: string[]
|
|
36
|
-
/**
|
|
37
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Called when an emoji is chosen. Custom palettes should forward their click or keydown event
|
|
39
|
+
* so `ReactionPicker` can keep the palette open on shift-picks.
|
|
40
|
+
*/
|
|
41
|
+
onSelect?(emoji: string, event?: MouseEvent | KeyboardEvent): void
|
|
38
42
|
/** How to draw each emoji token. Defaults to the token string (OS emoji font). */
|
|
39
43
|
renderReaction?: RenderReaction
|
|
40
44
|
}
|
|
@@ -65,7 +69,7 @@ export function EmojiPicker({
|
|
|
65
69
|
}
|
|
66
70
|
aria-label={value}
|
|
67
71
|
aria-pressed={active}
|
|
68
|
-
onClick={() => onSelect?.(value)}
|
|
72
|
+
onClick={(e) => onSelect?.(value, e)}
|
|
69
73
|
>
|
|
70
74
|
{renderReaction(value)}
|
|
71
75
|
</button>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { MouseEvent } from 'react'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { isMultiSelectPick } from './reaction-picker'
|
|
4
|
+
|
|
5
|
+
function click(overrides: Partial<MouseEvent> = {}): MouseEvent {
|
|
6
|
+
return {
|
|
7
|
+
metaKey: false,
|
|
8
|
+
ctrlKey: false,
|
|
9
|
+
shiftKey: false,
|
|
10
|
+
altKey: false,
|
|
11
|
+
button: 0,
|
|
12
|
+
...overrides,
|
|
13
|
+
} as MouseEvent
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('isMultiSelectPick', () => {
|
|
17
|
+
it('keeps the palette open on a shift pick', () => {
|
|
18
|
+
expect(isMultiSelectPick(click({ shiftKey: true }))).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('closes on a plain pick', () => {
|
|
22
|
+
expect(isMultiSelectPick(click())).toBe(false)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// custom palettes may call onSelect without an event; that must keep today's close-on-pick
|
|
26
|
+
it('closes when no event is given', () => {
|
|
27
|
+
expect(isMultiSelectPick(undefined)).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it.each(['metaKey', 'ctrlKey', 'altKey'] as const)(
|
|
31
|
+
'does not treat a %s pick as multi-select',
|
|
32
|
+
(modifier) => {
|
|
33
|
+
expect(isMultiSelectPick(click({ [modifier]: true }))).toBe(false)
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useId, type ComponentType } from 'react'
|
|
1
|
+
import { useCallback, useId, type ComponentType, type KeyboardEvent, type MouseEvent } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
TldrawUiButton,
|
|
4
4
|
TldrawUiDropdownMenuContent,
|
|
@@ -40,6 +40,14 @@ export interface ReactionPickerProps {
|
|
|
40
40
|
className?: string
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Whether a palette pick asked to keep the palette open (shift held) for multi-select.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export function isMultiSelectPick(event?: MouseEvent | KeyboardEvent): boolean {
|
|
48
|
+
return !!event?.shiftKey
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
/**
|
|
44
52
|
* The add-reaction affordance: a smiley button that opens an emoji grid.
|
|
45
53
|
*
|
|
@@ -47,7 +55,8 @@ export interface ReactionPickerProps {
|
|
|
47
55
|
* reactions are added and the row reflows.
|
|
48
56
|
*
|
|
49
57
|
* Picking an emoji dismisses the grid — adding and removing alike, since either way the picker has
|
|
50
|
-
* done its job — and hands focus back to the trigger.
|
|
58
|
+
* done its job — and hands focus back to the trigger. Shift-picking keeps the grid open, for
|
|
59
|
+
* choosing several reactions in a row.
|
|
51
60
|
* @public @react
|
|
52
61
|
*/
|
|
53
62
|
export function ReactionPicker({
|
|
@@ -69,9 +78,11 @@ export function ReactionPicker({
|
|
|
69
78
|
// what closes it. The palette's tokens are plain buttons — a swappable component, not radix
|
|
70
79
|
// menu items — so nothing dismisses the menu on its own.
|
|
71
80
|
const handleSelect = useCallback(
|
|
72
|
-
(value: string) => {
|
|
81
|
+
(value: string, event?: MouseEvent | KeyboardEvent) => {
|
|
73
82
|
onSelect?.(value)
|
|
74
|
-
|
|
83
|
+
if (!isMultiSelectPick(event)) {
|
|
84
|
+
tlmenus.deleteOpenMenu(id, editor?.contextId)
|
|
85
|
+
}
|
|
75
86
|
},
|
|
76
87
|
[onSelect, id, editor]
|
|
77
88
|
)
|