@crewlethq/ui 0.17.1 → 0.18.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.
Files changed (146) hide show
  1. package/README.md +36 -1
  2. package/dist/AccessDeniedCard/AccessDeniedCard.d.ts +39 -0
  3. package/dist/AccessDeniedCard/index.d.ts +3 -0
  4. package/dist/AccessDeniedCard/index.js +8 -0
  5. package/dist/AccessDeniedCard-WXM5DBDZ.css +8 -0
  6. package/dist/ArtifactCard/ArtifactCard.d.ts +61 -0
  7. package/dist/ArtifactCard/index.d.ts +3 -0
  8. package/dist/ArtifactCard/index.js +12 -0
  9. package/dist/ArtifactCard-EXVAEBV6.css +109 -0
  10. package/dist/AvatarPicker/index.js +4 -4
  11. package/dist/ChangeMark/ChangeMark.d.ts +26 -0
  12. package/dist/ChangeMark/index.d.ts +3 -0
  13. package/dist/ChangeMark/index.js +8 -0
  14. package/dist/ChangeMark-3VMTW7MT.css +22 -0
  15. package/dist/Combobox/index.js +1 -1
  16. package/dist/CommandPalette/index.js +3 -3
  17. package/dist/CursorFlag/CursorFlag.d.ts +34 -0
  18. package/dist/CursorFlag/index.d.ts +3 -0
  19. package/dist/CursorFlag/index.js +3 -0
  20. package/dist/CursorFlag-AJQJBUGY.css +61 -0
  21. package/dist/DataTable/index.js +6 -6
  22. package/dist/DataView/index.js +12 -12
  23. package/dist/DateTimePicker/index.js +2 -2
  24. package/dist/FollowBanner/FollowBanner.d.ts +35 -0
  25. package/dist/FollowBanner/index.d.ts +2 -0
  26. package/dist/FollowBanner/index.js +7 -0
  27. package/dist/FrameProtocol/index.d.ts +2 -0
  28. package/dist/FrameProtocol/index.js +2 -0
  29. package/dist/FrameProtocol/protocol.d.ts +164 -0
  30. package/dist/HuePicker/index.js +2 -2
  31. package/dist/LinkInterstitial/LinkInterstitial.d.ts +41 -0
  32. package/dist/LinkInterstitial/index.d.ts +3 -0
  33. package/dist/LinkInterstitial/index.js +11 -0
  34. package/dist/LinkInterstitial-ET4MSVSV.css +34 -0
  35. package/dist/ListInput/index.js +1 -1
  36. package/dist/LiveCursor/LiveCursor.d.ts +34 -0
  37. package/dist/LiveCursor/index.d.ts +3 -0
  38. package/dist/LiveCursor/index.js +4 -0
  39. package/dist/LiveCursor-DZHGIYWE.css +47 -0
  40. package/dist/MarkdownEditor/index.js +2 -2
  41. package/dist/Modal/index.js +2 -2
  42. package/dist/OrgTable/index.js +2 -2
  43. package/dist/PresenceModel/index.d.ts +2 -0
  44. package/dist/PresenceModel/index.js +3 -0
  45. package/dist/PresenceModel/presence.d.ts +80 -0
  46. package/dist/PresenceRing/PresenceRing.d.ts +31 -0
  47. package/dist/PresenceRing/index.d.ts +3 -0
  48. package/dist/PresenceRing/index.js +2 -0
  49. package/dist/PresenceRing-M4C22Q6U.css +68 -0
  50. package/dist/PresenceStack/PresenceStack.d.ts +36 -0
  51. package/dist/PresenceStack/index.d.ts +3 -0
  52. package/dist/PresenceStack/index.js +9 -0
  53. package/dist/PresenceStack-QSXQJND5.css +59 -0
  54. package/dist/PricingCard/index.js +2 -2
  55. package/dist/Principal/Principal.d.ts +35 -0
  56. package/dist/Principal/index.d.ts +2 -0
  57. package/dist/Principal/index.js +4 -0
  58. package/dist/PrincipalPicker/PrincipalPicker.d.ts +45 -0
  59. package/dist/PrincipalPicker/index.d.ts +3 -0
  60. package/dist/PrincipalPicker/index.js +12 -0
  61. package/dist/PrincipalPicker-7NNZTGUP.css +41 -0
  62. package/dist/SandboxedFrame/SandboxedFrame.d.ts +142 -0
  63. package/dist/SandboxedFrame/index.d.ts +3 -0
  64. package/dist/SandboxedFrame/index.js +8 -0
  65. package/dist/SandboxedFrame-L6HBQZCD.css +50 -0
  66. package/dist/ShareDialog/ShareDialog.d.ts +101 -0
  67. package/dist/ShareDialog/index.d.ts +5 -0
  68. package/dist/ShareDialog/index.js +19 -0
  69. package/dist/ShareDialog/roles.d.ts +10 -0
  70. package/dist/ShareDialog-YZ4F37MW.css +146 -0
  71. package/dist/SheetGrid/SheetGrid.d.ts +78 -0
  72. package/dist/SheetGrid/cells.d.ts +117 -0
  73. package/dist/SheetGrid/index.d.ts +5 -0
  74. package/dist/SheetGrid/index.js +18 -0
  75. package/dist/SheetGrid-4HOXDVIB.css +595 -0
  76. package/dist/SheetModel/clipboard.d.ts +37 -0
  77. package/dist/SheetModel/edits.d.ts +68 -0
  78. package/dist/SheetModel/index.d.ts +14 -0
  79. package/dist/SheetModel/index.js +3 -0
  80. package/dist/SheetModel/labels.d.ts +71 -0
  81. package/dist/SheetModel/navigation.d.ts +79 -0
  82. package/dist/SheetModel/options.d.ts +28 -0
  83. package/dist/SheetModel/types.d.ts +126 -0
  84. package/dist/SheetModel/values.d.ts +76 -0
  85. package/dist/SheetModel/view.d.ts +32 -0
  86. package/dist/Switcher/index.js +5 -5
  87. package/dist/Tag/Tag.d.ts +13 -0
  88. package/dist/Tag/index.js +1 -1
  89. package/dist/{Tag-4BRQS6GN.css → Tag-SRGTK5KI.css} +42 -0
  90. package/dist/TagsInput/index.js +2 -2
  91. package/dist/ThemeSwitcher/apply.d.ts +43 -0
  92. package/dist/ThemeSwitcher/index.d.ts +4 -2
  93. package/dist/ThemeSwitcher/index.js +3 -2
  94. package/dist/ThemeSwitcher/preferences.d.ts +2 -19
  95. package/dist/ThemeToggle/index.js +3 -2
  96. package/dist/TimeWindowPicker/index.js +3 -3
  97. package/dist/Timeline/Timeline.d.ts +7 -2
  98. package/dist/Timeline/index.js +1 -1
  99. package/dist/VersionTimeline/VersionTimeline.d.ts +76 -0
  100. package/dist/VersionTimeline/index.d.ts +3 -0
  101. package/dist/VersionTimeline/index.js +10 -0
  102. package/dist/VersionTimeline-EBJ6MIPJ.css +108 -0
  103. package/dist/{chunk-SBRE7IML.js → chunk-2ONWEDQT.js} +1 -1
  104. package/dist/chunk-2YR5ZPQ3.js +216 -0
  105. package/dist/chunk-3BYIWOTV.js +29 -0
  106. package/dist/chunk-3UAA4IWJ.js +98 -0
  107. package/dist/chunk-4KP4RC3O.js +25 -0
  108. package/dist/chunk-4U3GQ7D2.js +99 -0
  109. package/dist/chunk-5XACKNYR.js +53 -0
  110. package/dist/{chunk-FSKTOIYC.js → chunk-76FXDZAZ.js} +2 -2
  111. package/dist/chunk-A7EBCHEG.js +49 -0
  112. package/dist/{chunk-JCO3SIST.js → chunk-AQCV62K6.js} +1 -1
  113. package/dist/{chunk-O67VF5MS.js → chunk-AUQPBL4O.js} +2 -2
  114. package/dist/{chunk-HENOTGUV.js → chunk-BOW2VUCH.js} +2 -2
  115. package/dist/{chunk-UCYIRDND.js → chunk-DQSXWQMJ.js} +1 -1
  116. package/dist/chunk-ESKP3TXM.js +1 -0
  117. package/dist/chunk-F6V2PTN6.js +142 -0
  118. package/dist/chunk-FW7HN6AP.js +40 -0
  119. package/dist/chunk-FYXRWBJJ.js +1235 -0
  120. package/dist/{chunk-LOECRMHA.js → chunk-GFV4LJHH.js} +4 -3
  121. package/dist/chunk-I6POAB4V.js +20 -0
  122. package/dist/{chunk-PQ63NLFC.js → chunk-IFKBDRNJ.js} +1 -1
  123. package/dist/{chunk-6NN2F7GM.js → chunk-KVT3TJVG.js} +4 -4
  124. package/dist/chunk-LFZ45HV2.js +46 -0
  125. package/dist/{chunk-6TSZCBID.js → chunk-NP2C4Y4E.js} +1 -1
  126. package/dist/{chunk-FA2V2VT6.js → chunk-NZY77ZDU.js} +3 -3
  127. package/dist/chunk-PPAQFGS2.js +12 -0
  128. package/dist/chunk-S35G6PNC.js +80 -0
  129. package/dist/chunk-VYDFB6PJ.js +243 -0
  130. package/dist/chunk-WXAULPBH.js +135 -0
  131. package/dist/chunk-YM3GJHGW.js +69 -0
  132. package/dist/chunk-YYZRLLLY.js +822 -0
  133. package/dist/{chunk-K3OWD7BJ.js → chunk-Z24ZUSTX.js} +4 -19
  134. package/dist/chunk-ZXXDZJQN.js +36 -0
  135. package/dist/generated/index.d.ts +18 -0
  136. package/dist/generated/index.js +82 -62
  137. package/dist/index.js +82 -62
  138. package/dist/styles.css +1578 -149
  139. package/dist/utils/index.js +1 -1
  140. package/package.json +3 -3
  141. package/dist/{chunk-DEEVA6TV.js → chunk-2TZATTIU.js} +1 -1
  142. package/dist/{chunk-2UU5IGZI.js → chunk-5NVKTB6E.js} +1 -1
  143. package/dist/{chunk-CRVETI5L.js → chunk-CENOOTAV.js} +0 -0
  144. package/dist/{chunk-JF7X673Z.js → chunk-ERV5BUS3.js} +1 -1
  145. package/dist/{chunk-NXDEPUVG.js → chunk-PA4CYTBY.js} +1 -1
  146. package/dist/{chunk-77IEHFY4.js → chunk-QQGWES46.js} +1 -1
@@ -0,0 +1,99 @@
1
+ import { PrincipalAvatar } from './chunk-4KP4RC3O.js';
2
+ import { RelativeTime } from './chunk-3CZ7XAVY.js';
3
+ import { AvatarStack } from './chunk-UAN5NE7M.js';
4
+ import { Card } from './chunk-EG6NYWVK.js';
5
+ import { VisuallyHidden } from './chunk-BZFWDIV6.js';
6
+ import { cx } from './chunk-ODEQTPFM.js';
7
+ import './ArtifactCard-EXVAEBV6.css';
8
+ import { useId } from 'react';
9
+ import { TableGlyph, FileTextGlyph, FileCodeGlyph, PinGlyph } from '@crewlethq/icons/glyphs';
10
+ import { jsx, jsxs } from 'react/jsx-runtime';
11
+
12
+ var ARTIFACT_KINDS = ["html", "markdown", "sheet"];
13
+ var KIND_GLYPHS = {
14
+ html: FileCodeGlyph,
15
+ markdown: FileTextGlyph,
16
+ sheet: TableGlyph
17
+ };
18
+ function ArtifactKindGlyph({ kind, size = "md" }) {
19
+ const Drawn = KIND_GLYPHS[kind];
20
+ return /* @__PURE__ */ jsx(Drawn, { size });
21
+ }
22
+ var LABELS = {
23
+ kinds: { html: "Page", markdown: "Document", sheet: "Sheet" },
24
+ pinned: "Pinned",
25
+ version: (version) => `v${version}`,
26
+ changes: (count) => count === 1 ? "1 change since you looked" : `${count} changes since you looked`,
27
+ present: (count) => count === 1 ? "1 here now" : `${count} here now`,
28
+ updated: "Updated"
29
+ };
30
+ function ArtifactCard({
31
+ title,
32
+ kind,
33
+ href,
34
+ preview,
35
+ owner,
36
+ updatedAt,
37
+ updatedBy,
38
+ presence,
39
+ changes,
40
+ pinned = false,
41
+ version,
42
+ project,
43
+ size = "card",
44
+ className,
45
+ now,
46
+ labels
47
+ }) {
48
+ const text = { ...LABELS, ...labels, kinds: { ...LABELS.kinds, ...labels?.kinds } };
49
+ const titleId = useId();
50
+ const factsId = useId();
51
+ const present = presence && presence.length > 0 ? presence : null;
52
+ return /* @__PURE__ */ jsxs(
53
+ Card,
54
+ {
55
+ href,
56
+ variant: "subtle",
57
+ padding: "none",
58
+ className: cx("crewlet-artifact-card", `crewlet-artifact-card--${size}`, className),
59
+ "aria-labelledby": titleId,
60
+ "aria-describedby": factsId,
61
+ children: [
62
+ size === "card" ? /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-card__preview", "aria-hidden": true, children: preview ?? /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-card__glyph", children: /* @__PURE__ */ jsx(ArtifactKindGlyph, { kind, size: 32 }) }) }) : /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-card__glyph crewlet-artifact-card__glyph--compact", "aria-hidden": true, children: /* @__PURE__ */ jsx(ArtifactKindGlyph, { kind, size: "lg" }) }),
63
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-artifact-card__body", children: [
64
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-artifact-card__title-row", children: [
65
+ /* @__PURE__ */ jsx("span", { id: titleId, className: "crewlet-artifact-card__title", children: title }),
66
+ pinned ? /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-card__pin", title: text.pinned, "aria-hidden": true, children: /* @__PURE__ */ jsx(PinGlyph, { size: "xs" }) }) : null
67
+ ] }),
68
+ /* @__PURE__ */ jsxs("span", { id: factsId, className: "crewlet-artifact-card__facts", children: [
69
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-artifact-card__line", children: [
70
+ /* @__PURE__ */ jsx("span", { children: text.kinds[kind] }),
71
+ project ? /* @__PURE__ */ jsx("span", { children: project }) : null,
72
+ version !== void 0 ? /* @__PURE__ */ jsx("span", { children: text.version(version) }) : null,
73
+ pinned ? /* @__PURE__ */ jsx(VisuallyHidden, { children: text.pinned }) : null
74
+ ] }),
75
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-artifact-card__line", children: [
76
+ owner ? /* @__PURE__ */ jsxs("span", { className: "crewlet-artifact-card__owner", children: [
77
+ /* @__PURE__ */ jsx(PrincipalAvatar, { principal: owner, size: "xs" }),
78
+ /* @__PURE__ */ jsx("span", { children: owner.name })
79
+ ] }) : null,
80
+ /* @__PURE__ */ jsxs("span", { children: [
81
+ updatedBy ? `${updatedBy} ` : `${text.updated} `,
82
+ /* @__PURE__ */ jsx(RelativeTime, { value: updatedAt, now })
83
+ ] })
84
+ ] }),
85
+ present || changes !== void 0 && changes > 0 ? /* @__PURE__ */ jsxs("span", { className: "crewlet-artifact-card__line", children: [
86
+ present ? /* @__PURE__ */ jsxs("span", { className: "crewlet-artifact-card__presence", children: [
87
+ /* @__PURE__ */ jsx(AvatarStack, { members: present, max: 3, size: "xs", decorative: true }),
88
+ /* @__PURE__ */ jsx("span", { children: text.present(present.length) })
89
+ ] }) : null,
90
+ changes !== void 0 && changes > 0 ? /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-card__changes", children: text.changes(changes) }) : null
91
+ ] }) : null
92
+ ] })
93
+ ] })
94
+ ]
95
+ }
96
+ );
97
+ }
98
+
99
+ export { ARTIFACT_KINDS, ArtifactCard, ArtifactKindGlyph };
@@ -0,0 +1,53 @@
1
+ import { CursorFlag } from './chunk-3BYIWOTV.js';
2
+ import { presenceHueClass } from './chunk-I6POAB4V.js';
3
+ import { cx } from './chunk-ODEQTPFM.js';
4
+ import './LiveCursor-DZHGIYWE.css';
5
+ import { useState, useEffect } from 'react';
6
+ import { MousePointer2Glyph } from '@crewlethq/icons/glyphs';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ var LIVE_CURSOR_REST_MS = 5e3;
10
+ var LIVE_CURSOR_HIDE_MS = 3e4;
11
+ function LiveCursor({
12
+ x,
13
+ y,
14
+ name,
15
+ hue,
16
+ kind = "human",
17
+ idleAfterMs = LIVE_CURSOR_REST_MS,
18
+ hideAfterMs = LIVE_CURSOR_HIDE_MS,
19
+ interpolate = true,
20
+ className
21
+ }) {
22
+ const [phase, setPhase] = useState("live");
23
+ useEffect(() => {
24
+ setPhase("live");
25
+ const rest = setTimeout(() => setPhase("resting"), idleAfterMs);
26
+ const hide = setTimeout(() => setPhase("gone"), hideAfterMs);
27
+ return () => {
28
+ clearTimeout(rest);
29
+ clearTimeout(hide);
30
+ };
31
+ }, [x, y, idleAfterMs, hideAfterMs]);
32
+ return /* @__PURE__ */ jsxs(
33
+ "span",
34
+ {
35
+ "aria-hidden": "true",
36
+ "data-phase": phase,
37
+ className: cx(
38
+ "crewlet-live-cursor",
39
+ presenceHueClass(hue),
40
+ interpolate && "crewlet-live-cursor--glide",
41
+ phase !== "live" && `crewlet-live-cursor--${phase}`,
42
+ className
43
+ ),
44
+ style: { transform: `translate(${x}px, ${y}px)` },
45
+ children: [
46
+ /* @__PURE__ */ jsx(MousePointer2Glyph, { size: "lg", filled: true, className: "crewlet-live-cursor__pointer" }),
47
+ /* @__PURE__ */ jsx(CursorFlag, { name, hue, kind, placement: "inline", className: "crewlet-live-cursor__flag" })
48
+ ]
49
+ }
50
+ );
51
+ }
52
+
53
+ export { LIVE_CURSOR_HIDE_MS, LIVE_CURSOR_REST_MS, LiveCursor };
@@ -1,10 +1,10 @@
1
1
  import { Menu } from './chunk-NS7BVLL3.js';
2
- import { EmptyValue } from './chunk-EUCC37WY.js';
2
+ import { Modal } from './chunk-2TZATTIU.js';
3
3
  import { Checkbox } from './chunk-75CLJNDQ.js';
4
- import { Modal } from './chunk-DEEVA6TV.js';
5
4
  import { useClipboard } from './chunk-STJ7QBC3.js';
6
5
  import { Button } from './chunk-OG6CL3EB.js';
7
6
  import { IconButton } from './chunk-4FQT6ZAY.js';
7
+ import { EmptyValue } from './chunk-EUCC37WY.js';
8
8
  import { useCardHeaderSlot } from './chunk-AV2DXAEL.js';
9
9
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
10
10
  import { cx } from './chunk-ODEQTPFM.js';
@@ -0,0 +1,49 @@
1
+ import { ConfirmModal } from './chunk-2TZATTIU.js';
2
+ import { InlineCode } from './chunk-5CLW2CXT.js';
3
+ import { Callout } from './chunk-SVSCLHHI.js';
4
+ import './LinkInterstitial-ET4MSVSV.css';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ var LABELS = {
8
+ title: "Open an external link?",
9
+ intro: (source) => source ? `${source} wants to open a page outside Crewlet. Open it only if you trust where it goes.` : "This page wants to open a link outside Crewlet. Open it only if you trust where it goes.",
10
+ destination: "Destination",
11
+ insecure: "This link does not use a secure connection, so anyone on the network can read what you send to it.",
12
+ open: "Open link",
13
+ cancel: "Cancel"
14
+ };
15
+ function openExternal(url) {
16
+ window.open(url.href, "_blank", "noopener,noreferrer");
17
+ }
18
+ function LinkInterstitial({ url, source, onClose, onOpened, labels }) {
19
+ const text = { ...LABELS, ...labels };
20
+ const mail = url?.protocol === "mailto:";
21
+ const where = url ? mail ? decodeURIComponent(url.pathname) : url.hostname : "";
22
+ return /* @__PURE__ */ jsx(
23
+ ConfirmModal,
24
+ {
25
+ open: url !== null,
26
+ onClose,
27
+ onConfirm: () => {
28
+ if (!url) return;
29
+ openExternal(url);
30
+ onOpened?.(url);
31
+ onClose();
32
+ },
33
+ title: text.title,
34
+ message: text.intro(source),
35
+ confirmLabel: text.open,
36
+ cancelLabel: text.cancel,
37
+ shape: "framed",
38
+ size: "md",
39
+ children: url ? /* @__PURE__ */ jsxs("div", { className: "crewlet-link-interstitial", children: [
40
+ /* @__PURE__ */ jsx("span", { className: "crewlet-link-interstitial__label", children: text.destination }),
41
+ /* @__PURE__ */ jsx("span", { className: "crewlet-link-interstitial__host", children: where }),
42
+ /* @__PURE__ */ jsx(InlineCode, { className: "crewlet-link-interstitial__href", children: url.href }),
43
+ url.protocol === "http:" ? /* @__PURE__ */ jsx(Callout, { variant: "warning", children: text.insecure }) : null
44
+ ] }) : null
45
+ }
46
+ );
47
+ }
48
+
49
+ export { LinkInterstitial, openExternal };
@@ -1,4 +1,4 @@
1
- import { Tag } from './chunk-LOECRMHA.js';
1
+ import { Tag } from './chunk-GFV4LJHH.js';
2
2
  import { useListbox } from './chunk-F73MC7EZ.js';
3
3
  import { announce } from './chunk-RSAQOKH3.js';
4
4
  import { IconButton } from './chunk-4FQT6ZAY.js';
@@ -36,7 +36,7 @@ function TimelineItem({
36
36
  if (position === null) {
37
37
  throw new Error("Timeline.Item must be rendered inside a Timeline.");
38
38
  }
39
- const spoken = label ?? `${itemLabel} ${position}`;
39
+ const spoken = label === false ? null : label ?? `${itemLabel} ${position}`;
40
40
  return /* @__PURE__ */ jsxs(
41
41
  "li",
42
42
  {
@@ -46,7 +46,7 @@ function TimelineItem({
46
46
  children: [
47
47
  /* @__PURE__ */ jsx("span", { className: "crewlet-timeline__rail", children: /* @__PURE__ */ jsx("span", { className: "crewlet-timeline__node", "aria-hidden": true, children: marker ?? position }) }),
48
48
  /* @__PURE__ */ jsxs("div", { className: "crewlet-timeline__body", children: [
49
- /* @__PURE__ */ jsx(VisuallyHidden, { children: spoken }),
49
+ spoken === null ? null : /* @__PURE__ */ jsx(VisuallyHidden, { children: spoken }),
50
50
  children
51
51
  ] })
52
52
  ]
@@ -1,6 +1,6 @@
1
- import { HuePicker } from './chunk-NXDEPUVG.js';
2
- import { NODE_HUE_NAMES } from './chunk-AYKXMIOR.js';
1
+ import { HuePicker } from './chunk-PA4CYTBY.js';
3
2
  import { Popover } from './chunk-BWRGFKJP.js';
3
+ import { NODE_HUE_NAMES } from './chunk-AYKXMIOR.js';
4
4
  import { CharacterPicker } from './chunk-5PSXBL73.js';
5
5
  import { Avatar } from './chunk-5OM447RW.js';
6
6
  import { cx } from './chunk-ODEQTPFM.js';
@@ -1,4 +1,4 @@
1
- import { Tag } from './chunk-LOECRMHA.js';
1
+ import { Tag } from './chunk-GFV4LJHH.js';
2
2
  import './PricingCard-HTAHG4ST.css';
3
3
  import { forwardRef } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,142 @@
1
+ import { isDensityPreference, isPaletteId, isThemePreference, applyTheme, applyPalette, applyDensity } from './chunk-ZXXDZJQN.js';
2
+ import { DEFAULT_THEME_ID } from '@crewlethq/tokens';
3
+
4
+ var FRAME_MESSAGE = {
5
+ /** Frame to host, on the window: "I am the document you embedded, here is your nonce". */
6
+ hello: "crewlet.hello",
7
+ /** Host to frame, on the window, carrying the transferred port. */
8
+ port: "crewlet.port",
9
+ /** Host to frame, on the port: the reader's appearance changed. */
10
+ theme: "crewlet.theme",
11
+ /** Frame to host, on the port: open this link outside the frame. */
12
+ openLink: "crewlet.openLink",
13
+ /** Frame to host, on the port: give keyboard focus back to the page around the frame. */
14
+ focusExit: "crewlet.focusExit"
15
+ };
16
+ var FRAME_BRIDGE = { min: 1, max: 1 };
17
+ var FRAME_ORIGIN_MODES = ["per_artifact", "shared"];
18
+ var DEFAULT_FRAME_THEME = { mode: "system", palette: DEFAULT_THEME_ID, density: "normal" };
19
+ function frameFragment(nonce, theme) {
20
+ const params = new URLSearchParams();
21
+ params.set("nonce", nonce);
22
+ params.set("theme", theme.mode);
23
+ params.set("palette", theme.palette);
24
+ params.set("density", theme.density);
25
+ return params.toString();
26
+ }
27
+ var NONCE = /^[A-Za-z0-9_-]{16,256}$/;
28
+ function parseFrameFragment(hash) {
29
+ const params = new URLSearchParams(hash.startsWith("#") ? hash.slice(1) : hash);
30
+ const nonce = params.get("nonce");
31
+ const mode = params.get("theme");
32
+ const palette = params.get("palette");
33
+ const density = params.get("density");
34
+ return {
35
+ nonce: nonce !== null && NONCE.test(nonce) ? nonce : null,
36
+ theme: {
37
+ mode: isThemePreference(mode) ? mode : DEFAULT_FRAME_THEME.mode,
38
+ // A well-formed id the tokens do not know selects no block and paints
39
+ // the default, which is what an unknown palette should do.
40
+ palette: isPaletteId(palette) ? palette : DEFAULT_FRAME_THEME.palette,
41
+ density: isDensityPreference(density) ? density : DEFAULT_FRAME_THEME.density
42
+ }
43
+ };
44
+ }
45
+ function isFrameNonce(value) {
46
+ return typeof value === "string" && NONCE.test(value);
47
+ }
48
+ function httpOrigin(raw) {
49
+ let url;
50
+ try {
51
+ url = new URL(raw);
52
+ } catch {
53
+ return null;
54
+ }
55
+ return url.protocol === "https:" || url.protocol === "http:" ? url.origin : null;
56
+ }
57
+ function frameOriginMatches(mode, expected, actual) {
58
+ return mode === "per_artifact" ? actual === expected : actual === "null";
59
+ }
60
+ function portTargetOrigin(mode, origin) {
61
+ return mode === "per_artifact" ? origin : "*";
62
+ }
63
+ var FRAME_SANDBOX_TOKENS = {
64
+ "allow-scripts": "always",
65
+ "allow-forms": "always",
66
+ // Only on an origin of the artifact's own: on the shared host it would hand
67
+ // every artifact the same storage and the same origin.
68
+ "allow-same-origin": "per_artifact",
69
+ // Only when the version declares them, which the engine decides.
70
+ "allow-downloads": "declared",
71
+ "allow-modals": "declared"
72
+ };
73
+ function checkFrameSandbox(sandbox, mode) {
74
+ const tokens = sandbox.split(/\s+/).filter(Boolean);
75
+ const refused = tokens.filter((token) => {
76
+ const rule = FRAME_SANDBOX_TOKENS[token];
77
+ if (rule === void 0) return true;
78
+ return rule === "per_artifact" && mode !== "per_artifact";
79
+ });
80
+ return refused.length > 0 ? { ok: false, refused } : { ok: true, value: tokens.join(" ") };
81
+ }
82
+ var record = (data) => typeof data === "object" && data !== null && !Array.isArray(data) ? data : null;
83
+ var CONTRACT = /^\d{1,4}\.\d{1,5}\.\d{1,6}$/;
84
+ var bridgeVersion = (value) => typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 9999;
85
+ function readFrameHello(data) {
86
+ const message = record(data);
87
+ if (!message || message["type"] !== FRAME_MESSAGE.hello) return null;
88
+ const { nonce, contract } = message;
89
+ const bridge = record(message["bridge"]);
90
+ if (!isFrameNonce(nonce) || typeof contract !== "string" || !CONTRACT.test(contract) || !bridge) return null;
91
+ const { min, max } = bridge;
92
+ if (!bridgeVersion(min) || !bridgeVersion(max) || min > max) return null;
93
+ return { type: FRAME_MESSAGE.hello, nonce, contract, bridge: { min, max } };
94
+ }
95
+ function negotiateBridge(offered, supported = FRAME_BRIDGE) {
96
+ const high = Math.min(offered.max, supported.max);
97
+ return high >= Math.max(offered.min, supported.min) ? high : null;
98
+ }
99
+ function readFramePortMessage(data) {
100
+ const message = record(data);
101
+ if (!message || message["type"] !== FRAME_MESSAGE.port || !bridgeVersion(message["bridge"])) return null;
102
+ return { type: FRAME_MESSAGE.port, bridge: message["bridge"] };
103
+ }
104
+ function frameThemeMessage(theme) {
105
+ return { type: FRAME_MESSAGE.theme, mode: theme.mode, palette: theme.palette, density: theme.density };
106
+ }
107
+ function readFrameTheme(data) {
108
+ const message = record(data);
109
+ if (!message || message["type"] !== FRAME_MESSAGE.theme) return null;
110
+ const { mode, palette, density } = message;
111
+ if (!isThemePreference(mode) || !isPaletteId(palette) || !isDensityPreference(density)) return null;
112
+ return { mode, palette, density };
113
+ }
114
+ function applyFrameTheme(theme, root) {
115
+ applyTheme(theme.mode, root);
116
+ applyPalette(theme.palette, root);
117
+ applyDensity(theme.density, root);
118
+ }
119
+ function readOpenLink(data) {
120
+ const message = record(data);
121
+ if (!message || message["type"] !== FRAME_MESSAGE.openLink) return null;
122
+ return typeof message["url"] === "string" ? message["url"] : null;
123
+ }
124
+ function isFocusExit(data) {
125
+ return record(data)?.["type"] === FRAME_MESSAGE.focusExit;
126
+ }
127
+ var EXTERNAL_URL_MAX = 2048;
128
+ function externalUrl(raw) {
129
+ if (raw.length === 0 || raw.length > EXTERNAL_URL_MAX) return null;
130
+ let url;
131
+ try {
132
+ url = new URL(raw);
133
+ } catch {
134
+ return null;
135
+ }
136
+ if (url.protocol === "mailto:") return url.pathname.length > 0 ? url : null;
137
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
138
+ if (url.username !== "" || url.password !== "" || url.hostname === "") return null;
139
+ return url;
140
+ }
141
+
142
+ export { DEFAULT_FRAME_THEME, EXTERNAL_URL_MAX, FRAME_BRIDGE, FRAME_MESSAGE, FRAME_ORIGIN_MODES, FRAME_SANDBOX_TOKENS, applyFrameTheme, checkFrameSandbox, externalUrl, frameFragment, frameOriginMatches, frameThemeMessage, httpOrigin, isFocusExit, isFrameNonce, negotiateBridge, parseFrameFragment, portTargetOrigin, readFrameHello, readFramePortMessage, readFrameTheme, readOpenLink };
@@ -0,0 +1,40 @@
1
+ import { Callout } from './chunk-SVSCLHHI.js';
2
+ import { Button } from './chunk-OG6CL3EB.js';
3
+ import { isComposing } from './chunk-R25RGPUB.js';
4
+ import { cx } from './chunk-ODEQTPFM.js';
5
+ import { useEffect } from 'react';
6
+ import { EyeGlyph, UserGlyph } from '@crewlethq/icons/glyphs';
7
+ import { jsx } from 'react/jsx-runtime';
8
+
9
+ var LABELS = {
10
+ following: (name) => `Following ${name}. Press Esc or scroll to stop.`,
11
+ left: (name) => `${name} left. You stopped following.`,
12
+ stop: "Stop following",
13
+ dismiss: "Dismiss"
14
+ };
15
+ function FollowBanner({ name, state = "following", onStop, onDismiss, className, labels }) {
16
+ const text = { ...LABELS, ...labels };
17
+ const following = state === "following";
18
+ useEffect(() => {
19
+ if (!following) return void 0;
20
+ const onKeyDown = (event) => {
21
+ if (event.key !== "Escape" || event.defaultPrevented || isComposing(event)) return;
22
+ onStop();
23
+ };
24
+ document.addEventListener("keydown", onKeyDown);
25
+ return () => document.removeEventListener("keydown", onKeyDown);
26
+ }, [following, onStop]);
27
+ return /* @__PURE__ */ jsx(
28
+ Callout,
29
+ {
30
+ layout: "banner",
31
+ live: "polite",
32
+ icon: following ? /* @__PURE__ */ jsx(EyeGlyph, { size: "md" }) : /* @__PURE__ */ jsx(UserGlyph, { size: "md" }),
33
+ className: cx("crewlet-follow-banner", className),
34
+ action: following ? /* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", onClick: onStop, children: text.stop }) : onDismiss ? /* @__PURE__ */ jsx(Button, { size: "small", variant: "ghost", onClick: onDismiss, children: text.dismiss }) : void 0,
35
+ children: following ? text.following(name) : text.left(name)
36
+ }
37
+ );
38
+ }
39
+
40
+ export { FollowBanner };