@particle-academy/react-fancy 5.33.0 → 5.35.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/CHANGELOG.md +167 -0
- package/dist/activity-light.cjs +13 -0
- package/dist/activity-light.cjs.map +1 -0
- package/dist/activity-light.d.cts +64 -0
- package/dist/activity-light.d.ts +64 -0
- package/dist/activity-light.js +4 -0
- package/dist/activity-light.js.map +1 -0
- package/dist/button.cjs +4 -4
- package/dist/button.js +2 -2
- package/dist/{chunk-ZYVWKYPO.js → chunk-2OMB3OOR.js} +3 -3
- package/dist/{chunk-ZYVWKYPO.js.map → chunk-2OMB3OOR.js.map} +1 -1
- package/dist/{chunk-G6VMNSCM.js → chunk-46IPQEUP.js} +3 -3
- package/dist/{chunk-G6VMNSCM.js.map → chunk-46IPQEUP.js.map} +1 -1
- package/dist/{chunk-H6PXIB35.cjs → chunk-5Q7EQQTT.cjs} +4 -4
- package/dist/{chunk-H6PXIB35.cjs.map → chunk-5Q7EQQTT.cjs.map} +1 -1
- package/dist/{chunk-BQ77ZWFM.js → chunk-6ASZUE4T.js} +4 -4
- package/dist/{chunk-BQ77ZWFM.js.map → chunk-6ASZUE4T.js.map} +1 -1
- package/dist/{chunk-WEKQD4BQ.js → chunk-DCDW3LW6.js} +3 -3
- package/dist/{chunk-WEKQD4BQ.js.map → chunk-DCDW3LW6.js.map} +1 -1
- package/dist/{chunk-VRJTPXJ7.cjs → chunk-DNV7MUEE.cjs} +81 -20
- package/dist/chunk-DNV7MUEE.cjs.map +1 -0
- package/dist/{chunk-YMU3SS6D.cjs → chunk-E2SZBIAV.cjs} +16 -16
- package/dist/{chunk-YMU3SS6D.cjs.map → chunk-E2SZBIAV.cjs.map} +1 -1
- package/dist/chunk-EHNMKQ4O.cjs +71 -0
- package/dist/chunk-EHNMKQ4O.cjs.map +1 -0
- package/dist/chunk-IR6JSQ4O.cjs +233 -0
- package/dist/chunk-IR6JSQ4O.cjs.map +1 -0
- package/dist/{chunk-5TUZENNJ.cjs → chunk-MTUH2X3W.cjs} +4 -4
- package/dist/{chunk-5TUZENNJ.cjs.map → chunk-MTUH2X3W.cjs.map} +1 -1
- package/dist/{chunk-B4PFNSX7.js → chunk-O3WEV562.js} +79 -21
- package/dist/chunk-O3WEV562.js.map +1 -0
- package/dist/chunk-PZTA6NCD.js +69 -0
- package/dist/chunk-PZTA6NCD.js.map +1 -0
- package/dist/{chunk-UWL6U3L7.cjs → chunk-RMJGF7MN.cjs} +3 -3
- package/dist/{chunk-UWL6U3L7.cjs.map → chunk-RMJGF7MN.cjs.map} +1 -1
- package/dist/{chunk-EIUS2K72.js → chunk-TX6VUYBC.js} +3 -3
- package/dist/{chunk-EIUS2K72.js.map → chunk-TX6VUYBC.js.map} +1 -1
- package/dist/{chunk-LQFQ2UKU.cjs → chunk-WWUKTXPX.cjs} +3 -3
- package/dist/{chunk-LQFQ2UKU.cjs.map → chunk-WWUKTXPX.cjs.map} +1 -1
- package/dist/chunk-Z4CW3U6C.js +231 -0
- package/dist/chunk-Z4CW3U6C.js.map +1 -0
- package/dist/editor.cjs +4 -4
- package/dist/editor.js +2 -2
- package/dist/index-DXUAAZzE.d.cts +157 -0
- package/dist/index-DXUAAZzE.d.ts +157 -0
- package/dist/index.cjs +160 -138
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +27 -25
- package/dist/inputs.cjs +9 -9
- package/dist/inputs.js +3 -3
- package/dist/json-editor.cjs +15 -15
- package/dist/json-editor.js +4 -4
- package/dist/magic-wand.cjs +4 -4
- package/dist/magic-wand.js +3 -3
- package/dist/prompt-input.cjs +5 -4
- package/dist/prompt-input.d.cts +2 -77
- package/dist/prompt-input.d.ts +2 -77
- package/dist/prompt-input.js +4 -3
- package/dist/reason-tag.cjs +4 -4
- package/dist/reason-tag.js +3 -3
- package/dist/virtual-list.cjs +13 -0
- package/dist/virtual-list.cjs.map +1 -0
- package/dist/virtual-list.d.cts +125 -0
- package/dist/virtual-list.d.ts +125 -0
- package/dist/virtual-list.js +4 -0
- package/dist/virtual-list.js.map +1 -0
- package/package.json +23 -2
- package/dist/chunk-B4PFNSX7.js.map +0 -1
- package/dist/chunk-VRJTPXJ7.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,173 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.35.0] - 2026-10-10
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`PromptInput` told every Windows and Linux user to press a key they do not
|
|
19
|
+
have.** The keyboard hint was a hardcoded `⌘ + Enter to send`, and so was the
|
|
20
|
+
default placeholder. **Nothing to do** — the hint now names the modifier the
|
|
21
|
+
platform actually has, and the keys it accepts are unchanged (`⌘/Ctrl+Enter`
|
|
22
|
+
submits everywhere, as it always did).
|
|
23
|
+
|
|
24
|
+
It is worth more than one wrong glyph. A reader who cannot act on a hint
|
|
25
|
+
learns to ignore hints, which costs the credibility of every hint beside it.
|
|
26
|
+
In the lean Linux container where it was found, the glyph had no font behind
|
|
27
|
+
it either and rendered as a tofu box.
|
|
28
|
+
|
|
29
|
+
- New `platform?: "apple" | "generic"` prop. Omit it and the component reads
|
|
30
|
+
the platform after mount; pass it when the host knows better than
|
|
31
|
+
`navigator` — an Electron renderer forwarding `process.platform`, a remote
|
|
32
|
+
session where the keyboard is not the one running the browser, or a test
|
|
33
|
+
that needs the branch it is not sitting on.
|
|
34
|
+
- The platform is resolved AFTER mount, never during render, so this does not
|
|
35
|
+
introduce a hydration mismatch under SSR.
|
|
36
|
+
- `generic` is the fallback, and not arbitrarily: submit accepts
|
|
37
|
+
`metaKey || ctrlKey`, so `Ctrl` is a TRUE label on an Apple keyboard while
|
|
38
|
+
`⌘` is a false one everywhere else. The honest default is the pressable one.
|
|
39
|
+
- `detectPlatform(hints)` and `modifierKeyLabel(platform)` are exported for
|
|
40
|
+
any surface with a keyboard hint of its own. **Both take the platform as an
|
|
41
|
+
ARGUMENT** rather than reading a global, which is the only way a test can
|
|
42
|
+
cover the platform the developer is not sitting at.
|
|
43
|
+
|
|
44
|
+
- **`PromptInput`'s attach control rendered as a tofu box in a lean container.**
|
|
45
|
+
Its icon was a literal paperclip emoji, and an emoji needs a dedicated COLOUR
|
|
46
|
+
EMOJI FONT — not merely a font with broad coverage — which a container image
|
|
47
|
+
has no reason to carry. **Nothing to do**: it now draws `icon="paperclip"`
|
|
48
|
+
from the kit's own icon layer, as the rest of the suite does. The attachment
|
|
49
|
+
chips had the same emoji and are fixed with it.
|
|
50
|
+
|
|
51
|
+
The `send` button's trailing `->` arrow became `iconTrailing="arrow-right"` in
|
|
52
|
+
the same pass — same class of risk, same primitive already in the kit.
|
|
53
|
+
|
|
54
|
+
- **`PromptInput` dropped any `data-*` or `aria-*` handle put on it.** It named
|
|
55
|
+
every prop it used and had no rest spread, so a consumer's handle vanished
|
|
56
|
+
silently — the component contract's "agents never guess DOM" broken in the
|
|
57
|
+
one component an agent is most likely to be asked to drive. This is issue #22
|
|
58
|
+
recurring in a component that was not in that sweep. **Nothing to do**;
|
|
59
|
+
`className` now merges rather than replacing the surface's own classes, and
|
|
60
|
+
the root carries `data-react-fancy-prompt-input` whether or not a consumer
|
|
61
|
+
adds a handle.
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- `data-react-fancy-prompt-input`, `-drag` (`over` / `idle`) and `-hint`
|
|
66
|
+
(`apple` / `generic`) on `PromptInput`, so a bridge reads the composer's drop
|
|
67
|
+
and platform state off the DOM instead of inferring it from Tailwind colours.
|
|
68
|
+
|
|
69
|
+
### Notes
|
|
70
|
+
|
|
71
|
+
- **A suite cannot see a font, which is why this needed a consumer to find it.**
|
|
72
|
+
Both glyph bugs passed every test here and would have passed forever: jsdom
|
|
73
|
+
has no fonts, so an emoji asserted in a DOM string is indistinguishable from
|
|
74
|
+
one a user can see. It took a screenshot of a real Electron window on Linux.
|
|
75
|
+
The new guard therefore does not test rendering — it tests the SOURCE for the
|
|
76
|
+
property that predicts the failure, that no component contains a character
|
|
77
|
+
above the Basic Multilingual Plane (the emoji dataset excepted). That line is
|
|
78
|
+
where "needs a font with good coverage" becomes "needs a second font dedicated
|
|
79
|
+
to emoji". BMP symbols are deliberately not covered: smaller risk, one
|
|
80
|
+
judgement call each, and a rule with false positives gets deleted.
|
|
81
|
+
- Measured while here, because the same report asked: **no Fancy package
|
|
82
|
+
declares a `font-family` at all**, so none of our defaults can be hijacked by
|
|
83
|
+
a generic family resolving to whatever a machine's fontconfig puts first. The
|
|
84
|
+
showcase and the starter kit both lead their stack with a named face.
|
|
85
|
+
- **This release shipped without a `lab:run`** — same dead Anthropic key as
|
|
86
|
+
5.34.0 (401 since before the previous release wave, `fancy-labs` #5). It went
|
|
87
|
+
out on a green package suite (498 tests) plus sabotage verification instead.
|
|
88
|
+
- Each fix was verified by breaking it on purpose: hardcoding the glyph back
|
|
89
|
+
fails 2 tests, defaulting an unknown platform to Apple fails 5, letting free
|
|
90
|
+
text overturn an enumerated platform fails 1, putting an emoji into a
|
|
91
|
+
different component fails the source guard, restoring the emoji icon fails 5,
|
|
92
|
+
dropping the rest spread fails 1, and letting `className` replace instead of
|
|
93
|
+
merge fails 1.
|
|
94
|
+
|
|
95
|
+
## [5.34.0] - 2026-10-09
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **`VirtualList` — a windowed list for ledgers of tens of thousands of rows**,
|
|
100
|
+
with measured variable row heights, deep links by row id, and sticky-bottom
|
|
101
|
+
tail-following. **Nothing to do**: it is a new component, and no existing
|
|
102
|
+
export changed.
|
|
103
|
+
|
|
104
|
+
Built for an agent event stream, where all three matter at once — message
|
|
105
|
+
rows wrap while event rows do not, a link carries `?at=<rowId>` to the exact
|
|
106
|
+
row under discussion, and new rows keep arriving while the human reads older
|
|
107
|
+
ones. `fancy-term`'s scrollback, `CommitHistory` and long `Table` bodies want
|
|
108
|
+
the same thing.
|
|
109
|
+
|
|
110
|
+
Three decisions worth knowing before you reach for it:
|
|
111
|
+
|
|
112
|
+
- **Heights are measured, not predicted.** `estimateRowHeight` only seeds rows
|
|
113
|
+
that have never been on screen; a row's real height replaces it the moment
|
|
114
|
+
it renders. There is deliberately no `rowHeight(item)` callback — that is a
|
|
115
|
+
prediction about text wrapping, font loading and container width, and when
|
|
116
|
+
it is wrong the error accumulates down the list until rows draw in the wrong
|
|
117
|
+
place and `scrollToId` lands on a neighbour.
|
|
118
|
+
- **`followTail` is controlled** (`followTail` + `onFollowChange`), so the
|
|
119
|
+
follow state is readable and writable by an agent over a bridge, as the
|
|
120
|
+
Human+ contract requires. The `↓ n new` pill is yours to render, from
|
|
121
|
+
`onUnseenChange`.
|
|
122
|
+
- **Tail-follow breaks on INTENT, not on scroll position.** Any upward wheel,
|
|
123
|
+
drag, `PageUp` or `Home` stops following at once — even while still pinned to
|
|
124
|
+
the bottom with `scrollTop` unchanged. Position cannot distinguish "the human
|
|
125
|
+
scrolled up" from "content grew downward", so a position-only implementation
|
|
126
|
+
follows through a wheel-up and yanks the reader back on the next append.
|
|
127
|
+
|
|
128
|
+
`scrollToId` resolves asynchronously when the target is far down an unmeasured
|
|
129
|
+
list, and `onScrollToIdResolved(id, landed)` reports when it has settled.
|
|
130
|
+
`landed` is `false` when the id is not in `items` — a deep link to a pruned row
|
|
131
|
+
must be distinguishable from a successful scroll to the top.
|
|
132
|
+
|
|
133
|
+
- **`ActivityLight` — an activity indicator for an EDGE**, a relationship
|
|
134
|
+
between two parties, with a recency ramp (`live` / `recent` / `quiet` /
|
|
135
|
+
`unseen`), an optional direction and an optional count. **Nothing to do.**
|
|
136
|
+
|
|
137
|
+
It is not a presence dot and does not replace `Avatar status`: that belongs to
|
|
138
|
+
one party and says whether *they* are online, while this belongs to a pair and
|
|
139
|
+
says when *the link between them* last moved.
|
|
140
|
+
|
|
141
|
+
**It distinguishes "none" from "cannot see", and that is the point.**
|
|
142
|
+
`level: null` means the edge is real but undatable and renders **nothing** — no
|
|
143
|
+
dot, no dash, no placeholder, because a grey dot asserts "quiet", which is a
|
|
144
|
+
claim that cannot be made. `count: null` or omitted renders no number. But a
|
|
145
|
+
measured **`count: 0` does render**, because "we looked and it was none" is a
|
|
146
|
+
fact, and suppressing it would be indistinguishable from never having looked.
|
|
147
|
+
A zero that should have been "unknown" tells a reader an agent works alone, and
|
|
148
|
+
they believe it.
|
|
149
|
+
|
|
150
|
+
### Changed
|
|
151
|
+
|
|
152
|
+
- Both new components are reachable by subpath — `@particle-academy/react-fancy/virtual-list`
|
|
153
|
+
and `/activity-light` — alongside the barrel, like every other component.
|
|
154
|
+
|
|
155
|
+
### Security
|
|
156
|
+
|
|
157
|
+
- `source-map-js` is pinned forward to `^1.2.2` via `overrides`. Versions up to
|
|
158
|
+
1.2.1 allow an event-loop denial of service through indexed source-map section
|
|
159
|
+
offsets, and it arrives here transitively through the build toolchain.
|
|
160
|
+
**Nothing for a consumer to do, and no runtime change**: an npm package does
|
|
161
|
+
not ship a lockfile, so this governs builds OF this repo, not anything
|
|
162
|
+
installed FROM it. Recorded rather than left silent because the override it
|
|
163
|
+
sits beside — `shell-quote` `^1.9.0`, added for an earlier advisory — was
|
|
164
|
+
carried with no note of why, and had drifted back inside the vulnerable range
|
|
165
|
+
before anyone looked.
|
|
166
|
+
|
|
167
|
+
### Notes
|
|
168
|
+
|
|
169
|
+
- **This release shipped without a `lab:run`.** The estate's rule is to run the
|
|
170
|
+
lab before any release; the Anthropic key behind it has been returning 401
|
|
171
|
+
since before the previous release wave (tracked as `fancy-labs` #5), so it has
|
|
172
|
+
judged nothing. This went out on a green package suite (469 tests) plus
|
|
173
|
+
sabotage verification of the new suites instead. Stated here because a reader
|
|
174
|
+
should not have to find it out.
|
|
175
|
+
- The new suites were checked by breaking the implementation on purpose, since a
|
|
176
|
+
new test that passes either way proves nothing. Removing height measurement
|
|
177
|
+
fails 4 of the `VirtualList` tests; making tail-follow watch position instead
|
|
178
|
+
of intent fails 2; gating the count on `count` rather than "is it counted" —
|
|
179
|
+
the classic falsy-zero bug — fails both `ActivityLight` zero cases.
|
|
180
|
+
|
|
14
181
|
## [5.33.0] - 2026-10-04
|
|
15
182
|
|
|
16
183
|
### Fixed
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkEHNMKQ4O_cjs = require('./chunk-EHNMKQ4O.cjs');
|
|
4
|
+
require('./chunk-ZU5Q77OT.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Object.defineProperty(exports, "ActivityLight", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function () { return chunkEHNMKQ4O_cjs.ActivityLight; }
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=activity-light.cjs.map
|
|
13
|
+
//# sourceMappingURL=activity-light.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"activity-light.cjs"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* How recently an edge carried traffic.
|
|
6
|
+
*
|
|
7
|
+
* `null` is not a level — it means the edge is real but **undatable**, and it
|
|
8
|
+
* renders nothing at all. A grey dot would be the claim "this edge is quiet",
|
|
9
|
+
* which is a different statement from "we cannot see when it last moved".
|
|
10
|
+
*/
|
|
11
|
+
type ActivityLevel = "live" | "recent" | "quiet" | "unseen";
|
|
12
|
+
/** Which way the traffic went. Absent means undirected, not "both". */
|
|
13
|
+
type ActivityDirection = "in" | "out" | "both";
|
|
14
|
+
interface ActivityLightProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
15
|
+
/**
|
|
16
|
+
* The recency of this edge, or `null` for a real edge whose recency cannot be
|
|
17
|
+
* observed — which renders NOTHING.
|
|
18
|
+
*/
|
|
19
|
+
level: ActivityLevel | null;
|
|
20
|
+
/** Which way the traffic went. Omit when it is not known or not meaningful. */
|
|
21
|
+
direction?: ActivityDirection;
|
|
22
|
+
/**
|
|
23
|
+
* How much traffic. `null` (or omitted) means **not counted** and renders no
|
|
24
|
+
* number — never a `0`, because a zero claims the other party worked alone.
|
|
25
|
+
* A measured `0` IS rendered: "we looked and it was none" is a fact.
|
|
26
|
+
*/
|
|
27
|
+
count?: number | null;
|
|
28
|
+
/** Accessible name for the edge — "Genie and Tynn". Required. */
|
|
29
|
+
label: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Activity on an **edge** — a relationship between two parties — with a recency
|
|
34
|
+
* ramp and an optional volume.
|
|
35
|
+
*
|
|
36
|
+
* Not a presence dot. `Avatar status` belongs to one party and says whether
|
|
37
|
+
* *they* are online; this belongs to a pair and says when *the link between
|
|
38
|
+
* them* last moved. Use it in a comms column, a collaborator lane, anywhere the
|
|
39
|
+
* subject is the relationship rather than the participant.
|
|
40
|
+
*
|
|
41
|
+
* ## Two kinds of nothing, and they must not look alike
|
|
42
|
+
*
|
|
43
|
+
* - **`level: null`** — the edge is real, its recency is not observable. Renders
|
|
44
|
+
* NOTHING: no dot, no dash, no placeholder. A grey dot would assert "quiet",
|
|
45
|
+
* which is a claim we cannot make.
|
|
46
|
+
* - **`count: null`** or omitted — the edge's recency is known but its volume is
|
|
47
|
+
* not. Renders the light with no number.
|
|
48
|
+
* - **`count: 0`** — measured, and none. Renders `0`, because that is a fact.
|
|
49
|
+
* Suppressing it would be indistinguishable from not having looked.
|
|
50
|
+
*
|
|
51
|
+
* The distinction reads as pedantic until a count is wrong in the direction
|
|
52
|
+
* that flatters: a `0` where the truth is "unknown" tells the reader an agent
|
|
53
|
+
* works alone, and they believe it.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* <ActivityLight level="live" direction="both" count={12} label="Genie and Tynn" />
|
|
58
|
+
* <ActivityLight level="quiet" count={0} label="Genie and Weaver" />
|
|
59
|
+
* <ActivityLight level={null} label="Genie and an unknown peer" />
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare const ActivityLight: react.ForwardRefExoticComponent<ActivityLightProps & react.RefAttributes<HTMLSpanElement>>;
|
|
63
|
+
|
|
64
|
+
export { type ActivityDirection, type ActivityLevel, ActivityLight, type ActivityLightProps };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* How recently an edge carried traffic.
|
|
6
|
+
*
|
|
7
|
+
* `null` is not a level — it means the edge is real but **undatable**, and it
|
|
8
|
+
* renders nothing at all. A grey dot would be the claim "this edge is quiet",
|
|
9
|
+
* which is a different statement from "we cannot see when it last moved".
|
|
10
|
+
*/
|
|
11
|
+
type ActivityLevel = "live" | "recent" | "quiet" | "unseen";
|
|
12
|
+
/** Which way the traffic went. Absent means undirected, not "both". */
|
|
13
|
+
type ActivityDirection = "in" | "out" | "both";
|
|
14
|
+
interface ActivityLightProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
15
|
+
/**
|
|
16
|
+
* The recency of this edge, or `null` for a real edge whose recency cannot be
|
|
17
|
+
* observed — which renders NOTHING.
|
|
18
|
+
*/
|
|
19
|
+
level: ActivityLevel | null;
|
|
20
|
+
/** Which way the traffic went. Omit when it is not known or not meaningful. */
|
|
21
|
+
direction?: ActivityDirection;
|
|
22
|
+
/**
|
|
23
|
+
* How much traffic. `null` (or omitted) means **not counted** and renders no
|
|
24
|
+
* number — never a `0`, because a zero claims the other party worked alone.
|
|
25
|
+
* A measured `0` IS rendered: "we looked and it was none" is a fact.
|
|
26
|
+
*/
|
|
27
|
+
count?: number | null;
|
|
28
|
+
/** Accessible name for the edge — "Genie and Tynn". Required. */
|
|
29
|
+
label: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Activity on an **edge** — a relationship between two parties — with a recency
|
|
34
|
+
* ramp and an optional volume.
|
|
35
|
+
*
|
|
36
|
+
* Not a presence dot. `Avatar status` belongs to one party and says whether
|
|
37
|
+
* *they* are online; this belongs to a pair and says when *the link between
|
|
38
|
+
* them* last moved. Use it in a comms column, a collaborator lane, anywhere the
|
|
39
|
+
* subject is the relationship rather than the participant.
|
|
40
|
+
*
|
|
41
|
+
* ## Two kinds of nothing, and they must not look alike
|
|
42
|
+
*
|
|
43
|
+
* - **`level: null`** — the edge is real, its recency is not observable. Renders
|
|
44
|
+
* NOTHING: no dot, no dash, no placeholder. A grey dot would assert "quiet",
|
|
45
|
+
* which is a claim we cannot make.
|
|
46
|
+
* - **`count: null`** or omitted — the edge's recency is known but its volume is
|
|
47
|
+
* not. Renders the light with no number.
|
|
48
|
+
* - **`count: 0`** — measured, and none. Renders `0`, because that is a fact.
|
|
49
|
+
* Suppressing it would be indistinguishable from not having looked.
|
|
50
|
+
*
|
|
51
|
+
* The distinction reads as pedantic until a count is wrong in the direction
|
|
52
|
+
* that flatters: a `0` where the truth is "unknown" tells the reader an agent
|
|
53
|
+
* works alone, and they believe it.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* <ActivityLight level="live" direction="both" count={12} label="Genie and Tynn" />
|
|
58
|
+
* <ActivityLight level="quiet" count={0} label="Genie and Weaver" />
|
|
59
|
+
* <ActivityLight level={null} label="Genie and an unknown peer" />
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare const ActivityLight: react.ForwardRefExoticComponent<ActivityLightProps & react.RefAttributes<HTMLSpanElement>>;
|
|
63
|
+
|
|
64
|
+
export { type ActivityDirection, type ActivityLevel, ActivityLight, type ActivityLightProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"activity-light.js"}
|
package/dist/button.cjs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
require('./chunk-M5MUGSON.cjs');
|
|
4
|
-
var
|
|
4
|
+
var chunkRMJGF7MN_cjs = require('./chunk-RMJGF7MN.cjs');
|
|
5
|
+
require('./chunk-BFKCVSNK.cjs');
|
|
5
6
|
require('./chunk-XCZZVRBY.cjs');
|
|
6
7
|
require('./chunk-QKFOJKVE.cjs');
|
|
7
|
-
require('./chunk-BFKCVSNK.cjs');
|
|
8
8
|
require('./chunk-ZU5Q77OT.cjs');
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
Object.defineProperty(exports, "Action", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunkRMJGF7MN_cjs.Action; }
|
|
15
15
|
});
|
|
16
16
|
Object.defineProperty(exports, "Button", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunkRMJGF7MN_cjs.Button; }
|
|
19
19
|
});
|
|
20
20
|
//# sourceMappingURL=button.cjs.map
|
|
21
21
|
//# sourceMappingURL=button.cjs.map
|
package/dist/button.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import './chunk-LQ4ICCN2.js';
|
|
2
|
-
export { Action, Button } from './chunk-
|
|
2
|
+
export { Action, Button } from './chunk-46IPQEUP.js';
|
|
3
|
+
import './chunk-XJXQG34J.js';
|
|
3
4
|
import './chunk-45BBNJQD.js';
|
|
4
5
|
import './chunk-IVRYSSM7.js';
|
|
5
|
-
import './chunk-XJXQG34J.js';
|
|
6
6
|
import './chunk-MMW2JGKZ.js';
|
|
7
7
|
//# sourceMappingURL=button.js.map
|
|
8
8
|
//# sourceMappingURL=button.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Textarea } from './chunk-DEFGT75G.js';
|
|
2
2
|
import { Badge } from './chunk-6FCQD2KQ.js';
|
|
3
|
-
import { Button } from './chunk-
|
|
3
|
+
import { Button } from './chunk-46IPQEUP.js';
|
|
4
4
|
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
5
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -198,5 +198,5 @@ function caretRect(ta, start, end) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
export { MagicWand };
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
202
|
-
//# sourceMappingURL=chunk-
|
|
201
|
+
//# sourceMappingURL=chunk-2OMB3OOR.js.map
|
|
202
|
+
//# sourceMappingURL=chunk-2OMB3OOR.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/MagicWand/MagicWand.tsx"],"names":[],"mappings":"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","file":"chunk-ZYVWKYPO.js","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Button } from \"../Button\";\nimport { Badge } from \"../Badge\";\nimport { Textarea } from \"../inputs/Textarea/Textarea\";\n\n/**\n * MagicWand — selection-anchored floating toolbar for text inputs.\n *\n * When the user highlights text inside the wrapped <Textarea>, a small\n * pill of AI quick-actions floats above the selection. Clicking an\n * action invokes the host callback with the selected substring + range;\n * the host returns a replacement string and MagicWand swaps it in-place.\n *\n * <MagicWand\n * value={body}\n * onValueChange={setBody}\n * actions={[\n * { id: \"rephrase\", label: \"Rephrase\", hint: \"same meaning, different words\",\n * run: async (s) => await ai.rephrase(s) },\n * …\n * ]}\n * />\n *\n * Two appearances: `\"floating\"` (default — full labels) and `\"pill\"`\n * (icon-only, compact). Auto-hides on click-away or scroll by default.\n */\nexport type MagicWandSelection = { start: number; end: number; text: string };\n\nexport type MagicWandAction = {\n /** Stable id, also shown as the busy-state key. */\n id: string;\n /** Display label (first character used in `pill` mode). */\n label: string;\n /** Tooltip body. */\n hint?: string;\n /** Optional kind chip rendered next to the label. */\n tag?: string;\n /** Returns the replacement text for the selection. */\n run: (selection: string, range: MagicWandSelection) => Promise<string> | string;\n};\n\nexport type MagicWandAppearance = \"pill\" | \"floating\";\n\nexport interface MagicWandProps {\n /** Controlled textarea value. */\n value: string;\n /** Called on every edit. */\n onValueChange: (v: string) => void;\n /** Action list. */\n actions: MagicWandAction[];\n /** Visual treatment. Defaults to \"floating\". */\n appearance?: MagicWandAppearance;\n /** Auto-hide the wand on click-away or scroll. Defaults to true. */\n autoHide?: boolean;\n /** Textarea rows. */\n rows?: number;\n /** Placeholder. */\n placeholder?: string;\n /** Called after an action runs successfully. */\n onAction?: (action: MagicWandAction, selection: MagicWandSelection, replacement: string) => void;\n}\n\nexport function MagicWand({\n value,\n onValueChange,\n actions,\n appearance = \"floating\",\n autoHide = true,\n rows = 6,\n placeholder,\n onAction,\n}: MagicWandProps) {\n const taRef = useRef<HTMLTextAreaElement>(null);\n const wandRef = useRef<HTMLDivElement>(null);\n const [sel, setSel] = useState<MagicWandSelection | null>(null);\n const [pos, setPos] = useState<{ x: number; y: number } | null>(null);\n const [busy, setBusy] = useState<string | null>(null);\n\n const measureSelection = useCallback(() => {\n const ta = taRef.current;\n if (!ta) return;\n const start = ta.selectionStart ?? 0;\n const end = ta.selectionEnd ?? 0;\n if (start === end) {\n setSel(null);\n setPos(null);\n return;\n }\n const text = ta.value.slice(start, end);\n setSel({ start, end, text });\n const rect = caretRect(ta, start, end);\n if (rect) setPos({ x: rect.x, y: rect.y });\n }, []);\n\n useEffect(() => {\n if (!autoHide) return;\n const onScroll = () => {\n setSel(null);\n setPos(null);\n };\n window.addEventListener(\"scroll\", onScroll, true);\n return () => window.removeEventListener(\"scroll\", onScroll, true);\n }, [autoHide]);\n\n useEffect(() => {\n if (!autoHide) return;\n const onDocMouseDown = (e: MouseEvent) => {\n if (wandRef.current?.contains(e.target as Node)) return;\n if (taRef.current?.contains(e.target as Node)) return;\n setSel(null);\n setPos(null);\n };\n document.addEventListener(\"mousedown\", onDocMouseDown);\n return () => document.removeEventListener(\"mousedown\", onDocMouseDown);\n }, [autoHide]);\n\n const handleAction = async (action: MagicWandAction) => {\n if (!sel) return;\n setBusy(action.id);\n try {\n const replacement = await action.run(sel.text, sel);\n const next = value.slice(0, sel.start) + replacement + value.slice(sel.end);\n onValueChange(next);\n onAction?.(action, sel, replacement);\n } finally {\n setBusy(null);\n setSel(null);\n setPos(null);\n }\n };\n\n return (\n <div className=\"relative\">\n <Textarea\n ref={taRef as unknown as React.Ref<HTMLTextAreaElement>}\n value={value}\n onValueChange={onValueChange}\n onSelect={measureSelection}\n onKeyUp={measureSelection}\n onMouseUp={measureSelection}\n rows={rows}\n placeholder={placeholder}\n />\n {sel && pos && (\n <Wand\n ref={wandRef}\n pos={pos}\n actions={actions}\n appearance={appearance}\n busy={busy}\n onAction={handleAction}\n selectionLength={sel.text.length}\n />\n )}\n </div>\n );\n}\n\nfunction Wand({\n ref,\n pos,\n actions,\n appearance,\n busy,\n onAction,\n selectionLength,\n}: {\n ref: React.RefObject<HTMLDivElement | null>;\n pos: { x: number; y: number };\n actions: MagicWandAction[];\n appearance: MagicWandAppearance;\n busy: string | null;\n onAction: (a: MagicWandAction) => void;\n selectionLength: number;\n}) {\n return (\n <div\n ref={ref}\n className=\"absolute z-20 -translate-x-1/2 -translate-y-full\"\n style={{ left: pos.x, top: pos.y - 6 }}\n >\n <div className=\"flex items-center gap-1 rounded-full border border-violet-300 bg-white px-1.5 py-1 shadow-lg ring-1 ring-violet-200 dark:border-violet-700 dark:bg-zinc-900 dark:ring-violet-900\">\n <span className=\"ml-1 mr-0.5 inline-flex items-center gap-1 text-[10px] font-medium text-violet-700 dark:text-violet-300\">\n <span aria-hidden>✦</span>\n {appearance === \"floating\" && (\n <Badge color=\"violet\">{selectionLength} chars</Badge>\n )}\n </span>\n {actions.map((a) => (\n <Button\n key={a.id}\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => onAction(a)}\n disabled={busy !== null}\n title={a.hint}\n >\n {busy === a.id ? \"…\" : appearance === \"pill\" ? a.label[0] : a.label}\n {appearance === \"floating\" && a.tag && (\n <span className=\"ml-1 rounded-full bg-zinc-100 px-1 text-[9px] font-medium text-zinc-500 dark:bg-zinc-800\">\n {a.tag}\n </span>\n )}\n </Button>\n ))}\n </div>\n <div className=\"absolute left-1/2 top-full -translate-x-1/2 -translate-y-px\">\n <div className=\"h-2 w-2 rotate-45 border-b border-r border-violet-300 bg-white dark:border-violet-700 dark:bg-zinc-900\" />\n </div>\n </div>\n );\n}\n\n/**\n * Caret/selection geometry: mirror the textarea into a hidden div and\n * measure the selected substring's bounding rect. Not perfect for\n * resized textareas, but works for typical chat composers.\n */\nfunction caretRect(\n ta: HTMLTextAreaElement,\n start: number,\n end: number,\n): { x: number; y: number } | null {\n const div = document.createElement(\"div\");\n const style = getComputedStyle(ta);\n const props = [\n \"boxSizing\",\n \"width\",\n \"height\",\n \"overflowX\",\n \"overflowY\",\n \"borderTopWidth\",\n \"borderRightWidth\",\n \"borderBottomWidth\",\n \"borderLeftWidth\",\n \"paddingTop\",\n \"paddingRight\",\n \"paddingBottom\",\n \"paddingLeft\",\n \"fontStyle\",\n \"fontVariant\",\n \"fontWeight\",\n \"fontStretch\",\n \"fontSize\",\n \"fontSizeAdjust\",\n \"lineHeight\",\n \"fontFamily\",\n \"textAlign\",\n \"textTransform\",\n \"textIndent\",\n \"letterSpacing\",\n \"wordSpacing\",\n \"tabSize\",\n ] as const;\n for (const p of props) (div.style as any)[p] = (style as any)[p];\n div.style.position = \"absolute\";\n div.style.top = \"-9999px\";\n div.style.left = \"-9999px\";\n div.style.whiteSpace = \"pre-wrap\";\n div.style.wordWrap = \"break-word\";\n\n const value = ta.value;\n div.textContent = value.substring(0, start);\n const startSpan = document.createElement(\"span\");\n startSpan.textContent = value.substring(start, end) || \".\";\n div.appendChild(startSpan);\n\n document.body.appendChild(div);\n const taRect = ta.getBoundingClientRect();\n const parentRect =\n (ta.offsetParent as HTMLElement | null)?.getBoundingClientRect() ?? taRect;\n const spanRect = startSpan.getBoundingClientRect();\n const divRect = div.getBoundingClientRect();\n const offsetX = spanRect.left - divRect.left;\n const offsetY = spanRect.top - divRect.top;\n document.body.removeChild(div);\n\n const x = taRect.left - parentRect.left + offsetX + spanRect.width / 2;\n const y = taRect.top - parentRect.top + offsetY - ta.scrollTop;\n return { x, y };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/MagicWand/MagicWand.tsx"],"names":[],"mappings":"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","file":"chunk-2OMB3OOR.js","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Button } from \"../Button\";\nimport { Badge } from \"../Badge\";\nimport { Textarea } from \"../inputs/Textarea/Textarea\";\n\n/**\n * MagicWand — selection-anchored floating toolbar for text inputs.\n *\n * When the user highlights text inside the wrapped <Textarea>, a small\n * pill of AI quick-actions floats above the selection. Clicking an\n * action invokes the host callback with the selected substring + range;\n * the host returns a replacement string and MagicWand swaps it in-place.\n *\n * <MagicWand\n * value={body}\n * onValueChange={setBody}\n * actions={[\n * { id: \"rephrase\", label: \"Rephrase\", hint: \"same meaning, different words\",\n * run: async (s) => await ai.rephrase(s) },\n * …\n * ]}\n * />\n *\n * Two appearances: `\"floating\"` (default — full labels) and `\"pill\"`\n * (icon-only, compact). Auto-hides on click-away or scroll by default.\n */\nexport type MagicWandSelection = { start: number; end: number; text: string };\n\nexport type MagicWandAction = {\n /** Stable id, also shown as the busy-state key. */\n id: string;\n /** Display label (first character used in `pill` mode). */\n label: string;\n /** Tooltip body. */\n hint?: string;\n /** Optional kind chip rendered next to the label. */\n tag?: string;\n /** Returns the replacement text for the selection. */\n run: (selection: string, range: MagicWandSelection) => Promise<string> | string;\n};\n\nexport type MagicWandAppearance = \"pill\" | \"floating\";\n\nexport interface MagicWandProps {\n /** Controlled textarea value. */\n value: string;\n /** Called on every edit. */\n onValueChange: (v: string) => void;\n /** Action list. */\n actions: MagicWandAction[];\n /** Visual treatment. Defaults to \"floating\". */\n appearance?: MagicWandAppearance;\n /** Auto-hide the wand on click-away or scroll. Defaults to true. */\n autoHide?: boolean;\n /** Textarea rows. */\n rows?: number;\n /** Placeholder. */\n placeholder?: string;\n /** Called after an action runs successfully. */\n onAction?: (action: MagicWandAction, selection: MagicWandSelection, replacement: string) => void;\n}\n\nexport function MagicWand({\n value,\n onValueChange,\n actions,\n appearance = \"floating\",\n autoHide = true,\n rows = 6,\n placeholder,\n onAction,\n}: MagicWandProps) {\n const taRef = useRef<HTMLTextAreaElement>(null);\n const wandRef = useRef<HTMLDivElement>(null);\n const [sel, setSel] = useState<MagicWandSelection | null>(null);\n const [pos, setPos] = useState<{ x: number; y: number } | null>(null);\n const [busy, setBusy] = useState<string | null>(null);\n\n const measureSelection = useCallback(() => {\n const ta = taRef.current;\n if (!ta) return;\n const start = ta.selectionStart ?? 0;\n const end = ta.selectionEnd ?? 0;\n if (start === end) {\n setSel(null);\n setPos(null);\n return;\n }\n const text = ta.value.slice(start, end);\n setSel({ start, end, text });\n const rect = caretRect(ta, start, end);\n if (rect) setPos({ x: rect.x, y: rect.y });\n }, []);\n\n useEffect(() => {\n if (!autoHide) return;\n const onScroll = () => {\n setSel(null);\n setPos(null);\n };\n window.addEventListener(\"scroll\", onScroll, true);\n return () => window.removeEventListener(\"scroll\", onScroll, true);\n }, [autoHide]);\n\n useEffect(() => {\n if (!autoHide) return;\n const onDocMouseDown = (e: MouseEvent) => {\n if (wandRef.current?.contains(e.target as Node)) return;\n if (taRef.current?.contains(e.target as Node)) return;\n setSel(null);\n setPos(null);\n };\n document.addEventListener(\"mousedown\", onDocMouseDown);\n return () => document.removeEventListener(\"mousedown\", onDocMouseDown);\n }, [autoHide]);\n\n const handleAction = async (action: MagicWandAction) => {\n if (!sel) return;\n setBusy(action.id);\n try {\n const replacement = await action.run(sel.text, sel);\n const next = value.slice(0, sel.start) + replacement + value.slice(sel.end);\n onValueChange(next);\n onAction?.(action, sel, replacement);\n } finally {\n setBusy(null);\n setSel(null);\n setPos(null);\n }\n };\n\n return (\n <div className=\"relative\">\n <Textarea\n ref={taRef as unknown as React.Ref<HTMLTextAreaElement>}\n value={value}\n onValueChange={onValueChange}\n onSelect={measureSelection}\n onKeyUp={measureSelection}\n onMouseUp={measureSelection}\n rows={rows}\n placeholder={placeholder}\n />\n {sel && pos && (\n <Wand\n ref={wandRef}\n pos={pos}\n actions={actions}\n appearance={appearance}\n busy={busy}\n onAction={handleAction}\n selectionLength={sel.text.length}\n />\n )}\n </div>\n );\n}\n\nfunction Wand({\n ref,\n pos,\n actions,\n appearance,\n busy,\n onAction,\n selectionLength,\n}: {\n ref: React.RefObject<HTMLDivElement | null>;\n pos: { x: number; y: number };\n actions: MagicWandAction[];\n appearance: MagicWandAppearance;\n busy: string | null;\n onAction: (a: MagicWandAction) => void;\n selectionLength: number;\n}) {\n return (\n <div\n ref={ref}\n className=\"absolute z-20 -translate-x-1/2 -translate-y-full\"\n style={{ left: pos.x, top: pos.y - 6 }}\n >\n <div className=\"flex items-center gap-1 rounded-full border border-violet-300 bg-white px-1.5 py-1 shadow-lg ring-1 ring-violet-200 dark:border-violet-700 dark:bg-zinc-900 dark:ring-violet-900\">\n <span className=\"ml-1 mr-0.5 inline-flex items-center gap-1 text-[10px] font-medium text-violet-700 dark:text-violet-300\">\n <span aria-hidden>✦</span>\n {appearance === \"floating\" && (\n <Badge color=\"violet\">{selectionLength} chars</Badge>\n )}\n </span>\n {actions.map((a) => (\n <Button\n key={a.id}\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => onAction(a)}\n disabled={busy !== null}\n title={a.hint}\n >\n {busy === a.id ? \"…\" : appearance === \"pill\" ? a.label[0] : a.label}\n {appearance === \"floating\" && a.tag && (\n <span className=\"ml-1 rounded-full bg-zinc-100 px-1 text-[9px] font-medium text-zinc-500 dark:bg-zinc-800\">\n {a.tag}\n </span>\n )}\n </Button>\n ))}\n </div>\n <div className=\"absolute left-1/2 top-full -translate-x-1/2 -translate-y-px\">\n <div className=\"h-2 w-2 rotate-45 border-b border-r border-violet-300 bg-white dark:border-violet-700 dark:bg-zinc-900\" />\n </div>\n </div>\n );\n}\n\n/**\n * Caret/selection geometry: mirror the textarea into a hidden div and\n * measure the selected substring's bounding rect. Not perfect for\n * resized textareas, but works for typical chat composers.\n */\nfunction caretRect(\n ta: HTMLTextAreaElement,\n start: number,\n end: number,\n): { x: number; y: number } | null {\n const div = document.createElement(\"div\");\n const style = getComputedStyle(ta);\n const props = [\n \"boxSizing\",\n \"width\",\n \"height\",\n \"overflowX\",\n \"overflowY\",\n \"borderTopWidth\",\n \"borderRightWidth\",\n \"borderBottomWidth\",\n \"borderLeftWidth\",\n \"paddingTop\",\n \"paddingRight\",\n \"paddingBottom\",\n \"paddingLeft\",\n \"fontStyle\",\n \"fontVariant\",\n \"fontWeight\",\n \"fontStretch\",\n \"fontSize\",\n \"fontSizeAdjust\",\n \"lineHeight\",\n \"fontFamily\",\n \"textAlign\",\n \"textTransform\",\n \"textIndent\",\n \"letterSpacing\",\n \"wordSpacing\",\n \"tabSize\",\n ] as const;\n for (const p of props) (div.style as any)[p] = (style as any)[p];\n div.style.position = \"absolute\";\n div.style.top = \"-9999px\";\n div.style.left = \"-9999px\";\n div.style.whiteSpace = \"pre-wrap\";\n div.style.wordWrap = \"break-word\";\n\n const value = ta.value;\n div.textContent = value.substring(0, start);\n const startSpan = document.createElement(\"span\");\n startSpan.textContent = value.substring(start, end) || \".\";\n div.appendChild(startSpan);\n\n document.body.appendChild(div);\n const taRect = ta.getBoundingClientRect();\n const parentRect =\n (ta.offsetParent as HTMLElement | null)?.getBoundingClientRect() ?? taRect;\n const spanRect = startSpan.getBoundingClientRect();\n const divRect = div.getBoundingClientRect();\n const offsetX = spanRect.left - divRect.left;\n const offsetY = spanRect.top - divRect.top;\n document.body.removeChild(div);\n\n const x = taRect.left - parentRect.left + offsetX + spanRect.width / 2;\n const y = taRect.top - parentRect.top + offsetY - ta.scrollTop;\n return { x, y };\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { sanitizeHref } from './chunk-XJXQG34J.js';
|
|
1
2
|
import { resolve } from './chunk-45BBNJQD.js';
|
|
2
3
|
import { Icon } from './chunk-IVRYSSM7.js';
|
|
3
|
-
import { sanitizeHref } from './chunk-XJXQG34J.js';
|
|
4
4
|
import { cn } from './chunk-MMW2JGKZ.js';
|
|
5
5
|
import { forwardRef } from 'react';
|
|
6
6
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
@@ -452,5 +452,5 @@ Button.displayName = "Button";
|
|
|
452
452
|
var Action = Button;
|
|
453
453
|
|
|
454
454
|
export { Action, Button };
|
|
455
|
-
//# sourceMappingURL=chunk-
|
|
456
|
-
//# sourceMappingURL=chunk-
|
|
455
|
+
//# sourceMappingURL=chunk-46IPQEUP.js.map
|
|
456
|
+
//# sourceMappingURL=chunk-46IPQEUP.js.map
|