@lovett/ui 0.2.0 → 0.2.3
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/dist/{chunk-RBYWGBQ2.js → chunk-GP7BKVZC.js} +8 -5
- package/dist/chunk-GP7BKVZC.js.map +1 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/{rich-composer-impl-5NO443A6.js → rich-composer-impl-F5PQVFZT.js} +3 -3
- package/dist/{rich-composer-impl-5NO443A6.js.map → rich-composer-impl-F5PQVFZT.js.map} +1 -1
- package/dist/styles.css +53 -1
- package/dist/theme-v2.css +26 -0
- package/dist/tokens.css +13 -0
- package/package.json +2 -2
- package/src/__tests__/clip-reserve.test.ts +431 -0
- package/src/detail/__tests__/activity-pane.test.tsx +34 -0
- package/src/detail/activity-pane.tsx +10 -0
- package/src/styles.css +53 -1
- package/src/theme-v2.css +26 -0
- package/src/thread/__tests__/thread.test.tsx +188 -0
- package/src/thread/attachments.tsx +1 -1
- package/src/thread/comment.tsx +48 -2
- package/src/thread/composer.tsx +4 -1
- package/src/thread/reactions.tsx +2 -1
- package/src/thread/types.ts +95 -0
- package/src/tokens.css +13 -0
- package/dist/chunk-RBYWGBQ2.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -7378,6 +7378,16 @@ interface ThreadReactionCount {
|
|
|
7378
7378
|
* ADR-147 D17 — optimistic send is a first-class state, not a nicety.
|
|
7379
7379
|
* A comment silently lost to a bad connection is the worst thing this surface
|
|
7380
7380
|
* can do to someone, and it is the most likely.
|
|
7381
|
+
*
|
|
7382
|
+
* `pending` AND `failed` ARE VIEWER-LOCAL, and that is an invariant a host has
|
|
7383
|
+
* to hold rather than a thing this type can enforce. Both describe a comment
|
|
7384
|
+
* that has not landed: it exists in one browser, no other viewer can see it,
|
|
7385
|
+
* and no read should ever return either value. A host that puts `failed` on a
|
|
7386
|
+
* row it fetched is describing somebody ELSE's comment as the viewer's own
|
|
7387
|
+
* failed send, and the retry drawn beside it — the one control on this surface
|
|
7388
|
+
* with no capability flag, because it never needed one — becomes the same lie
|
|
7389
|
+
* `canEdit` exists to stop. Absent means `sent`, which is what every read
|
|
7390
|
+
* should leave it as.
|
|
7381
7391
|
*/
|
|
7382
7392
|
type CommentDeliveryState = 'sent' | 'pending' | 'failed';
|
|
7383
7393
|
/**
|
|
@@ -7493,6 +7503,91 @@ interface ThreadComment {
|
|
|
7493
7503
|
* sees a word change.
|
|
7494
7504
|
*/
|
|
7495
7505
|
readonly moderated?: boolean | undefined;
|
|
7506
|
+
/**
|
|
7507
|
+
* Whether to DRAW the edit control on THIS comment.
|
|
7508
|
+
*
|
|
7509
|
+
* 0.2.0 forwarded `onEdit` and `onDelete` through to the thread and gated
|
|
7510
|
+
* both menu items on nothing but "a callback exists, and this is not a
|
|
7511
|
+
* tombstone". There was no authorship check anywhere in the render, so a
|
|
7512
|
+
* host that wired the two mutations drew them on every live comment,
|
|
7513
|
+
* including the ones the viewer did not write. Every server this was pointed
|
|
7514
|
+
* at refused correctly, which is exactly what makes it the wrong kind of
|
|
7515
|
+
* bug. It is not a hole; it is a LIE. A menu item that exists and can never
|
|
7516
|
+
* work tells somebody they may do a thing they may not, and it is worse than
|
|
7517
|
+
* the absent item, because the absent item at least agrees with the server.
|
|
7518
|
+
*
|
|
7519
|
+
* TWO flags rather than one `viewerIsAuthor`, and the argument for the
|
|
7520
|
+
* second one is already in this interface. `moderated` exists because a
|
|
7521
|
+
* comment can be removed by somebody OTHER than its author (FU-0031,
|
|
7522
|
+
* FU-0032) — so a surface that can produce that tombstone has a delete
|
|
7523
|
+
* predicate that is NOT authorship, while its edit stays author-only. This
|
|
7524
|
+
* module cannot say what any host's predicates ARE, and does not try to; it
|
|
7525
|
+
* only has to be able to spell two that differ, and `moderated` is already
|
|
7526
|
+
* proof that they do. One consumer's arrangement is the existence proof
|
|
7527
|
+
* rather than the rule: a moderated delete on a route of its own behind an
|
|
7528
|
+
* admin rank, and the author-gated edit deliberately left alone, because the
|
|
7529
|
+
* rank that removes somebody's words is not the rank that rewrites them.
|
|
7530
|
+
* A single `viewerIsAuthor` cannot express that pair, and the day it has to,
|
|
7531
|
+
* the fix is a breaking rename rather than a field.
|
|
7532
|
+
*
|
|
7533
|
+
* (Naming a consumer here is a narrower act than the one the TOMBSTONE
|
|
7534
|
+
* docblock in `comment.tsx` rules out. That one would have told every host
|
|
7535
|
+
* how to DERIVE a value — a schema stated as this module's fact. This one
|
|
7536
|
+
* cites an arrangement to show a shape is reachable, and constrains nobody:
|
|
7537
|
+
* a host whose two predicates are identical sets both flags the same way and
|
|
7538
|
+
* never notices the seam has two halves.)
|
|
7539
|
+
*
|
|
7540
|
+
* A per-comment field rather than a predicate in the render context, because
|
|
7541
|
+
* `ThreadLinkPreview.canHide` is already that shape and a second shape for
|
|
7542
|
+
* the same idea is a second thing to learn. It also means nothing between
|
|
7543
|
+
* here and the control needs new plumbing: `<Thread>` and `<ActivityPane>`
|
|
7544
|
+
* forward `comments` verbatim, so the capability arrives with the row it
|
|
7545
|
+
* describes.
|
|
7546
|
+
*
|
|
7547
|
+
* The SHAPE is `canHide`'s; the DEFAULT is its opposite, and that is worth
|
|
7548
|
+
* saying out loud before somebody reasons from the precedent and guesses
|
|
7549
|
+
* backwards. `canHide` is REQUIRED and therefore fails closed — a card whose
|
|
7550
|
+
* host says nothing draws no dismiss control. These two are OPTIONAL and
|
|
7551
|
+
* fail open. The difference is not principle, it is history: link previews
|
|
7552
|
+
* were new when `canHide` landed, so requiring it cost no host anything,
|
|
7553
|
+
* whereas edit and delete already render for everyone and a required field
|
|
7554
|
+
* would be a breaking change that silently removes controls until it is
|
|
7555
|
+
* supplied. "ABSENT MEANS TRUE" below is the whole of that argument.
|
|
7556
|
+
*
|
|
7557
|
+
* Deriving it here from `author.id` against a viewer id was the other
|
|
7558
|
+
* candidate and is rejected twice over: a derived rule recomputed on the
|
|
7559
|
+
* client is a second copy waiting to disagree with the server's, and
|
|
7560
|
+
* `author.id` is nullable by D5 — `ON DELETE SET NULL` would quietly turn
|
|
7561
|
+
* "the author is gone" into "you wrote this".
|
|
7562
|
+
*
|
|
7563
|
+
* ABSENT MEANS TRUE, and that is the uncomfortable half of this. A host on
|
|
7564
|
+
* 0.2.0 that wired `onEdit` and upgrades without touching its data keeps
|
|
7565
|
+
* precisely the render it has today, defect included. The alternative —
|
|
7566
|
+
* absent means false — fixes that host by silently taking the edit control
|
|
7567
|
+
* off the viewer's OWN comments until it maps two new fields: a release in
|
|
7568
|
+
* which nobody can edit anything, nothing errors, no test in the host fails,
|
|
7569
|
+
* and it survives to production for the same reason FU-0019 did. Trading a
|
|
7570
|
+
* known lie for a new silent regression is not a trade, so the default
|
|
7571
|
+
* preserves the render and this docblock is the notice. The seam is one
|
|
7572
|
+
* field wide; a host that means it should set it.
|
|
7573
|
+
*
|
|
7574
|
+
* RENDERING ONLY, and never an authorization decision — the same words
|
|
7575
|
+
* `canHide` is written in, for the same reason. The server re-checks
|
|
7576
|
+
* authorship on every edit and rank on every moderated delete; a client that
|
|
7577
|
+
* flips this to `true` earns a 404, not an edit.
|
|
7578
|
+
*/
|
|
7579
|
+
readonly canEdit?: boolean | undefined;
|
|
7580
|
+
/**
|
|
7581
|
+
* Whether to DRAW the delete control on THIS comment. Everything above
|
|
7582
|
+
* applies, including that absent means true and that this decides RENDERING
|
|
7583
|
+
* only — the server remains the gate and refuses on its own authority.
|
|
7584
|
+
*
|
|
7585
|
+
* It is separate from `canEdit` so the two predicates can differ, which on a
|
|
7586
|
+
* surface with moderation they already do: an admin may remove a comment
|
|
7587
|
+
* they may not rewrite, so `canDelete: true` with `canEdit: false` is the
|
|
7588
|
+
* shape that describes them and there is no way to spell it with one flag.
|
|
7589
|
+
*/
|
|
7590
|
+
readonly canDelete?: boolean | undefined;
|
|
7496
7591
|
readonly reactions?: readonly ThreadReactionCount[] | undefined;
|
|
7497
7592
|
readonly attachments?: readonly ThreadAttachment[] | undefined;
|
|
7498
7593
|
/**
|
|
@@ -7604,6 +7699,16 @@ interface ActivityPaneProps {
|
|
|
7604
7699
|
* by opening the surface rather than by reading the source, and the source
|
|
7605
7700
|
* understates it.
|
|
7606
7701
|
*
|
|
7702
|
+
* WIRING `onEdit` OR `onDelete` IS HALF THE JOB. The callback says the host
|
|
7703
|
+
* has the mutation; `canEdit` / `canDelete` on each `ThreadComment` say
|
|
7704
|
+
* whether this viewer may use it on THAT comment, and the pane forwards
|
|
7705
|
+
* `comments` verbatim, so they arrive with no plumbing here. Set them, or
|
|
7706
|
+
* every live comment in the feed gets an Edit the server will 404 — which is
|
|
7707
|
+
* what forwarding these two without them shipped as, and the same shape as
|
|
7708
|
+
* the `onHideLinkPreview` note below. Absent means the item is drawn, so
|
|
7709
|
+
* nothing this pane renders today disappears on upgrade; that is a
|
|
7710
|
+
* deliberate default and `ThreadComment` argues it.
|
|
7711
|
+
*
|
|
7607
7712
|
* `onRetry` is the same omission with one sharper edge: the failed send it
|
|
7608
7713
|
* recovers was posted by THIS pane's own composer, so dropping it left the
|
|
7609
7714
|
* pane manufacturing a state and offering nobody a way out of it.
|
|
@@ -8381,6 +8486,14 @@ interface ThreadRenderContext extends RichComposerAttachmentProps {
|
|
|
8381
8486
|
*/
|
|
8382
8487
|
readonly onCopyLink?: ((commentId: string) => void) | undefined;
|
|
8383
8488
|
readonly onCopyText?: ((commentId: string, ok: boolean) => void) | undefined;
|
|
8489
|
+
/**
|
|
8490
|
+
* Edit and delete are gated TWICE, and the second gate is the per-comment
|
|
8491
|
+
* one: the callback here says the host wired the mutation, and `canEdit` /
|
|
8492
|
+
* `canDelete` on the row say whether this viewer may use it on THAT comment
|
|
8493
|
+
* (see `ThreadComment`). Wiring the callback alone draws the item on every
|
|
8494
|
+
* live comment, which is what 0.2.0 did and what the server then refused.
|
|
8495
|
+
* Absent flags keep the item, so nothing a 0.2.0 host draws disappears.
|
|
8496
|
+
*/
|
|
8384
8497
|
readonly onEdit?: ((commentId: string) => void) | undefined;
|
|
8385
8498
|
readonly onDelete?: ((commentId: string) => void) | undefined;
|
|
8386
8499
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cn, composeRefs, useAnchoredPosition, useLayer, useOutsideClick, POPOVER_SURFACE_STYLE, __export, Input, Popover, handleTabTrap, Button, Kbd, usePrefersReducedMotion, QUIET_ICON_BOX, QUIET_ICON, RichThreadComposer, ThreadComposer, CommentBody, tabbablesWithin, AttachmentImage, AttachmentGif } from './chunk-
|
|
2
|
-
export { ATTACHMENT_ACCEPT, ATTACHMENT_LIMITS, AttachmentButton, AttachmentDropZone, AttachmentGif, AttachmentImage, AttachmentLightbox, AttachmentPlaceholder, AttachmentTray, Button, CodeBlock, CommentBody, GIF_SEARCH_DEBOUNCE_MS, GifPicker, Input, Kbd, MarkdownCode, MarkdownRenderer, PASTE_ATTACHMENT_THRESHOLD, POPOVER_SURFACE_STYLE, Popover, RichThreadComposer, ThreadComposer, applyMarkdownFormat, cn, computeAnchoredPosition, formatAttachmentSize, parseMentionHref, pastedTextFilename, shouldAttachPaste, useAnchoredPosition, useAttachmentUrl, useAttachments, useEscapeKey, useLayer, useOutsideClick, usePrefersReducedMotion } from './chunk-
|
|
1
|
+
import { cn, composeRefs, useAnchoredPosition, useLayer, useOutsideClick, POPOVER_SURFACE_STYLE, __export, Input, Popover, handleTabTrap, Button, Kbd, usePrefersReducedMotion, QUIET_ICON_BOX, QUIET_ICON, RichThreadComposer, ThreadComposer, CommentBody, tabbablesWithin, AttachmentImage, AttachmentGif } from './chunk-GP7BKVZC.js';
|
|
2
|
+
export { ATTACHMENT_ACCEPT, ATTACHMENT_LIMITS, AttachmentButton, AttachmentDropZone, AttachmentGif, AttachmentImage, AttachmentLightbox, AttachmentPlaceholder, AttachmentTray, Button, CodeBlock, CommentBody, GIF_SEARCH_DEBOUNCE_MS, GifPicker, Input, Kbd, MarkdownCode, MarkdownRenderer, PASTE_ATTACHMENT_THRESHOLD, POPOVER_SURFACE_STYLE, Popover, RichThreadComposer, ThreadComposer, applyMarkdownFormat, cn, computeAnchoredPosition, formatAttachmentSize, parseMentionHref, pastedTextFilename, shouldAttachPaste, useAnchoredPosition, useAttachmentUrl, useAttachments, useEscapeKey, useLayer, useOutsideClick, usePrefersReducedMotion } from './chunk-GP7BKVZC.js';
|
|
3
3
|
import React2, { forwardRef, Children, isValidElement, createContext, useContext, useId, useRef, useEffect, useMemo, useCallback, useState, useLayoutEffect, memo, useSyncExternalStore, cloneElement, Fragment as Fragment$1 } from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { Minus, Check, ChevronDown, X, Loader2, Play, VolumeX, Volume2, ChevronUp, Link2, ChevronLeft, MoreHorizontal, ThumbsUp as ThumbsUp$1, MessageCircle, Share2, Search, Plus, ChevronRight, CircleX, CircleCheck, CircleSlash, CircleDotDashed, Circle, CircleDashed, Pencil, House, GripVertical, Folder, Flag, ImageOff, ChevronsUpDown, SlidersHorizontal, Copy, FolderClosed, FolderPlus, Paperclip, Upload, Image, Code, Table as Table$1, FileText, FileType, AlertCircle, RotateCcw, ArrowRight, ListFilter, ArrowLeft, ArrowDown, ArrowUp, Info, ChartLine, Table2, Inbox, TriangleAlert, ChevronsLeft, ChevronsUp, ChevronsRight, ChevronsDown, Star, Settings2, CornerDownRight, Trash2, SmilePlus, ArrowBigUp, ArrowBigDown, ExternalLink, RotateCw, MessageSquare, Lock, PanelRight, SquareDashed, Maximize2, Archive, Printer, Repeat2, CornerUpRight } from 'lucide-react';
|
|
@@ -24429,7 +24429,8 @@ var CHIP_BASE = cn(
|
|
|
24429
24429
|
"inline-flex items-center justify-center gap-[var(--space-1)] border px-[var(--space-2)] text-[12px] font-semibold tabular-nums",
|
|
24430
24430
|
"transition-[background-color,border-color,color,box-shadow,transform] duration-[var(--dur-fast)] ease-[var(--ease-out)]",
|
|
24431
24431
|
"motion-reduce:transition-none",
|
|
24432
|
-
|
|
24432
|
+
// INSET: chips sit inside clipping ancestors on both hosts.
|
|
24433
|
+
"focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]",
|
|
24433
24434
|
"active:scale-[0.96] motion-reduce:active:scale-100",
|
|
24434
24435
|
"disabled:cursor-not-allowed disabled:opacity-60"
|
|
24435
24436
|
);
|
|
@@ -25294,6 +25295,8 @@ function CommentItem({
|
|
|
25294
25295
|
const author = node.author ?? null;
|
|
25295
25296
|
const tint = asAvatarTint(author?.tint);
|
|
25296
25297
|
const canEngage = !deleted && state === "sent";
|
|
25298
|
+
const canEdit = ctx.onEdit !== void 0 && !deleted && node.canEdit !== false;
|
|
25299
|
+
const canDelete = ctx.onDelete !== void 0 && !deleted && node.canDelete !== false;
|
|
25297
25300
|
const indentClampDepth = ctx.indentClampDepth ?? DEFAULT_INDENT_CLAMP_DEPTH;
|
|
25298
25301
|
const connected = renderDepth > 0;
|
|
25299
25302
|
const clamped = renderDepth > indentClampDepth;
|
|
@@ -25428,12 +25431,12 @@ function CommentItem({
|
|
|
25428
25431
|
ctx.onCopyLink?.(node.id);
|
|
25429
25432
|
}
|
|
25430
25433
|
},
|
|
25431
|
-
|
|
25434
|
+
...!canEdit ? {} : {
|
|
25432
25435
|
onEdit: () => {
|
|
25433
25436
|
ctx.onEdit?.(node.id);
|
|
25434
25437
|
}
|
|
25435
25438
|
},
|
|
25436
|
-
|
|
25439
|
+
...!canDelete ? {} : {
|
|
25437
25440
|
onDelete: () => {
|
|
25438
25441
|
ctx.onDelete?.(node.id);
|
|
25439
25442
|
}
|