@guard-rail/core 0.2.0 → 0.3.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/README.md +86 -6
- package/dist/adapters/supabase/db.js +11 -0
- package/dist/adapters/supabase/db.js.map +1 -1
- package/dist/core/adapter-types.d.ts +35 -1
- package/dist/core/config.d.ts +7 -1
- package/dist/core/config.js +11 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/scaffold.d.ts +11 -0
- package/dist/core/scaffold.js +19 -0
- package/dist/core/scaffold.js.map +1 -0
- package/dist/core/types.d.ts +14 -1
- package/dist/index.client.d.ts +18 -0
- package/dist/index.client.js +66 -0
- package/dist/index.client.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react/Admin.d.ts +8 -3
- package/dist/react/Admin.js +14 -6
- package/dist/react/Admin.js.map +1 -1
- package/dist/react/GRail/client.js +86 -27
- package/dist/react/GRail/client.js.map +1 -1
- package/dist/react/GRail/editor.d.ts +24 -0
- package/dist/react/GRail/editor.js +9 -0
- package/dist/react/GRail/editor.js.map +1 -0
- package/dist/react/GRail/index.js +2 -2
- package/dist/react/GRail/index.js.map +1 -1
- package/dist/react/GRail/render.d.ts +3 -3
- package/dist/react/GRail/universal.d.ts +22 -0
- package/dist/react/GRail/universal.js +29 -0
- package/dist/react/GRail/universal.js.map +1 -0
- package/dist/react/Icons.d.ts +2 -1
- package/dist/react/Icons.js +5 -0
- package/dist/react/Icons.js.map +1 -1
- package/dist/react/MediaPicker.js +3 -3
- package/dist/react/MediaPicker.js.map +1 -1
- package/dist/react/Provider.d.ts +9 -5
- package/dist/react/Provider.js +10 -4
- package/dist/react/Provider.js.map +1 -1
- package/dist/react/Repeater/client.d.ts +3 -3
- package/dist/react/Repeater/client.js +2 -2
- package/dist/react/Repeater/client.js.map +1 -1
- package/dist/react/Repeater/index.d.ts +3 -3
- package/dist/react/Team.js +7 -7
- package/dist/react/Team.js.map +1 -1
- package/dist/react/styles.d.ts +104 -30
- package/dist/react/styles.js +154 -65
- package/dist/react/styles.js.map +1 -1
- package/dist/react/useAnchorRect.js +1 -0
- package/dist/react/useAnchorRect.js.map +1 -1
- package/dist/server/data.js +1 -15
- package/dist/server/data.js.map +1 -1
- package/dist/server/loader.d.ts +9 -2
- package/dist/server/loader.js +74 -11
- package/dist/server/loader.js.map +1 -1
- package/dist/server/wrapper.d.ts +2 -2
- package/dist/server/wrapper.js +6 -2
- package/dist/server/wrapper.js.map +1 -1
- package/package.json +11 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
4
5
|
import { saveOverride } from "../../server/actions";
|
|
5
6
|
import { GRailRender } from "./render";
|
|
6
7
|
import { useGRail } from "../Provider";
|
|
7
8
|
import { useAnchorRect } from "../useAnchorRect";
|
|
8
|
-
import { CheckIcon, CloseIcon, SpinnerIcon } from "../Icons";
|
|
9
|
+
import { CheckIcon, CloseIcon, PencilIcon, SpinnerIcon } from "../Icons";
|
|
9
10
|
import { S } from "../styles";
|
|
10
11
|
import { MediaPicker } from "../MediaPicker";
|
|
11
12
|
function GRailClient({
|
|
@@ -19,7 +20,8 @@ function GRailClient({
|
|
|
19
20
|
const [error, setError] = useState(null);
|
|
20
21
|
const [row, setRow] = useState(initialOverride);
|
|
21
22
|
const elementRef = useRef(null);
|
|
22
|
-
const
|
|
23
|
+
const isImage = row.value.type === "image";
|
|
24
|
+
const rect = useAnchorRect(elementRef, editing || isEditMode && isImage);
|
|
23
25
|
const sessionTextRef = useRef(initialOverride.value.text ?? "");
|
|
24
26
|
const draftTextRef = useRef(initialOverride.value.text ?? "");
|
|
25
27
|
const isInline = row.value.type === "text" || row.value.type === "button";
|
|
@@ -78,20 +80,13 @@ function GRailClient({
|
|
|
78
80
|
override: renderedValue,
|
|
79
81
|
style: {
|
|
80
82
|
...htmlProps.style,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
...
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// can't out-stack a sibling on its own no matter how "on top"
|
|
89
|
-
// that sounds. `position` is required for z-index to apply at
|
|
90
|
-
// all on a normally static <img> — skipped for `fill` images,
|
|
91
|
-
// which next/image already positions absolutely internally;
|
|
92
|
-
// overriding that would break fill's own layout.
|
|
93
|
-
position: htmlProps.fill ? void 0 : "relative",
|
|
94
|
-
zIndex: 9998
|
|
83
|
+
// Images are left alone — no outline, position, or z-index — and
|
|
84
|
+
// get a separate ImageMarker instead. Restyling the <img> itself
|
|
85
|
+
// can't work for a background image with content layered over it.
|
|
86
|
+
...isImage ? { cursor: "pointer" } : {
|
|
87
|
+
outline: editing ? "2px solid #2563eb" : "1px dashed #60a5fa",
|
|
88
|
+
outlineOffset: 2,
|
|
89
|
+
cursor: editing ? "text" : "pointer"
|
|
95
90
|
}
|
|
96
91
|
},
|
|
97
92
|
onClick: (e) => {
|
|
@@ -118,18 +113,82 @@ function GRailClient({
|
|
|
118
113
|
} : {}
|
|
119
114
|
}
|
|
120
115
|
),
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
rect && (isImage || editing) && createPortal(
|
|
117
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
118
|
+
isImage && /* @__PURE__ */ jsx(ImageMarker, { rect, editing, onEdit: beginEdit }),
|
|
119
|
+
editing && /* @__PURE__ */ jsx(
|
|
120
|
+
Chrome,
|
|
121
|
+
{
|
|
122
|
+
rect,
|
|
123
|
+
isInline,
|
|
124
|
+
row,
|
|
125
|
+
error,
|
|
126
|
+
saving,
|
|
127
|
+
onChange: updateAttribute,
|
|
128
|
+
onPatch: patchValue,
|
|
129
|
+
onSave: handleSave,
|
|
130
|
+
onCancel: handleCancel
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
] }),
|
|
134
|
+
document.body
|
|
135
|
+
)
|
|
136
|
+
] });
|
|
137
|
+
}
|
|
138
|
+
function ImageMarker({
|
|
139
|
+
rect,
|
|
140
|
+
editing,
|
|
141
|
+
onEdit
|
|
142
|
+
}) {
|
|
143
|
+
const height = rect.bottom - rect.top;
|
|
144
|
+
if (rect.width <= 0 || height <= 0) return null;
|
|
145
|
+
if (typeof window !== "undefined" && (rect.bottom < 0 || rect.top > window.innerHeight)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const HANDLE_H = 26;
|
|
149
|
+
const small = height < HANDLE_H * 2 || rect.width < 120;
|
|
150
|
+
let handleTop;
|
|
151
|
+
if (small) {
|
|
152
|
+
handleTop = rect.top - HANDLE_H - 4 >= 4 ? rect.top - HANDLE_H - 4 : rect.bottom + 4;
|
|
153
|
+
} else {
|
|
154
|
+
handleTop = Math.min(
|
|
155
|
+
Math.max(rect.top + 6, 6),
|
|
156
|
+
Math.max(rect.top + 6, rect.bottom - HANDLE_H - 6)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const handleLeft = small ? rect.left : rect.left + 6;
|
|
160
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
161
|
+
/* @__PURE__ */ jsx(
|
|
162
|
+
"div",
|
|
123
163
|
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
164
|
+
"data-guardrail": true,
|
|
165
|
+
style: {
|
|
166
|
+
...S.markerOutline,
|
|
167
|
+
top: rect.top - 2,
|
|
168
|
+
left: rect.left - 2,
|
|
169
|
+
width: rect.width + 4,
|
|
170
|
+
height: height + 4,
|
|
171
|
+
border: editing ? "2px solid #2563eb" : "1px dashed #60a5fa"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
!editing && /* @__PURE__ */ jsxs(
|
|
176
|
+
"button",
|
|
177
|
+
{
|
|
178
|
+
type: "button",
|
|
179
|
+
"data-guardrail": true,
|
|
180
|
+
style: { ...S.markerHandle, top: handleTop, left: handleLeft },
|
|
181
|
+
"aria-label": "Edit image",
|
|
182
|
+
title: "Edit image",
|
|
183
|
+
onClick: (e) => {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
onEdit();
|
|
187
|
+
},
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ jsx(PencilIcon, {}),
|
|
190
|
+
" Edit"
|
|
191
|
+
]
|
|
133
192
|
}
|
|
134
193
|
)
|
|
135
194
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/react/GRail/client.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { saveOverride } from \"../../server/actions\";\nimport { GRailRender } from \"./render\";\nimport { useGRail } from \"../Provider\";\nimport { useAnchorRect } from \"../useAnchorRect\";\nimport { CheckIcon, CloseIcon, SpinnerIcon } from \"../Icons\";\nimport { S } from \"../styles\";\nimport { MediaPicker } from \"../MediaPicker\";\nimport type { HTMLKeys, OverrideRow, OverrideValue } from \"../../core/types\";\n\nexport function GRailClient({\n as,\n initialOverride,\n ...htmlProps\n}: {\n as?: HTMLKeys;\n initialOverride: OverrideRow;\n [key: string]: unknown;\n}) {\n const { isEditMode } = useGRail();\n const [editing, setEditing] = useState(false);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [row, setRow] = useState<OverrideRow>(initialOverride);\n\n const elementRef = useRef<HTMLElement | null>(null);\n const rect = useAnchorRect(elementRef, editing);\n\n /**\n * The text the element was rendered with when this edit session began.\n *\n * While editing, the DOM owns the text and React must not touch it. Feeding\n * state back into a contentEditable's children makes React replace the text\n * node on every keystroke, which resets the caret to offset 0 — typing then\n * appears reversed and backspace jumps to the start. Holding this value\n * constant means the children prop never changes, so React leaves the node\n * alone and the caret stays put.\n */\n const sessionTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n /** Live DOM text, updated without re-rendering. Read on save. */\n const draftTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n\n const isInline = row.value.type === \"text\" || row.value.type === \"button\";\n\n const beginEdit = () => {\n sessionTextRef.current = row.value.text ?? \"\";\n draftTextRef.current = row.value.text ?? \"\";\n setError(null);\n setEditing(true);\n };\n\n const updateAttribute = (key: string, value: string) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, [key]: value } }));\n\n const patchValue = (patch: Partial<OverrideValue>) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, ...patch } }));\n\n const handleSave = async () => {\n // Inline text lives in the DOM, not in state, so read it back here.\n const next: OverrideRow = isInline\n ? { ...row, value: { ...row.value, text: draftTextRef.current } }\n : row;\n\n setSaving(true);\n setError(null);\n const res = await saveOverride(next);\n setSaving(false);\n\n if (!res.success) {\n setError(res.error ?? \"Save failed.\");\n return;\n }\n setRow(next);\n setEditing(false);\n };\n\n const handleCancel = () => {\n setRow(initialOverride);\n draftTextRef.current = initialOverride.value.text ?? \"\";\n if (isInline && elementRef.current) {\n elementRef.current.textContent = initialOverride.value.text ?? \"\";\n }\n setError(null);\n setEditing(false);\n };\n\n // Clicking away from an active edit cancels it, the same as Escape. Anything\n // inside the field itself (repositioning the caret) or inside the anchored\n // toolbar/panel (both carry data-guardrail) is not \"away\" — checked via\n // closest() rather than React's onClick so a stopPropagation() inside the\n // chrome (image/link panel inputs) reliably suppresses this too, since that\n // call stops the native event before it ever reaches this document listener.\n useEffect(() => {\n if (!editing) return;\n const handlePointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (elementRef.current?.contains(target)) return;\n if (target instanceof Element && target.closest(\"[data-guardrail]\")) return;\n handleCancel();\n };\n document.addEventListener(\"mousedown\", handlePointerDown);\n return () => document.removeEventListener(\"mousedown\", handlePointerDown);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editing]);\n\n if (!isEditMode) {\n return <GRailRender as={as} {...htmlProps} override={row.value} />;\n }\n\n // Frozen during the session so React never rewrites the live text node.\n const renderedValue =\n editing && isInline\n ? { ...row.value, text: sessionTextRef.current }\n : row.value;\n\n return (\n <>\n <GRailRender\n as={as}\n {...htmlProps}\n ref={elementRef}\n override={renderedValue}\n style={{\n ...(htmlProps.style as React.CSSProperties),\n outline: editing ? \"2px solid #2563eb\" : \"1px dashed #60a5fa\",\n outlineOffset: 2,\n cursor: editing ? \"text\" : \"pointer\",\n ...(row.value.type === \"image\" && {\n // A decorative sibling stacked above the image (a hero's\n // text/gradient scrim, say) covers the outline too — outline is\n // part of this element's own paint, not a separate layer, so it\n // can't out-stack a sibling on its own no matter how \"on top\"\n // that sounds. `position` is required for z-index to apply at\n // all on a normally static <img> — skipped for `fill` images,\n // which next/image already positions absolutely internally;\n // overriding that would break fill's own layout.\n position: htmlProps.fill ? undefined : \"relative\",\n zIndex: 9998,\n }),\n }}\n onClick={(e: React.MouseEvent) => {\n if (editing) return;\n e.preventDefault();\n // preventDefault only blocks the browser's native action (following\n // an href); it does not stop the click from bubbling. A field\n // rendered inside a Link would still trigger the Link's own onClick\n // and navigate anyway — stopPropagation is what actually claims the\n // click for editing instead. (Use `editable={false}` on fields\n // meant to stay pure navigation, e.g. inside a card that links to\n // that content's own page.)\n e.stopPropagation();\n beginEdit();\n }}\n // Spread only for inline text: images and links would receive invalid\n // DOM props (contentEditable on next/image, etc).\n {...(isInline\n ? {\n contentEditable: editing,\n suppressContentEditableWarning: true,\n onInput: (e: React.FormEvent<HTMLElement>) => {\n // Ref only — setState here re-renders and moves the caret.\n draftTextRef.current = e.currentTarget.textContent ?? \"\";\n },\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n void handleSave();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n handleCancel();\n }\n },\n }\n : {})}\n />\n\n {editing && rect && (\n <Chrome\n rect={rect}\n isInline={isInline}\n row={row}\n error={error}\n saving={saving}\n onChange={updateAttribute}\n onPatch={patchValue}\n onSave={handleSave}\n onCancel={handleCancel}\n />\n )}\n </>\n );\n}\n\nfunction Chrome({\n rect,\n isInline,\n row,\n error,\n saving,\n onChange,\n onPatch,\n onSave,\n onCancel,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n isInline: boolean;\n row: OverrideRow;\n error: string | null;\n saving: boolean;\n onChange: (key: string, value: string) => void;\n onPatch: (patch: Partial<OverrideValue>) => void;\n onSave: () => void;\n onCancel: () => void;\n}) {\n const { storage } = useGRail();\n const [pickerOpen, setPickerOpen] = useState(false);\n const GAP = 8;\n // Flip above the element when there is no room below it.\n const below = rect.bottom + GAP;\n const flip = typeof window !== \"undefined\" && below > window.innerHeight - 120;\n\n const position: React.CSSProperties = {\n left: Math.max(8, rect.left),\n ...(flip ? { top: Math.max(8, rect.top - GAP - 44) } : { top: below }),\n };\n\n const actions = (\n <>\n <button\n style={S.iconBtnPrimary}\n onClick={onSave}\n disabled={saving}\n title=\"Save (Enter)\"\n aria-label=\"Save\"\n >\n {saving ? <SpinnerIcon /> : <CheckIcon />}\n </button>\n <button\n style={S.iconBtn}\n onClick={onCancel}\n disabled={saving}\n title=\"Cancel (Esc)\"\n aria-label=\"Cancel\"\n >\n <CloseIcon />\n </button>\n </>\n );\n\n // Text and buttons edit in place, so they only need the two controls.\n if (isInline) {\n return (\n <div data-guardrail style={{ ...S.anchoredBar, ...position }}>\n {actions}\n {error && <span style={{ ...S.error, margin: \"0 6px\" }}>{error}</span>}\n </div>\n );\n }\n\n // Images and links carry attributes with no on-page representation.\n return (\n <div\n data-guardrail\n style={{ ...S.anchoredPanel, ...position }}\n onClick={(e) => e.stopPropagation()}\n >\n {row.value.type === \"image\" && (\n <>\n {row.value.src && (\n // Plain img: previews must not depend on remotePatterns.\n <img src={row.value.src} alt=\"\" style={S.previewThumb} />\n )}\n\n {storage && (\n <button\n type=\"button\"\n style={S.secondaryBtn}\n onClick={() => setPickerOpen(true)}\n >\n {row.value.src ? \"Replace image\" : \"Choose image\"}\n </button>\n )}\n\n <label style={S.label}>Alt text</label>\n <input\n style={S.field}\n value={row.value.alt || \"\"}\n placeholder=\"Describe the image for screen readers\"\n onChange={(e) => onChange(\"alt\", e.target.value)}\n />\n\n {/* Only without storage: there is no picker then, so a URL is the one\n way to set an image. A typed URL has unknown proportions, so any\n stored dimensions are cleared. */}\n {!storage && (\n <>\n <label style={S.label}>Image URL</label>\n <input\n style={S.field}\n value={row.value.src || \"\"}\n onChange={(e) =>\n onPatch({ src: e.target.value, width: undefined, height: undefined })\n }\n />\n </>\n )}\n\n {pickerOpen && (\n <MediaPicker\n current={row.value.src}\n onClose={() => setPickerOpen(false)}\n onSelect={(image) =>\n onPatch({ src: image.src, width: image.width, height: image.height })\n }\n />\n )}\n </>\n )}\n\n {row.value.type === \"link\" && (\n <>\n <label style={S.label}>Link text</label>\n <input\n style={S.field}\n value={row.value.text || \"\"}\n onChange={(e) => onChange(\"text\", e.target.value)}\n />\n <label style={S.label}>URL</label>\n <input\n style={S.field}\n value={row.value.href || \"\"}\n onChange={(e) => onChange(\"href\", e.target.value)}\n />\n </>\n )}\n\n {error && <div style={{ ...S.error, marginBottom: 8 }}>{error}</div>}\n <div style={{ display: \"flex\", gap: 4, justifyContent: \"flex-end\" }}>\n {actions}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/client.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { saveOverride } from \"../../server/actions\";\nimport { GRailRender } from \"./render\";\nimport { useGRail } from \"../Provider\";\nimport { useAnchorRect } from \"../useAnchorRect\";\nimport { CheckIcon, CloseIcon, PencilIcon, SpinnerIcon } from \"../Icons\";\nimport { S } from \"../styles\";\nimport { MediaPicker } from \"../MediaPicker\";\nimport type { HTMLKeys, OverrideRow, OverrideValue } from \"../../core/types\";\n\nexport function GRailClient({\n as,\n initialOverride,\n ...htmlProps\n}: {\n as?: HTMLKeys;\n initialOverride: OverrideRow;\n [key: string]: unknown;\n}) {\n const { isEditMode } = useGRail();\n const [editing, setEditing] = useState(false);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [row, setRow] = useState<OverrideRow>(initialOverride);\n\n const elementRef = useRef<HTMLElement | null>(null);\n const isImage = row.value.type === \"image\";\n // Image fields keep a rect for their whole time in edit mode (their marker\n // needs it); everything else only while actively editing.\n const rect = useAnchorRect(elementRef, editing || (isEditMode && isImage));\n\n /**\n * The text the element was rendered with when this edit session began.\n *\n * While editing, the DOM owns the text and React must not touch it. Feeding\n * state back into a contentEditable's children makes React replace the text\n * node on every keystroke, which resets the caret to offset 0 — typing then\n * appears reversed and backspace jumps to the start. Holding this value\n * constant means the children prop never changes, so React leaves the node\n * alone and the caret stays put.\n */\n const sessionTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n /** Live DOM text, updated without re-rendering. Read on save. */\n const draftTextRef = useRef<string>(initialOverride.value.text ?? \"\");\n\n const isInline = row.value.type === \"text\" || row.value.type === \"button\";\n\n const beginEdit = () => {\n sessionTextRef.current = row.value.text ?? \"\";\n draftTextRef.current = row.value.text ?? \"\";\n setError(null);\n setEditing(true);\n };\n\n const updateAttribute = (key: string, value: string) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, [key]: value } }));\n\n const patchValue = (patch: Partial<OverrideValue>) =>\n setRow((prev) => ({ ...prev, value: { ...prev.value, ...patch } }));\n\n const handleSave = async () => {\n // Inline text lives in the DOM, not in state, so read it back here.\n const next: OverrideRow = isInline\n ? { ...row, value: { ...row.value, text: draftTextRef.current } }\n : row;\n\n setSaving(true);\n setError(null);\n const res = await saveOverride(next);\n setSaving(false);\n\n if (!res.success) {\n setError(res.error ?? \"Save failed.\");\n return;\n }\n setRow(next);\n setEditing(false);\n };\n\n const handleCancel = () => {\n setRow(initialOverride);\n draftTextRef.current = initialOverride.value.text ?? \"\";\n if (isInline && elementRef.current) {\n elementRef.current.textContent = initialOverride.value.text ?? \"\";\n }\n setError(null);\n setEditing(false);\n };\n\n // Clicking away from an active edit cancels it, the same as Escape. Anything\n // inside the field itself (repositioning the caret) or inside the anchored\n // toolbar/panel (both carry data-guardrail) is not \"away\" — checked via\n // closest() rather than React's onClick so a stopPropagation() inside the\n // chrome (image/link panel inputs) reliably suppresses this too, since that\n // call stops the native event before it ever reaches this document listener.\n useEffect(() => {\n if (!editing) return;\n const handlePointerDown = (e: MouseEvent) => {\n const target = e.target as Node;\n if (elementRef.current?.contains(target)) return;\n if (target instanceof Element && target.closest(\"[data-guardrail]\")) return;\n handleCancel();\n };\n document.addEventListener(\"mousedown\", handlePointerDown);\n return () => document.removeEventListener(\"mousedown\", handlePointerDown);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editing]);\n\n if (!isEditMode) {\n return <GRailRender as={as} {...htmlProps} override={row.value} />;\n }\n\n // Frozen during the session so React never rewrites the live text node.\n const renderedValue =\n editing && isInline\n ? { ...row.value, text: sessionTextRef.current }\n : row.value;\n\n return (\n <>\n <GRailRender\n as={as}\n {...htmlProps}\n ref={elementRef}\n override={renderedValue}\n style={{\n ...(htmlProps.style as React.CSSProperties),\n // Images are left alone — no outline, position, or z-index — and\n // get a separate ImageMarker instead. Restyling the <img> itself\n // can't work for a background image with content layered over it.\n ...(isImage\n ? { cursor: \"pointer\" }\n : {\n outline: editing ? \"2px solid #2563eb\" : \"1px dashed #60a5fa\",\n outlineOffset: 2,\n cursor: editing ? \"text\" : \"pointer\",\n }),\n }}\n onClick={(e: React.MouseEvent) => {\n if (editing) return;\n e.preventDefault();\n // preventDefault only blocks the browser's native action (following\n // an href); it does not stop the click from bubbling. A field\n // rendered inside a Link would still trigger the Link's own onClick\n // and navigate anyway — stopPropagation is what actually claims the\n // click for editing instead. (Use `editable={false}` on fields\n // meant to stay pure navigation, e.g. inside a card that links to\n // that content's own page.)\n e.stopPropagation();\n beginEdit();\n }}\n // Spread only for inline text: images and links would receive invalid\n // DOM props (contentEditable on next/image, etc).\n {...(isInline\n ? {\n contentEditable: editing,\n suppressContentEditableWarning: true,\n onInput: (e: React.FormEvent<HTMLElement>) => {\n // Ref only — setState here re-renders and moves the caret.\n draftTextRef.current = e.currentTarget.textContent ?? \"\";\n },\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n void handleSave();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n handleCancel();\n }\n },\n }\n : {})}\n />\n\n {/*\n Portaled to <body>, not rendered next to the field. The chrome is\n position:fixed with a high z-index, but z-index only ranks within the\n nearest stacking context — rendered inline, a field inside any ancestor\n that forms one (a z-indexed or transformed hero, say) traps its own\n popup there, where unrelated content stacked higher elsewhere on the\n page can paint straight over it. Also sidesteps a transformed ancestor\n silently re-anchoring position:fixed to itself instead of the viewport.\n */}\n {rect &&\n (isImage || editing) &&\n createPortal(\n <>\n {isImage && (\n <ImageMarker rect={rect} editing={editing} onEdit={beginEdit} />\n )}\n {editing && (\n <Chrome\n rect={rect}\n isInline={isInline}\n row={row}\n error={error}\n saving={saving}\n onChange={updateAttribute}\n onPatch={patchValue}\n onSave={handleSave}\n onCancel={handleCancel}\n />\n )}\n </>,\n document.body,\n )}\n </>\n );\n}\n\n/**\n * Dashed outline plus an \"Edit\" handle for an image field, positioned from the\n * image's measured rect rather than attached to the image. Everything here is\n * portaled to <body> by the caller, so it ranks above page content whatever the\n * image's own ancestors do, and never shifts the developer's layout.\n */\nfunction ImageMarker({\n rect,\n editing,\n onEdit,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n editing: boolean;\n onEdit: () => void;\n}) {\n const height = rect.bottom - rect.top;\n // Collapsed (display:none, zero-size) or scrolled fully out of view.\n if (rect.width <= 0 || height <= 0) return null;\n if (typeof window !== \"undefined\" && (rect.bottom < 0 || rect.top > window.innerHeight)) {\n return null;\n }\n\n const HANDLE_H = 26;\n // A logo-sized image would be mostly covered by its own handle, so small\n // ones get it just outside (above, or below when there's no room above).\n const small = height < HANDLE_H * 2 || rect.width < 120;\n let handleTop: number;\n if (small) {\n handleTop = rect.top - HANDLE_H - 4 >= 4 ? rect.top - HANDLE_H - 4 : rect.bottom + 4;\n } else {\n // Stays on screen for a tall background that's partly scrolled past, but\n // never leaves the image's own bounds.\n handleTop = Math.min(\n Math.max(rect.top + 6, 6),\n Math.max(rect.top + 6, rect.bottom - HANDLE_H - 6),\n );\n }\n const handleLeft = small ? rect.left : rect.left + 6;\n\n return (\n <>\n <div\n data-guardrail\n style={{\n ...S.markerOutline,\n top: rect.top - 2,\n left: rect.left - 2,\n width: rect.width + 4,\n height: height + 4,\n border: editing ? \"2px solid #2563eb\" : \"1px dashed #60a5fa\",\n }}\n />\n {!editing && (\n <button\n type=\"button\"\n data-guardrail\n style={{ ...S.markerHandle, top: handleTop, left: handleLeft }}\n aria-label=\"Edit image\"\n title=\"Edit image\"\n onClick={(e) => {\n // React bubbles synthetic events through portals to the field's\n // *React* ancestors — a field inside a <Link> would otherwise\n // navigate on this click, same reason the field's own onClick\n // claims its click.\n e.preventDefault();\n e.stopPropagation();\n onEdit();\n }}\n >\n <PencilIcon /> Edit\n </button>\n )}\n </>\n );\n}\n\nfunction Chrome({\n rect,\n isInline,\n row,\n error,\n saving,\n onChange,\n onPatch,\n onSave,\n onCancel,\n}: {\n rect: { top: number; left: number; bottom: number; width: number };\n isInline: boolean;\n row: OverrideRow;\n error: string | null;\n saving: boolean;\n onChange: (key: string, value: string) => void;\n onPatch: (patch: Partial<OverrideValue>) => void;\n onSave: () => void;\n onCancel: () => void;\n}) {\n const { storage } = useGRail();\n const [pickerOpen, setPickerOpen] = useState(false);\n const GAP = 8;\n // Flip above the element when there is no room below it.\n const below = rect.bottom + GAP;\n const flip = typeof window !== \"undefined\" && below > window.innerHeight - 120;\n\n const position: React.CSSProperties = {\n left: Math.max(8, rect.left),\n ...(flip ? { top: Math.max(8, rect.top - GAP - 44) } : { top: below }),\n };\n\n const actions = (\n <>\n <button\n style={S.iconBtnPrimary}\n onClick={onSave}\n disabled={saving}\n title=\"Save (Enter)\"\n aria-label=\"Save\"\n >\n {saving ? <SpinnerIcon /> : <CheckIcon />}\n </button>\n <button\n style={S.iconBtn}\n onClick={onCancel}\n disabled={saving}\n title=\"Cancel (Esc)\"\n aria-label=\"Cancel\"\n >\n <CloseIcon />\n </button>\n </>\n );\n\n // Text and buttons edit in place, so they only need the two controls.\n if (isInline) {\n return (\n <div data-guardrail style={{ ...S.anchoredBar, ...position }}>\n {actions}\n {error && <span style={{ ...S.error, margin: \"0 6px\" }}>{error}</span>}\n </div>\n );\n }\n\n // Images and links carry attributes with no on-page representation.\n return (\n <div\n data-guardrail\n style={{ ...S.anchoredPanel, ...position }}\n onClick={(e) => e.stopPropagation()}\n >\n {row.value.type === \"image\" && (\n <>\n {row.value.src && (\n // Plain img: previews must not depend on remotePatterns.\n <img src={row.value.src} alt=\"\" style={S.previewThumb} />\n )}\n\n {storage && (\n <button\n type=\"button\"\n style={S.secondaryBtn}\n onClick={() => setPickerOpen(true)}\n >\n {row.value.src ? \"Replace image\" : \"Choose image\"}\n </button>\n )}\n\n <label style={S.label}>Alt text</label>\n <input\n style={S.field}\n value={row.value.alt || \"\"}\n placeholder=\"Describe the image for screen readers\"\n onChange={(e) => onChange(\"alt\", e.target.value)}\n />\n\n {/* Only without storage: there is no picker then, so a URL is the one\n way to set an image. A typed URL has unknown proportions, so any\n stored dimensions are cleared. */}\n {!storage && (\n <>\n <label style={S.label}>Image URL</label>\n <input\n style={S.field}\n value={row.value.src || \"\"}\n onChange={(e) =>\n onPatch({ src: e.target.value, width: undefined, height: undefined })\n }\n />\n </>\n )}\n\n {pickerOpen && (\n <MediaPicker\n current={row.value.src}\n onClose={() => setPickerOpen(false)}\n onSelect={(image) =>\n onPatch({ src: image.src, width: image.width, height: image.height })\n }\n />\n )}\n </>\n )}\n\n {row.value.type === \"link\" && (\n <>\n <label style={S.label}>Link text</label>\n <input\n style={S.field}\n value={row.value.text || \"\"}\n onChange={(e) => onChange(\"text\", e.target.value)}\n />\n <label style={S.label}>URL</label>\n <input\n style={S.field}\n value={row.value.href || \"\"}\n onChange={(e) => onChange(\"href\", e.target.value)}\n />\n </>\n )}\n\n {error && <div style={{ ...S.error, marginBottom: 8 }}>{error}</div>}\n <div style={{ display: \"flex\", gap: 4, justifyContent: \"flex-end\" }}>\n {actions}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { HTMLKeys, OverrideRow } from '../../core/types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The editor, loaded on demand rather than imported statically.
|
|
6
|
+
*
|
|
7
|
+
* A static import puts the editing UI (popups, image picker, save-action
|
|
8
|
+
* wiring) into the JavaScript every visitor downloads: Next preloads every
|
|
9
|
+
* client module that a route's server graph imports, whether or not that page
|
|
10
|
+
* ever renders it. Confirmed by measurement — before this, an anonymous page
|
|
11
|
+
* loaded the editor chunk. Behind a dynamic import it is only fetched when an
|
|
12
|
+
* editor's <GRail> actually renders it, and it is still rendered on the
|
|
13
|
+
* server, so there is no flash.
|
|
14
|
+
*
|
|
15
|
+
* Shared by the server <GRail> and the client-component one, so the promise
|
|
16
|
+
* that anonymous visitors get no editor code holds for both.
|
|
17
|
+
*/
|
|
18
|
+
declare const GRailEditor: React.ComponentType<{
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
as?: HTMLKeys;
|
|
21
|
+
initialOverride: OverrideRow;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export { GRailEditor };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/editor.tsx"],"sourcesContent":["\"use client\";\n\nimport dynamic from \"next/dynamic\";\n\n/**\n * The editor, loaded on demand rather than imported statically.\n *\n * A static import puts the editing UI (popups, image picker, save-action\n * wiring) into the JavaScript every visitor downloads: Next preloads every\n * client module that a route's server graph imports, whether or not that page\n * ever renders it. Confirmed by measurement — before this, an anonymous page\n * loaded the editor chunk. Behind a dynamic import it is only fetched when an\n * editor's <GRail> actually renders it, and it is still rendered on the\n * server, so there is no flash.\n *\n * Shared by the server <GRail> and the client-component one, so the promise\n * that anonymous visitors get no editor code holds for both.\n */\nexport const GRailEditor = dynamic(() =>\n import(\"./client\").then((m) => m.GRailClient),\n);\n"],"mappings":";AAEA,OAAO,aAAa;AAgBb,MAAM,cAAc;AAAA,EAAQ,MACjC,OAAO,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW;AAC9C;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { GRailEditor } from "./editor";
|
|
4
4
|
import { GRailRender } from "./render";
|
|
5
5
|
import { fetchOrScaffold } from "../../server/data";
|
|
6
6
|
import { getCachedViewer } from "../../server/session";
|
|
@@ -9,7 +9,7 @@ async function GRail(props) {
|
|
|
9
9
|
const override = await fetchOrScaffold(id, props);
|
|
10
10
|
const viewer = await getCachedViewer();
|
|
11
11
|
if (editable && viewer && viewer.role) {
|
|
12
|
-
return /* @__PURE__ */ jsx(
|
|
12
|
+
return /* @__PURE__ */ jsx(GRailEditor, { ...rest, initialOverride: override });
|
|
13
13
|
}
|
|
14
14
|
return /* @__PURE__ */ jsx(GRailRender, { ...rest, override: override.value });
|
|
15
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/react/GRail/index.tsx"],"sourcesContent":["import React from \"react\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { GRailEditor } from \"./editor\";\nimport { GRailRender } from \"./render\";\nimport { fetchOrScaffold } from \"../../server/data\";\nimport { getCachedViewer } from \"../../server/session\";\nimport type { HTMLKeys, PolymorphicProps } from \"../../core/types\";\n\n/**\n * An editable field. Server Component by default: anonymous visitors get plain\n * server-rendered markup with no client JS. The editor only loads for a\n * verified session.\n */\nexport async function GRail<T extends HTMLKeys = \"span\">(\n props: PolymorphicProps<T>,\n) {\n const { id, editable = true, ...rest } = props;\n const override = await fetchOrScaffold(id, props as Record<string, unknown>);\n const viewer = await getCachedViewer();\n\n // Only members who can actually write get the editor, and only when this\n // field allows it — everyone else gets the same server-rendered markup an\n // anonymous visitor sees.\n if (editable && viewer && viewer.role) {\n return <GRailEditor {...(rest as object)} initialOverride={override} />;\n }\n\n return <GRailRender {...(rest as object)} override={override.value} />;\n}\n"],"mappings":"AAuBW;AAvBX,OAAO,WAAW;AAClB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAQhC,eAAsB,MACpB,OACA;AACA,QAAM,EAAE,IAAI,WAAW,MAAM,GAAG,KAAK,IAAI;AACzC,QAAM,WAAW,MAAM,gBAAgB,IAAI,KAAgC;AAC3E,QAAM,SAAS,MAAM,gBAAgB;AAKrC,MAAI,YAAY,UAAU,OAAO,MAAM;AACrC,WAAO,oBAAC,eAAa,GAAI,MAAiB,iBAAiB,UAAU;AAAA,EACvE;AAEA,SAAO,oBAAC,eAAa,GAAI,MAAiB,UAAU,SAAS,OAAO;AACtE;","names":[]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
2
|
+
import React__default from 'react';
|
|
3
3
|
import { HTMLKeys, OverrideValue } from '../../core/types.js';
|
|
4
4
|
|
|
5
5
|
interface RenderProps {
|
|
6
6
|
as?: HTMLKeys;
|
|
7
7
|
override: OverrideValue;
|
|
8
|
-
children?:
|
|
8
|
+
children?: React__default.ReactNode;
|
|
9
9
|
/** Forwarded to the rendered element so the editor can measure it. */
|
|
10
|
-
ref?:
|
|
10
|
+
ref?: React__default.Ref<HTMLElement>;
|
|
11
11
|
[key: string]: unknown;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { HTMLKeys, PolymorphicProps } from '../../core/types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* <GRail> as seen from a Client Component.
|
|
7
|
+
*
|
|
8
|
+
* `import { GRail } from "@guard-rail/core"` resolves here (via the
|
|
9
|
+
* "react-server" export condition) whenever the importing file is a Client
|
|
10
|
+
* Component, and to the async server version everywhere else — same name, same
|
|
11
|
+
* props, so a component moving between the two needs no code change.
|
|
12
|
+
*
|
|
13
|
+
* A Client Component can't await a query, so instead of fetching this reads its
|
|
14
|
+
* value from content the layout preloaded into context. That context is filled
|
|
15
|
+
* during the server render, so anonymous visitors still get real content in the
|
|
16
|
+
* initial HTML (this component is itself server-rendered for the first paint) —
|
|
17
|
+
* SEO is unaffected. An unedited field, or one outside the preload, renders the
|
|
18
|
+
* JSX that was written, exactly like the server version.
|
|
19
|
+
*/
|
|
20
|
+
declare function GRail<T extends HTMLKeys = "span">(props: PolymorphicProps<T>): react_jsx_runtime.JSX.Element;
|
|
21
|
+
|
|
22
|
+
export { GRail };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { GRailEditor } from "./editor";
|
|
5
|
+
import { GRailRender } from "./render";
|
|
6
|
+
import { useGRail, useGRailContent } from "../Provider";
|
|
7
|
+
import { generateScaffold } from "../../core/scaffold";
|
|
8
|
+
let warned = false;
|
|
9
|
+
function GRail(props) {
|
|
10
|
+
const { id, editable = true, ...rest } = props;
|
|
11
|
+
const { role } = useGRail();
|
|
12
|
+
const content = useGRailContent();
|
|
13
|
+
if (content === void 0 && process.env.NODE_ENV !== "production" && !warned) {
|
|
14
|
+
warned = true;
|
|
15
|
+
console.warn(
|
|
16
|
+
`Guardrail: <GRail id="${id}"> is inside a Client Component, but no content was preloaded for it, so it shows its JSX fallback. Preloading is off (prefetch: false) or this database adapter has no listOverrides().`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const stored = content?.values[id];
|
|
20
|
+
const override = stored ? { id: `content-${id}`, pathKey: id, value: stored } : generateScaffold(id, props);
|
|
21
|
+
if (editable && role) {
|
|
22
|
+
return /* @__PURE__ */ jsx(GRailEditor, { ...rest, initialOverride: override });
|
|
23
|
+
}
|
|
24
|
+
return /* @__PURE__ */ jsx(GRailRender, { ...rest, override: override.value });
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
GRail
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=universal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/react/GRail/universal.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport { GRailEditor } from \"./editor\";\nimport { GRailRender } from \"./render\";\nimport { useGRail, useGRailContent } from \"../Provider\";\nimport { generateScaffold } from \"../../core/scaffold\";\nimport type { HTMLKeys, OverrideRow, PolymorphicProps } from \"../../core/types\";\n\nlet warned = false;\n\n/**\n * <GRail> as seen from a Client Component.\n *\n * `import { GRail } from \"@guard-rail/core\"` resolves here (via the\n * \"react-server\" export condition) whenever the importing file is a Client\n * Component, and to the async server version everywhere else — same name, same\n * props, so a component moving between the two needs no code change.\n *\n * A Client Component can't await a query, so instead of fetching this reads its\n * value from content the layout preloaded into context. That context is filled\n * during the server render, so anonymous visitors still get real content in the\n * initial HTML (this component is itself server-rendered for the first paint) —\n * SEO is unaffected. An unedited field, or one outside the preload, renders the\n * JSX that was written, exactly like the server version.\n */\nexport function GRail<T extends HTMLKeys = \"span\">(props: PolymorphicProps<T>) {\n const { id, editable = true, ...rest } = props;\n const { role } = useGRail();\n const content = useGRailContent();\n\n if (content === undefined && process.env.NODE_ENV !== \"production\" && !warned) {\n warned = true;\n console.warn(\n `Guardrail: <GRail id=\"${id}\"> is inside a Client Component, but no content was ` +\n `preloaded for it, so it shows its JSX fallback. Preloading is off ` +\n `(prefetch: false) or this database adapter has no listOverrides().`,\n );\n }\n\n const stored = content?.values[id];\n const override: OverrideRow = stored\n ? { id: `content-${id}`, pathKey: id, value: stored }\n : generateScaffold(id, props as Record<string, unknown>);\n\n // Same gate as the server version: only a member who can write gets the\n // editor, and only when this field allows it.\n if (editable && role) {\n return <GRailEditor {...(rest as object)} initialOverride={override} />;\n }\n return <GRailRender {...(rest as object)} override={override.value} />;\n}\n"],"mappings":";AAgDW;AA9CX,OAAO,WAAW;AAClB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,UAAU,uBAAuB;AAC1C,SAAS,wBAAwB;AAGjC,IAAI,SAAS;AAiBN,SAAS,MAAmC,OAA4B;AAC7E,QAAM,EAAE,IAAI,WAAW,MAAM,GAAG,KAAK,IAAI;AACzC,QAAM,EAAE,KAAK,IAAI,SAAS;AAC1B,QAAM,UAAU,gBAAgB;AAEhC,MAAI,YAAY,UAAa,QAAQ,IAAI,aAAa,gBAAgB,CAAC,QAAQ;AAC7E,aAAS;AACT,YAAQ;AAAA,MACN,yBAAyB,EAAE;AAAA,IAG7B;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,OAAO,EAAE;AACjC,QAAM,WAAwB,SAC1B,EAAE,IAAI,WAAW,EAAE,IAAI,SAAS,IAAI,OAAO,OAAO,IAClD,iBAAiB,IAAI,KAAgC;AAIzD,MAAI,YAAY,MAAM;AACpB,WAAO,oBAAC,eAAa,GAAI,MAAiB,iBAAiB,UAAU;AAAA,EACvE;AACA,SAAO,oBAAC,eAAa,GAAI,MAAiB,UAAU,SAAS,OAAO;AACtE;","names":[]}
|
package/dist/react/Icons.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
|
|
3
3
|
declare const CheckIcon: () => react_jsx_runtime.JSX.Element;
|
|
4
4
|
declare const CloseIcon: () => react_jsx_runtime.JSX.Element;
|
|
5
|
+
declare const PencilIcon: () => react_jsx_runtime.JSX.Element;
|
|
5
6
|
declare const PeopleIcon: () => react_jsx_runtime.JSX.Element;
|
|
6
7
|
declare const PlusIcon: () => react_jsx_runtime.JSX.Element;
|
|
7
8
|
declare const TrashIcon: () => react_jsx_runtime.JSX.Element;
|
|
@@ -10,4 +11,4 @@ declare const ArrowDownIcon: () => react_jsx_runtime.JSX.Element;
|
|
|
10
11
|
declare const LogOutIcon: () => react_jsx_runtime.JSX.Element;
|
|
11
12
|
declare const SpinnerIcon: () => react_jsx_runtime.JSX.Element;
|
|
12
13
|
|
|
13
|
-
export { ArrowDownIcon, ArrowUpIcon, CheckIcon, CloseIcon, LogOutIcon, PeopleIcon, PlusIcon, SpinnerIcon, TrashIcon };
|
|
14
|
+
export { ArrowDownIcon, ArrowUpIcon, CheckIcon, CloseIcon, LogOutIcon, PencilIcon, PeopleIcon, PlusIcon, SpinnerIcon, TrashIcon };
|
package/dist/react/Icons.js
CHANGED
|
@@ -15,6 +15,10 @@ const CloseIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, "aria-hidden": "t
|
|
|
15
15
|
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
16
16
|
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
17
17
|
] });
|
|
18
|
+
const PencilIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, strokeWidth: 2, "aria-hidden": "true", children: [
|
|
19
|
+
/* @__PURE__ */ jsx("path", { d: "M12 20h9" }),
|
|
20
|
+
/* @__PURE__ */ jsx("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })
|
|
21
|
+
] });
|
|
18
22
|
const PeopleIcon = () => /* @__PURE__ */ jsxs("svg", { ...base, strokeWidth: 2, "aria-hidden": "true", children: [
|
|
19
23
|
/* @__PURE__ */ jsx("path", { d: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
|
|
20
24
|
/* @__PURE__ */ jsx("circle", { cx: "9", cy: "7", r: "4" }),
|
|
@@ -60,6 +64,7 @@ export {
|
|
|
60
64
|
CheckIcon,
|
|
61
65
|
CloseIcon,
|
|
62
66
|
LogOutIcon,
|
|
67
|
+
PencilIcon,
|
|
63
68
|
PeopleIcon,
|
|
64
69
|
PlusIcon,
|
|
65
70
|
SpinnerIcon,
|
package/dist/react/Icons.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/Icons.tsx"],"sourcesContent":["import React from \"react\";\n\n/**\n * Inline SVG rather than an icon package: Guardrail's chrome should not add a\n * runtime dependency to every consuming app.\n */\nconst base = {\n width: 14,\n height: 14,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 3,\n strokeLinecap: \"round\" as const,\n strokeLinejoin: \"round\" as const,\n};\n\nexport const CheckIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n);\n\nexport const CloseIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n);\n\nexport const PeopleIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M23 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n);\n\nexport const PlusIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n);\n\nexport const TrashIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <polyline points=\"3 6 5 6 21 6\" />\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" />\n <path d=\"M10 11v6M14 11v6M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\" />\n </svg>\n);\n\nexport const ArrowUpIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\" />\n <polyline points=\"6 11 12 5 18 11\" />\n </svg>\n);\n\nexport const ArrowDownIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <polyline points=\"18 13 12 19 6 13\" />\n </svg>\n);\n\nexport const LogOutIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\" />\n </svg>\n);\n\nexport const SpinnerIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M21 12a9 9 0 1 1-6.2-8.6\">\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from=\"0 12 12\"\n to=\"360 12 12\"\n dur=\"0.7s\"\n repeatCount=\"indefinite\"\n />\n </path>\n </svg>\n);\n"],"mappings":"AAmBI,cAKF,YALE;AAnBJ,OAAO,WAAW;AAMlB,MAAM,OAAO;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,gBAAgB;AAClB;AAEO,MAAM,YAAY,MACvB,oBAAC,SAAK,GAAG,MAAM,eAAY,QACzB,8BAAC,cAAS,QAAO,kBAAiB,GACpC;AAGK,MAAM,YAAY,MACvB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK;AAAA,EACpC,oBAAC,UAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,GACtC;AAGK,MAAM,aAAa,MACxB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,UAAK,GAAE,6CAA4C;AAAA,EACpD,oBAAC,YAAO,IAAG,KAAI,IAAG,KAAI,GAAE,KAAI;AAAA,EAC5B,oBAAC,UAAK,GAAE,8BAA6B;AAAA,EACrC,oBAAC,UAAK,GAAE,6BAA4B;AAAA,GACtC;AAGK,MAAM,WAAW,MACtB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,EACrC,oBAAC,UAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,GACvC;AAGK,MAAM,YAAY,MACvB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,cAAS,QAAO,gBAAe;AAAA,EAChC,oBAAC,UAAK,GAAE,iDAAgD;AAAA,EACxD,oBAAC,UAAK,GAAE,0DAAyD;AAAA,GACnE;AAGK,MAAM,cAAc,MACzB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI;AAAA,EACrC,oBAAC,cAAS,QAAO,mBAAkB;AAAA,GACrC;AAGK,MAAM,gBAAgB,MAC3B,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,EACrC,oBAAC,cAAS,QAAO,oBAAmB;AAAA,GACtC;AAGK,MAAM,aAAa,MACxB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,UAAK,GAAE,2CAA0C;AAAA,EAClD,oBAAC,cAAS,QAAO,oBAAmB;AAAA,EACpC,oBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK;AAAA,GACvC;AAGK,MAAM,cAAc,MACzB,oBAAC,SAAK,GAAG,MAAM,eAAY,QACzB,8BAAC,UAAK,GAAE,4BACN;AAAA,EAAC;AAAA;AAAA,IACC,eAAc;AAAA,IACd,MAAK;AAAA,IACL,MAAK;AAAA,IACL,IAAG;AAAA,IACH,KAAI;AAAA,IACJ,aAAY;AAAA;AACd,GACF,GACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/Icons.tsx"],"sourcesContent":["import React from \"react\";\n\n/**\n * Inline SVG rather than an icon package: Guardrail's chrome should not add a\n * runtime dependency to every consuming app.\n */\nconst base = {\n width: 14,\n height: 14,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 3,\n strokeLinecap: \"round\" as const,\n strokeLinejoin: \"round\" as const,\n};\n\nexport const CheckIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n);\n\nexport const CloseIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n);\n\nexport const PencilIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M12 20h9\" />\n <path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\" />\n </svg>\n);\n\nexport const PeopleIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\" />\n <circle cx=\"9\" cy=\"7\" r=\"4\" />\n <path d=\"M23 21v-2a4 4 0 0 0-3-3.87\" />\n <path d=\"M16 3.13a4 4 0 0 1 0 7.75\" />\n </svg>\n);\n\nexport const PlusIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n);\n\nexport const TrashIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <polyline points=\"3 6 5 6 21 6\" />\n <path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" />\n <path d=\"M10 11v6M14 11v6M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\" />\n </svg>\n);\n\nexport const ArrowUpIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"5\" />\n <polyline points=\"6 11 12 5 18 11\" />\n </svg>\n);\n\nexport const ArrowDownIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\" />\n <polyline points=\"18 13 12 19 6 13\" />\n </svg>\n);\n\nexport const LogOutIcon = () => (\n <svg {...base} strokeWidth={2} aria-hidden=\"true\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\" />\n <polyline points=\"16 17 21 12 16 7\" />\n <line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\" />\n </svg>\n);\n\nexport const SpinnerIcon = () => (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M21 12a9 9 0 1 1-6.2-8.6\">\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from=\"0 12 12\"\n to=\"360 12 12\"\n dur=\"0.7s\"\n repeatCount=\"indefinite\"\n />\n </path>\n </svg>\n);\n"],"mappings":"AAmBI,cAKF,YALE;AAnBJ,OAAO,WAAW;AAMlB,MAAM,OAAO;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,gBAAgB;AAClB;AAEO,MAAM,YAAY,MACvB,oBAAC,SAAK,GAAG,MAAM,eAAY,QACzB,8BAAC,cAAS,QAAO,kBAAiB,GACpC;AAGK,MAAM,YAAY,MACvB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK;AAAA,EACpC,oBAAC,UAAK,IAAG,KAAI,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,GACtC;AAGK,MAAM,aAAa,MACxB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,UAAK,GAAE,YAAW;AAAA,EACnB,oBAAC,UAAK,GAAE,2DAA0D;AAAA,GACpE;AAGK,MAAM,aAAa,MACxB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,UAAK,GAAE,6CAA4C;AAAA,EACpD,oBAAC,YAAO,IAAG,KAAI,IAAG,KAAI,GAAE,KAAI;AAAA,EAC5B,oBAAC,UAAK,GAAE,8BAA6B;AAAA,EACrC,oBAAC,UAAK,GAAE,6BAA4B;AAAA,GACtC;AAGK,MAAM,WAAW,MACtB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,EACrC,oBAAC,UAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK;AAAA,GACvC;AAGK,MAAM,YAAY,MACvB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,cAAS,QAAO,gBAAe;AAAA,EAChC,oBAAC,UAAK,GAAE,iDAAgD;AAAA,EACxD,oBAAC,UAAK,GAAE,0DAAyD;AAAA,GACnE;AAGK,MAAM,cAAc,MACzB,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI;AAAA,EACrC,oBAAC,cAAS,QAAO,mBAAkB;AAAA,GACrC;AAGK,MAAM,gBAAgB,MAC3B,qBAAC,SAAK,GAAG,MAAM,eAAY,QACzB;AAAA,sBAAC,UAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK,IAAG,MAAK;AAAA,EACrC,oBAAC,cAAS,QAAO,oBAAmB;AAAA,GACtC;AAGK,MAAM,aAAa,MACxB,qBAAC,SAAK,GAAG,MAAM,aAAa,GAAG,eAAY,QACzC;AAAA,sBAAC,UAAK,GAAE,2CAA0C;AAAA,EAClD,oBAAC,cAAS,QAAO,oBAAmB;AAAA,EACpC,oBAAC,UAAK,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI,IAAG,MAAK;AAAA,GACvC;AAGK,MAAM,cAAc,MACzB,oBAAC,SAAK,GAAG,MAAM,eAAY,QACzB,8BAAC,UAAK,GAAE,4BACN;AAAA,EAAC;AAAA;AAAA,IACC,eAAc;AAAA,IACd,MAAK;AAAA,IACL,MAAK;AAAA,IACL,IAAG;AAAA,IACH,KAAI;AAAA,IACJ,aAAY;AAAA;AACd,GACF,GACF;","names":[]}
|
|
@@ -3,7 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { createAssetUpload, listAssets } from "../server/actions";
|
|
5
5
|
import { CloseIcon, SpinnerIcon } from "./Icons";
|
|
6
|
-
import { S } from "./styles";
|
|
6
|
+
import { S, v } from "./styles";
|
|
7
7
|
import { prepareImage } from "./resizeImage";
|
|
8
8
|
function measure(url) {
|
|
9
9
|
return new Promise((resolve) => {
|
|
@@ -96,7 +96,7 @@ function MediaPicker({
|
|
|
96
96
|
/* @__PURE__ */ jsx(
|
|
97
97
|
"button",
|
|
98
98
|
{
|
|
99
|
-
style: { ...S.iconBtn, color: "
|
|
99
|
+
style: { ...S.iconBtn, color: v("muted") },
|
|
100
100
|
onClick: onClose,
|
|
101
101
|
"aria-label": "Close",
|
|
102
102
|
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
@@ -146,7 +146,7 @@ function MediaPicker({
|
|
|
146
146
|
}
|
|
147
147
|
),
|
|
148
148
|
error && /* @__PURE__ */ jsx("div", { style: { ...S.error, marginBottom: 12 }, children: error }),
|
|
149
|
-
assets === null ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: "
|
|
149
|
+
assets === null ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: v("muted") }, children: "Loading\u2026" }) : assets.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: v("muted") }, children: "No images uploaded yet." }) : /* @__PURE__ */ jsx("div", { style: S.pickerGrid, children: assets.map((asset) => /* @__PURE__ */ jsx(
|
|
150
150
|
"button",
|
|
151
151
|
{
|
|
152
152
|
type: "button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createAssetUpload, listAssets } from \"../server/actions\";\nimport { CloseIcon, SpinnerIcon } from \"./Icons\";\nimport { S } from \"./styles\";\nimport { prepareImage } from \"./resizeImage\";\nimport type { GRAsset } from \"../core/types\";\n\nexport interface PickedImage {\n src: string;\n width?: number;\n height?: number;\n}\n\ntype Size = { width?: number; height?: number };\n\n/**\n * Natural size is read in the browser, so no dimensions need storing on the\n * storage side and every adapter gets them for free.\n */\nfunction measure(url: string): Promise<Size> {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = () => resolve({});\n img.src = url;\n });\n}\n\nexport function MediaPicker({\n current,\n onSelect,\n onClose,\n}: {\n current?: string;\n onSelect: (image: PickedImage) => void;\n onClose: () => void;\n}) {\n const [assets, setAssets] = useState<GRAsset[] | null>(null);\n const [uploading, setUploading] = useState(false);\n const [dragging, setDragging] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n void listAssets().then(setAssets);\n }, []);\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n e.stopPropagation();\n onClose();\n };\n window.addEventListener(\"keydown\", onKey, true);\n return () => window.removeEventListener(\"keydown\", onKey, true);\n }, [onClose]);\n\n const choose = useCallback(\n (src: string, size: Size) => {\n onSelect({ src, ...size });\n onClose();\n },\n [onSelect, onClose],\n );\n\n const upload = async (file: File) => {\n if (uploading) return;\n setError(null);\n setUploading(true);\n try {\n // Scaled first, so the size check and the upload both see the file\n // that will actually be stored — an oversized phone photo that shrinks\n // under the cap is accepted.\n const prepared = await prepareImage(file);\n const payload = prepared.file;\n\n const res = await createAssetUpload({\n name: payload.name,\n contentType: payload.type,\n size: payload.size,\n });\n if (!res.success) {\n setError(res.error);\n return;\n }\n const { target } = res;\n\n // Straight to storage — the bytes never touch the Next.js server.\n const put = await fetch(target.url, {\n method: target.method,\n headers: target.headers,\n body: payload,\n });\n if (!put.ok) {\n setError(`Upload failed (${put.status}).`);\n return;\n }\n\n setAssets((prev) => [target.asset, ...(prev ?? [])]);\n choose(target.asset.url, { width: prepared.width, height: prepared.height });\n } catch {\n setError(\"Upload failed. Check your connection and try again.\");\n } finally {\n setUploading(false);\n }\n };\n\n const onFiles = (files: FileList | null | undefined) => {\n const file = files?.[0];\n if (file) void upload(file);\n };\n\n return (\n <>\n <div data-guardrail style={S.pickerScrim} onClick={onClose} />\n <div\n data-guardrail\n role=\"dialog\"\n aria-label=\"Choose an image\"\n style={S.picker}\n onClick={(e) => e.stopPropagation()}\n >\n <header style={S.drawerHead}>\n <h2 style={S.drawerTitle}>Choose an image</h2>\n <button\n style={{ ...S.iconBtn, color: \"#6b7280\" }}\n onClick={onClose}\n aria-label=\"Close\"\n >\n <CloseIcon />\n </button>\n </header>\n\n <div style={S.drawerBody}>\n <div\n role=\"button\"\n tabIndex={0}\n style={{ ...S.dropzone, ...(dragging ? S.dropzoneActive : {}) }}\n onClick={() => !uploading && inputRef.current?.click()}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") inputRef.current?.click();\n }}\n onDragOver={(e) => {\n e.preventDefault();\n setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragging(false);\n onFiles(e.dataTransfer.files);\n }}\n >\n {uploading ? (\n <span style={{ display: \"inline-flex\", gap: 8, alignItems: \"center\" }}>\n <SpinnerIcon /> Uploading…\n </span>\n ) : (\n \"Drop an image here, or click to upload\"\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp,image/gif,image/avif\"\n hidden\n onChange={(e) => {\n onFiles(e.target.files);\n e.target.value = \"\";\n }}\n />\n </div>\n\n {error && <div style={{ ...S.error, marginBottom: 12 }}>{error}</div>}\n\n {assets === null ? (\n <p style={{ fontSize: 13, color: \"#6b7280\" }}>Loading…</p>\n ) : assets.length === 0 ? (\n <p style={{ fontSize: 13, color: \"#6b7280\" }}>No images uploaded yet.</p>\n ) : (\n <div style={S.pickerGrid}>\n {assets.map((asset) => (\n <button\n key={asset.id}\n type=\"button\"\n title={asset.name}\n style={{\n ...S.pickerTile,\n ...(asset.url === current ? S.pickerTileActive : {}),\n }}\n onClick={async () => choose(asset.url, await measure(asset.url))}\n >\n {/* Plain img: the admin UI must not depend on next/image's\n remotePatterns being configured. */}\n <img src={asset.url} alt=\"\" loading=\"lazy\" style={S.pickerThumb} />\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createAssetUpload, listAssets } from \"../server/actions\";\nimport { CloseIcon, SpinnerIcon } from \"./Icons\";\nimport { S, v } from \"./styles\";\nimport { prepareImage } from \"./resizeImage\";\nimport type { GRAsset } from \"../core/types\";\n\nexport interface PickedImage {\n src: string;\n width?: number;\n height?: number;\n}\n\ntype Size = { width?: number; height?: number };\n\n/**\n * Natural size is read in the browser, so no dimensions need storing on the\n * storage side and every adapter gets them for free.\n */\nfunction measure(url: string): Promise<Size> {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });\n img.onerror = () => resolve({});\n img.src = url;\n });\n}\n\nexport function MediaPicker({\n current,\n onSelect,\n onClose,\n}: {\n current?: string;\n onSelect: (image: PickedImage) => void;\n onClose: () => void;\n}) {\n const [assets, setAssets] = useState<GRAsset[] | null>(null);\n const [uploading, setUploading] = useState(false);\n const [dragging, setDragging] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n void listAssets().then(setAssets);\n }, []);\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n e.stopPropagation();\n onClose();\n };\n window.addEventListener(\"keydown\", onKey, true);\n return () => window.removeEventListener(\"keydown\", onKey, true);\n }, [onClose]);\n\n const choose = useCallback(\n (src: string, size: Size) => {\n onSelect({ src, ...size });\n onClose();\n },\n [onSelect, onClose],\n );\n\n const upload = async (file: File) => {\n if (uploading) return;\n setError(null);\n setUploading(true);\n try {\n // Scaled first, so the size check and the upload both see the file\n // that will actually be stored — an oversized phone photo that shrinks\n // under the cap is accepted.\n const prepared = await prepareImage(file);\n const payload = prepared.file;\n\n const res = await createAssetUpload({\n name: payload.name,\n contentType: payload.type,\n size: payload.size,\n });\n if (!res.success) {\n setError(res.error);\n return;\n }\n const { target } = res;\n\n // Straight to storage — the bytes never touch the Next.js server.\n const put = await fetch(target.url, {\n method: target.method,\n headers: target.headers,\n body: payload,\n });\n if (!put.ok) {\n setError(`Upload failed (${put.status}).`);\n return;\n }\n\n setAssets((prev) => [target.asset, ...(prev ?? [])]);\n choose(target.asset.url, { width: prepared.width, height: prepared.height });\n } catch {\n setError(\"Upload failed. Check your connection and try again.\");\n } finally {\n setUploading(false);\n }\n };\n\n const onFiles = (files: FileList | null | undefined) => {\n const file = files?.[0];\n if (file) void upload(file);\n };\n\n return (\n <>\n <div data-guardrail style={S.pickerScrim} onClick={onClose} />\n <div\n data-guardrail\n role=\"dialog\"\n aria-label=\"Choose an image\"\n style={S.picker}\n onClick={(e) => e.stopPropagation()}\n >\n <header style={S.drawerHead}>\n <h2 style={S.drawerTitle}>Choose an image</h2>\n <button\n style={{ ...S.iconBtn, color: v(\"muted\") }}\n onClick={onClose}\n aria-label=\"Close\"\n >\n <CloseIcon />\n </button>\n </header>\n\n <div style={S.drawerBody}>\n <div\n role=\"button\"\n tabIndex={0}\n style={{ ...S.dropzone, ...(dragging ? S.dropzoneActive : {}) }}\n onClick={() => !uploading && inputRef.current?.click()}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") inputRef.current?.click();\n }}\n onDragOver={(e) => {\n e.preventDefault();\n setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDragging(false);\n onFiles(e.dataTransfer.files);\n }}\n >\n {uploading ? (\n <span style={{ display: \"inline-flex\", gap: 8, alignItems: \"center\" }}>\n <SpinnerIcon /> Uploading…\n </span>\n ) : (\n \"Drop an image here, or click to upload\"\n )}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp,image/gif,image/avif\"\n hidden\n onChange={(e) => {\n onFiles(e.target.files);\n e.target.value = \"\";\n }}\n />\n </div>\n\n {error && <div style={{ ...S.error, marginBottom: 12 }}>{error}</div>}\n\n {assets === null ? (\n <p style={{ fontSize: 13, color: v(\"muted\") }}>Loading…</p>\n ) : assets.length === 0 ? (\n <p style={{ fontSize: 13, color: v(\"muted\") }}>No images uploaded yet.</p>\n ) : (\n <div style={S.pickerGrid}>\n {assets.map((asset) => (\n <button\n key={asset.id}\n type=\"button\"\n title={asset.name}\n style={{\n ...S.pickerTile,\n ...(asset.url === current ? S.pickerTileActive : {}),\n }}\n onClick={async () => choose(asset.url, await measure(asset.url))}\n >\n {/* Plain img: the admin UI must not depend on next/image's\n remotePatterns being configured. */}\n <img src={asset.url} alt=\"\" loading=\"lazy\" style={S.pickerThumb} />\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/react/Provider.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
3
|
-
import { GRState } from '../core/types.js';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
import { GRState, GRContent } from '../core/types.js';
|
|
4
4
|
|
|
5
|
-
declare function GRailProvider({ children, state, }: {
|
|
6
|
-
children:
|
|
5
|
+
declare function GRailProvider({ children, state, content, }: {
|
|
6
|
+
children: React__default.ReactNode;
|
|
7
7
|
state: GRState;
|
|
8
|
+
/** Preloaded field values for <GRail>s inside Client Components. */
|
|
9
|
+
content?: GRContent;
|
|
8
10
|
}): react_jsx_runtime.JSX.Element;
|
|
11
|
+
/** Internal — the client-component <GRail> reads its preloaded value from here. */
|
|
12
|
+
declare function useGRailContent(): GRContent | undefined;
|
|
9
13
|
declare function useGRail(): GRState;
|
|
10
14
|
|
|
11
|
-
export { GRailProvider, useGRail };
|
|
15
|
+
export { GRailProvider, useGRail, useGRailContent };
|
package/dist/react/Provider.js
CHANGED
|
@@ -3,14 +3,19 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React, { createContext, useContext, Suspense } from "react";
|
|
4
4
|
import { EditSyncer } from "./Syncer";
|
|
5
5
|
const GRContext = createContext(void 0);
|
|
6
|
+
const GRContentContext = createContext(void 0);
|
|
6
7
|
function GRailProvider({
|
|
7
8
|
children,
|
|
8
|
-
state
|
|
9
|
+
state,
|
|
10
|
+
content
|
|
9
11
|
}) {
|
|
10
|
-
return /* @__PURE__ */
|
|
12
|
+
return /* @__PURE__ */ jsx(GRContext.Provider, { value: state, children: /* @__PURE__ */ jsxs(GRContentContext.Provider, { value: content, children: [
|
|
11
13
|
/* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(EditSyncer, {}) }),
|
|
12
14
|
children
|
|
13
|
-
] });
|
|
15
|
+
] }) });
|
|
16
|
+
}
|
|
17
|
+
function useGRailContent() {
|
|
18
|
+
return useContext(GRContentContext);
|
|
14
19
|
}
|
|
15
20
|
function useGRail() {
|
|
16
21
|
const context = useContext(GRContext);
|
|
@@ -21,6 +26,7 @@ function useGRail() {
|
|
|
21
26
|
}
|
|
22
27
|
export {
|
|
23
28
|
GRailProvider,
|
|
24
|
-
useGRail
|
|
29
|
+
useGRail,
|
|
30
|
+
useGRailContent
|
|
25
31
|
};
|
|
26
32
|
//# sourceMappingURL=Provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/Provider.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext, Suspense } from \"react\";\nimport type { GRState } from \"../core/types\";\nimport { EditSyncer } from \"./Syncer\";\n\nconst GRContext = createContext<GRState | undefined>(undefined);\n\nexport function GRailProvider({\n children,\n state,\n}: {\n children: React.ReactNode;\n state: GRState;\n}) {\n // Deliberately not held in useState. The server is the source of truth here,\n // and useState ignores prop changes after mount — so after signing in,\n // router.refresh() would deliver a fresh state that the provider discarded,\n // leaving the login bar on screen until a full page reload.\n return (\n <GRContext.Provider value={state}>\n {/* EditSyncer reads searchParams, which suspends during static render. */}\n
|
|
1
|
+
{"version":3,"sources":["../../src/react/Provider.tsx"],"sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext, Suspense } from \"react\";\nimport type { GRContent, GRState } from \"../core/types\";\nimport { EditSyncer } from \"./Syncer\";\n\nconst GRContext = createContext<GRState | undefined>(undefined);\nconst GRContentContext = createContext<GRContent | undefined>(undefined);\n\nexport function GRailProvider({\n children,\n state,\n content,\n}: {\n children: React.ReactNode;\n state: GRState;\n /** Preloaded field values for <GRail>s inside Client Components. */\n content?: GRContent;\n}) {\n // Deliberately not held in useState. The server is the source of truth here,\n // and useState ignores prop changes after mount — so after signing in,\n // router.refresh() would deliver a fresh state that the provider discarded,\n // leaving the login bar on screen until a full page reload.\n return (\n <GRContext.Provider value={state}>\n <GRContentContext.Provider value={content}>\n {/* EditSyncer reads searchParams, which suspends during static render. */}\n <Suspense fallback={null}>\n <EditSyncer />\n </Suspense>\n {children}\n </GRContentContext.Provider>\n </GRContext.Provider>\n );\n}\n\n/** Internal — the client-component <GRail> reads its preloaded value from here. */\nexport function useGRailContent(): GRContent | undefined {\n return useContext(GRContentContext);\n}\n\nexport function useGRail(): GRState {\n const context = useContext(GRContext);\n if (!context) {\n throw new Error(\"useGRail must be used inside withGuardrail()\");\n }\n return context;\n}\n"],"mappings":";AAyBM,SAGI,KAHJ;AAvBN,OAAO,SAAS,eAAe,YAAY,gBAAgB;AAE3D,SAAS,kBAAkB;AAE3B,MAAM,YAAY,cAAmC,MAAS;AAC9D,MAAM,mBAAmB,cAAqC,MAAS;AAEhE,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AACF,GAKG;AAKD,SACE,oBAAC,UAAU,UAAV,EAAmB,OAAO,OACzB,+BAAC,iBAAiB,UAAjB,EAA0B,OAAO,SAEhC;AAAA,wBAAC,YAAS,UAAU,MAClB,8BAAC,cAAW,GACd;AAAA,IACC;AAAA,KACH,GACF;AAEJ;AAGO,SAAS,kBAAyC;AACvD,SAAO,WAAW,gBAAgB;AACpC;AAEO,SAAS,WAAoB;AAClC,QAAM,UAAU,WAAW,SAAS;AACpC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;","names":[]}
|