@sandboxsonny/ace-kit 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/astro/AceLink.astro +20 -0
- package/dist/astro.d.ts +1 -1
- package/dist/{chunk-KEV3YAWN.js → chunk-2MXND4HG.js} +10 -4
- package/dist/{chunk-URLKW2YS.js → chunk-FVR55GIU.js} +5 -1
- package/dist/cli.js +4 -0
- package/dist/index.d.ts +31 -2
- package/dist/index.js +19 -1
- package/dist/react-router-server.d.ts +1 -1
- package/dist/react-router.d.ts +20 -1
- package/dist/react-router.js +15 -7
- package/dist/react.d.ts +23 -1
- package/dist/react.js +2 -2
- package/docs/api-reference.md +15 -8
- package/docs/astro.md +3 -0
- package/docs/react-router.md +1 -0
- package/docs/react.md +13 -1
- package/docs/typed-content.md +11 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @sandboxsonny/ace-kit
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 801266d: Support for the CMS's new Date and Link field types.
|
|
8
|
+
|
|
9
|
+
- `ace-kit generate` types a Date field as `string` (a day written `YYYY-MM-DD`) and a Link field as the new `AceLinkData` (`{ href, label? }`). Before, both were `unknown`.
|
|
10
|
+
- `AceLink` draws a link field: `AceLink.astro` for Astro, `AceLink` from `@sandboxsonny/ace-kit/react`, and a version from `@sandboxsonny/ace-kit/react-router` that uses the router's `Link` for pages of the site. It shows the editor's words, or yours as children, and draws nothing for an address that can't be trusted.
|
|
11
|
+
- `linkAttributes(link)` is what those components use, for sites that draw links their own way.
|
|
12
|
+
- `formatAceDate(date, format?)` turns a Date field's day into words in any language, and never shifts the day with the server's time zone.
|
|
13
|
+
|
|
14
|
+
Needs a CMS Ace API with the Date and Link field types (the API's migration 00018). Against an older API nothing changes.
|
|
15
|
+
|
|
3
16
|
## 0.1.2
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { linkAttributes } from "../dist/index.js";
|
|
3
|
+
import type { AceLinkData } from "../dist/index.js";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
/** A link field's value: `entry.values.button`. */
|
|
7
|
+
link: AceLinkData;
|
|
8
|
+
class?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const { link, class: className } = Astro.props;
|
|
12
|
+
const attributes = linkAttributes(link);
|
|
13
|
+
const hasContent = Astro.slots.has("default");
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
{attributes && (
|
|
17
|
+
<a href={attributes.href} class={className}>
|
|
18
|
+
{hasContent ? <slot /> : (attributes.label ?? attributes.href)}
|
|
19
|
+
</a>
|
|
20
|
+
)}
|
package/dist/astro.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type AceFieldSchema = {
|
|
3
3
|
handle: string;
|
|
4
4
|
label: string;
|
|
5
|
-
/** `text`, `textarea`, `select`, `number`, `checkbox`, `image` or `
|
|
5
|
+
/** `text`, `textarea`, `select`, `number`, `checkbox`, `image`, `richtext`, `date` or `link`. */
|
|
6
6
|
component_key: string;
|
|
7
7
|
config: { required?: boolean; options?: string[] } | null;
|
|
8
8
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { imageAttributes, textAreaSingleLine, textAreaParagraphs, textAreaLines, richTextImageSizes, listStart, headingLevel, linkAddress } from './chunk-
|
|
1
|
+
import { imageAttributes, linkAttributes, textAreaSingleLine, textAreaParagraphs, textAreaLines, richTextImageSizes, listStart, headingLevel, linkAddress } from './chunk-FVR55GIU.js';
|
|
2
2
|
import { listenForPreviewRefresh } from './chunk-YN7LJWGI.js';
|
|
3
3
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
import { Fragment as Fragment$1, useEffect } from 'react';
|
|
@@ -7,7 +7,13 @@ function AceImage({ image, sizes, lazy, ...rest }) {
|
|
|
7
7
|
const attributes = imageAttributes(image, { sizes, lazy });
|
|
8
8
|
return /* @__PURE__ */ jsx("img", { ...rest, ...attributes });
|
|
9
9
|
}
|
|
10
|
-
var PlainLink = ({ href, children }) => /* @__PURE__ */ jsx("a", { href, children });
|
|
10
|
+
var PlainLink = ({ href, children, className }) => /* @__PURE__ */ jsx("a", { href, className, children });
|
|
11
|
+
function AceLink({ link, children, className, as: Link = PlainLink }) {
|
|
12
|
+
const attributes = linkAttributes(link);
|
|
13
|
+
if (!attributes) return null;
|
|
14
|
+
return /* @__PURE__ */ jsx(Link, { href: attributes.href, className, children: children ?? attributes.label ?? attributes.href });
|
|
15
|
+
}
|
|
16
|
+
var PlainLink2 = ({ href, children }) => /* @__PURE__ */ jsx("a", { href, children });
|
|
11
17
|
function renderText(node, Link) {
|
|
12
18
|
return (node.marks ?? []).reduce((inner, mark) => {
|
|
13
19
|
if (mark.type === "bold") return /* @__PURE__ */ jsx("strong", { children: inner });
|
|
@@ -46,7 +52,7 @@ function Node({ node, Link }) {
|
|
|
46
52
|
return null;
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
|
-
function AceRichText({ document, link =
|
|
55
|
+
function AceRichText({ document, link = PlainLink2 }) {
|
|
50
56
|
return /* @__PURE__ */ jsx(Fragment, { children: renderNodes(document.content, link) });
|
|
51
57
|
}
|
|
52
58
|
function AceTextArea({ value, multiline = true }) {
|
|
@@ -63,4 +69,4 @@ function usePreviewRefresh(adminOrigin, refresh) {
|
|
|
63
69
|
}, [adminOrigin, refresh]);
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
export { AceImage, AceRichText, AceTextArea, usePreviewRefresh };
|
|
72
|
+
export { AceImage, AceLink, AceRichText, AceTextArea, usePreviewRefresh };
|
|
@@ -26,6 +26,10 @@ function linkAddress(address) {
|
|
|
26
26
|
function isInternalPath(address) {
|
|
27
27
|
return address.startsWith("/") && !address.startsWith("//");
|
|
28
28
|
}
|
|
29
|
+
function linkAttributes(link) {
|
|
30
|
+
const href = linkAddress(link.href);
|
|
31
|
+
return href ? { href, label: link.label ? link.label : null } : null;
|
|
32
|
+
}
|
|
29
33
|
|
|
30
34
|
// src/helpers/text-area.ts
|
|
31
35
|
var escapes = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
@@ -104,4 +108,4 @@ function renderRichText(document) {
|
|
|
104
108
|
return (document.content ?? []).map(renderNode).join("");
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
export { headingLevel, imageAttributes, isInternalPath, linkAddress, listStart, renderRichText, renderTextArea, richTextImageSizes, textAreaLines, textAreaParagraphs, textAreaSingleLine };
|
|
111
|
+
export { headingLevel, imageAttributes, isInternalPath, linkAddress, linkAttributes, listStart, renderRichText, renderTextArea, richTextImageSizes, textAreaLines, textAreaParagraphs, textAreaSingleLine };
|
package/dist/cli.js
CHANGED
|
@@ -119,6 +119,10 @@ function typeOfField(field) {
|
|
|
119
119
|
return { type: "AceImageData", uses: "AceImageData" };
|
|
120
120
|
case "richtext":
|
|
121
121
|
return { type: "AceRichTextDocument", uses: "AceRichTextDocument" };
|
|
122
|
+
case "date":
|
|
123
|
+
return { type: "string" };
|
|
124
|
+
case "link":
|
|
125
|
+
return { type: "AceLinkData", uses: "AceLinkData" };
|
|
122
126
|
default:
|
|
123
127
|
return { type: "unknown" };
|
|
124
128
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ type AceClientOptions = {
|
|
|
13
13
|
type AceFieldSchema = {
|
|
14
14
|
handle: string;
|
|
15
15
|
label: string;
|
|
16
|
-
/** `text`, `textarea`, `select`, `number`, `checkbox`, `image` or `
|
|
16
|
+
/** `text`, `textarea`, `select`, `number`, `checkbox`, `image`, `richtext`, `date` or `link`. */
|
|
17
17
|
component_key: string;
|
|
18
18
|
config: { required?: boolean; options?: string[] } | null;
|
|
19
19
|
};
|
|
@@ -178,6 +178,25 @@ type AceImageAttributes = {
|
|
|
178
178
|
/** What to put on an `<img>`: a web-sized `src`, `srcset` when there are copies, and the size so the page doesn't jump. */
|
|
179
179
|
declare function imageAttributes(image: AceImageData, { sizes, lazy }: AceImageOptions): AceImageAttributes;
|
|
180
180
|
|
|
181
|
+
type AceDateFormat = Intl.DateTimeFormatOptions & {
|
|
182
|
+
/** A language such as "en-GB". The default is the one the server or browser runs in. */
|
|
183
|
+
locale?: string;
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* A date field's day (a text such as "2026-10-13") as words, or null if it isn't a real day. A date field holds a
|
|
187
|
+
* day with no time of day, so this never shifts it with a time zone: 13 October is 13 October everywhere.
|
|
188
|
+
*/
|
|
189
|
+
declare function formatAceDate(date: string, format?: AceDateFormat): string | null;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* What a link field holds in an entry's values, as visitors get it: where it goes, already worked out (a link to a
|
|
193
|
+
* page of the site is its address, such as "/pages/about"), and the words it is shown with, if the editor gave any.
|
|
194
|
+
*/
|
|
195
|
+
type AceLinkData = {
|
|
196
|
+
href: string;
|
|
197
|
+
label?: string;
|
|
198
|
+
};
|
|
199
|
+
|
|
181
200
|
/**
|
|
182
201
|
* Where a link in rich text goes, or null if it can't be trusted. The CMS only allows paths, web addresses,
|
|
183
202
|
* mailto: and tel:; this checks again so the page stays safe even if something else ever gets into the text.
|
|
@@ -185,6 +204,16 @@ declare function imageAttributes(image: AceImageData, { sizes, lazy }: AceImageO
|
|
|
185
204
|
declare function linkAddress(address: string): string | null;
|
|
186
205
|
/** Whether a link stays on this site: a path such as "/pages/about", not "//elsewhere", "https://..." or "mailto:...". */
|
|
187
206
|
declare function isInternalPath(address: string): boolean;
|
|
207
|
+
/** What to draw for a link field: where it goes and its words (null when the editor gave none). */
|
|
208
|
+
type AceLinkAttributes = {
|
|
209
|
+
href: string;
|
|
210
|
+
label: string | null;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* What a link field's value is drawn with, or null if its address can't be trusted. The CMS only allows paths, web
|
|
214
|
+
* addresses, mailto: and tel:; this checks again so the page stays safe even if something else ever gets into the data.
|
|
215
|
+
*/
|
|
216
|
+
declare function linkAttributes(link: AceLinkData): AceLinkAttributes | null;
|
|
188
217
|
|
|
189
218
|
/** A CMS menu as links ready to draw. An entry link to the home entry goes to "/" so it isn't a redirect. */
|
|
190
219
|
declare function linksFromMenu(menu: AceMenu): AceNavigationLink[];
|
|
@@ -308,4 +337,4 @@ declare const listStart: (node: AceRichTextNode) => number | undefined;
|
|
|
308
337
|
*/
|
|
309
338
|
declare function renderRichText(document: AceRichTextDocument): string;
|
|
310
339
|
|
|
311
|
-
export { type AceAddress, type AceClient, type AceClientOptions, type AceContentTypeHandle, type AceContentTypeSchema, type AceContentTypes, type AceEntry, type AceFieldSchema, type AceImageAttributes, type AceImageData, type AceImageOptions, type AceMenu, type AceMenuItem, type AceNavigationLink, type AcePage, AcePreviewExpiredError, type AceRichTextDocument, type AceRichTextMark, type AceRichTextNode, AceUnavailableError, type AceValuesOf, addFramingPolicy, addressFromPath, createAceClient, framingPolicy, headingLevel, homeAddress, imageAttributes, isCurrentPage, isHomeAddress, isInternalPath, isRefreshRequest, linkAddress, linksFromMenu, listStart, listenForPreviewRefresh, loadMenuLinks, loadPage, originOf, pageNotices, pathOfAddress, previewHeaders, previewParameter, previewTokenFrom, refreshMessageType, refreshPageContent, renderRichText, renderTextArea, richTextImageSizes, swapPageContent, visitablePath };
|
|
340
|
+
export { type AceAddress, type AceClient, type AceClientOptions, type AceContentTypeHandle, type AceContentTypeSchema, type AceContentTypes, type AceDateFormat, type AceEntry, type AceFieldSchema, type AceImageAttributes, type AceImageData, type AceImageOptions, type AceLinkAttributes, type AceLinkData, type AceMenu, type AceMenuItem, type AceNavigationLink, type AcePage, AcePreviewExpiredError, type AceRichTextDocument, type AceRichTextMark, type AceRichTextNode, AceUnavailableError, type AceValuesOf, addFramingPolicy, addressFromPath, createAceClient, formatAceDate, framingPolicy, headingLevel, homeAddress, imageAttributes, isCurrentPage, isHomeAddress, isInternalPath, isRefreshRequest, linkAddress, linkAttributes, linksFromMenu, listStart, listenForPreviewRefresh, loadMenuLinks, loadPage, originOf, pageNotices, pathOfAddress, previewHeaders, previewParameter, previewTokenFrom, refreshMessageType, refreshPageContent, renderRichText, renderTextArea, richTextImageSizes, swapPageContent, visitablePath };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
export { pageNotices } from './chunk-XPZY5R5A.js';
|
|
2
|
-
export { headingLevel, imageAttributes, isInternalPath, linkAddress, listStart, renderRichText, renderTextArea, richTextImageSizes } from './chunk-
|
|
2
|
+
export { headingLevel, imageAttributes, isInternalPath, linkAddress, linkAttributes, listStart, renderRichText, renderTextArea, richTextImageSizes } from './chunk-FVR55GIU.js';
|
|
3
3
|
export { listenForPreviewRefresh, refreshPageContent } from './chunk-YN7LJWGI.js';
|
|
4
4
|
export { AcePreviewExpiredError, AceUnavailableError, createAceClient, isCurrentPage, linksFromMenu, loadMenuLinks, loadPage } from './chunk-XYOW63V6.js';
|
|
5
5
|
export { addressFromPath, homeAddress, isHomeAddress, pathOfAddress, visitablePath } from './chunk-M5WR5TCR.js';
|
|
6
6
|
export { addFramingPolicy, framingPolicy, isRefreshRequest, originOf, previewHeaders, previewParameter, previewTokenFrom, refreshMessageType, swapPageContent } from './chunk-YLO6IAXF.js';
|
|
7
|
+
|
|
8
|
+
// src/helpers/dates.ts
|
|
9
|
+
var defaultFormat = { dateStyle: "long" };
|
|
10
|
+
function formatAceDate(date, format = {}) {
|
|
11
|
+
const parts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
|
12
|
+
if (!parts) return null;
|
|
13
|
+
const [year, month, day] = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
|
|
14
|
+
const moment = new Date(Date.UTC(year, month - 1, day));
|
|
15
|
+
if (moment.getUTCFullYear() !== year || moment.getUTCMonth() !== month - 1 || moment.getUTCDate() !== day)
|
|
16
|
+
return null;
|
|
17
|
+
const { locale, ...options } = format;
|
|
18
|
+
return new Intl.DateTimeFormat(locale, {
|
|
19
|
+
...Object.keys(options).length > 0 ? options : defaultFormat,
|
|
20
|
+
timeZone: "UTC"
|
|
21
|
+
}).format(moment);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { formatAceDate };
|
|
@@ -75,7 +75,7 @@ type AceClientOptions = {
|
|
|
75
75
|
type AceFieldSchema = {
|
|
76
76
|
handle: string;
|
|
77
77
|
label: string;
|
|
78
|
-
/** `text`, `textarea`, `select`, `number`, `checkbox`, `image` or `
|
|
78
|
+
/** `text`, `textarea`, `select`, `number`, `checkbox`, `image`, `richtext`, `date` or `link`. */
|
|
79
79
|
component_key: string;
|
|
80
80
|
config: { required?: boolean; options?: string[] } | null;
|
|
81
81
|
};
|
package/dist/react-router.d.ts
CHANGED
|
@@ -29,6 +29,25 @@ type AceImageProps = AceImageOptions & {
|
|
|
29
29
|
/** An image from the CMS as an `<img>` with a web-sized `src`, `srcset`, its size and its alt text. */
|
|
30
30
|
declare function AceImage({ image, sizes, lazy, ...rest }: AceImageProps): react.JSX.Element;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* What a link field holds in an entry's values, as visitors get it: where it goes, already worked out (a link to a
|
|
34
|
+
* page of the site is its address, such as "/pages/about"), and the words it is shown with, if the editor gave any.
|
|
35
|
+
*/
|
|
36
|
+
type AceLinkData = {
|
|
37
|
+
href: string;
|
|
38
|
+
label?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A link field, drawn with React Router's `Link` for a page of this site. Everything else is as `AceLink` from
|
|
43
|
+
* `@sandboxsonny/ace-kit/react`: its words by default, nothing drawn for an address that can't be trusted.
|
|
44
|
+
*/
|
|
45
|
+
declare function AceLink({ link, children, className, }: {
|
|
46
|
+
link: AceLinkData;
|
|
47
|
+
children?: React.ReactNode;
|
|
48
|
+
className?: string;
|
|
49
|
+
}): react.JSX.Element;
|
|
50
|
+
|
|
32
51
|
type AceTextAreaProps = {
|
|
33
52
|
/** The text area field's value. Missing or blank draws nothing. */
|
|
34
53
|
value: string | undefined;
|
|
@@ -152,4 +171,4 @@ type AceRootData = {
|
|
|
152
171
|
};
|
|
153
172
|
};
|
|
154
173
|
|
|
155
|
-
export { AceImage, AceNotice, AceRichText, type AceRootData, AceTextArea, createMeta, meta, useAceMenu, useAcePreview };
|
|
174
|
+
export { AceImage, AceLink, AceNotice, AceRichText, type AceRootData, AceTextArea, createMeta, meta, useAceMenu, useAcePreview };
|
package/dist/react-router.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { pageNotices } from './chunk-XPZY5R5A.js';
|
|
2
|
-
import { AceRichText, usePreviewRefresh } from './chunk-
|
|
3
|
-
export { AceImage, AceTextArea } from './chunk-
|
|
4
|
-
import { isInternalPath } from './chunk-
|
|
2
|
+
import { AceLink, AceRichText, usePreviewRefresh } from './chunk-2MXND4HG.js';
|
|
3
|
+
export { AceImage, AceTextArea } from './chunk-2MXND4HG.js';
|
|
4
|
+
import { isInternalPath } from './chunk-FVR55GIU.js';
|
|
5
5
|
import './chunk-YN7LJWGI.js';
|
|
6
6
|
import './chunk-M5WR5TCR.js';
|
|
7
7
|
import './chunk-YLO6IAXF.js';
|
|
8
8
|
import { Link, useRevalidator, useRouteLoaderData } from 'react-router';
|
|
9
|
-
import {
|
|
9
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
10
10
|
import { useCallback } from 'react';
|
|
11
11
|
|
|
12
|
+
var RouterLink = ({ href, children, className }) => isInternalPath(href) ? /* @__PURE__ */ jsx(Link, { to: href, className, children }) : /* @__PURE__ */ jsx("a", { href, className, children });
|
|
13
|
+
function AceLink2({
|
|
14
|
+
link,
|
|
15
|
+
children,
|
|
16
|
+
className
|
|
17
|
+
}) {
|
|
18
|
+
return /* @__PURE__ */ jsx(AceLink, { link, className, as: RouterLink, children });
|
|
19
|
+
}
|
|
12
20
|
function AceNotice({ page, className }) {
|
|
13
21
|
const { heading, message } = pageNotices[page.kind];
|
|
14
22
|
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
@@ -17,9 +25,9 @@ function AceNotice({ page, className }) {
|
|
|
17
25
|
page.kind === "preview-expired" ? null : /* @__PURE__ */ jsx(Link, { to: "/", children: "Back to the home page" })
|
|
18
26
|
] });
|
|
19
27
|
}
|
|
20
|
-
var
|
|
28
|
+
var RouterLink2 = ({ href, children }) => isInternalPath(href) ? /* @__PURE__ */ jsx(Link, { to: href, children }) : /* @__PURE__ */ jsx("a", { href, children });
|
|
21
29
|
function AceRichText2({ document }) {
|
|
22
|
-
return /* @__PURE__ */ jsx(AceRichText, { document, link:
|
|
30
|
+
return /* @__PURE__ */ jsx(AceRichText, { document, link: RouterLink2 });
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
// src/react-router/meta.ts
|
|
@@ -52,4 +60,4 @@ function useAcePreview() {
|
|
|
52
60
|
usePreviewRefresh(useAceRootData()?.previewAdminOrigin, refresh);
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
export { AceNotice, AceRichText2 as AceRichText, createMeta, meta, useAceMenu, useAcePreview };
|
|
63
|
+
export { AceLink2 as AceLink, AceNotice, AceRichText2 as AceRichText, createMeta, meta, useAceMenu, useAcePreview };
|
package/dist/react.d.ts
CHANGED
|
@@ -29,6 +29,15 @@ type AceImageProps = AceImageOptions & {
|
|
|
29
29
|
/** An image from the CMS as an `<img>` with a web-sized `src`, `srcset`, its size and its alt text. */
|
|
30
30
|
declare function AceImage({ image, sizes, lazy, ...rest }: AceImageProps): react.JSX.Element;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* What a link field holds in an entry's values, as visitors get it: where it goes, already worked out (a link to a
|
|
34
|
+
* page of the site is its address, such as "/pages/about"), and the words it is shown with, if the editor gave any.
|
|
35
|
+
*/
|
|
36
|
+
type AceLinkData = {
|
|
37
|
+
href: string;
|
|
38
|
+
label?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
32
41
|
/** A mark on a piece of text. A link's address is already worked out: an entry link comes as its path. */
|
|
33
42
|
type AceRichTextMark = { type: "bold" } | { type: "italic" } | { type: "link"; attrs: { href: string } };
|
|
34
43
|
|
|
@@ -51,6 +60,7 @@ type AceRichTextDocument = { type: "doc"; content?: AceRichTextNode[] };
|
|
|
51
60
|
type AceRichTextLink = ComponentType<{
|
|
52
61
|
href: string;
|
|
53
62
|
children: ReactNode;
|
|
63
|
+
className?: string;
|
|
54
64
|
}>;
|
|
55
65
|
type AceRichTextProps = {
|
|
56
66
|
document: AceRichTextDocument;
|
|
@@ -63,6 +73,18 @@ type AceRichTextProps = {
|
|
|
63
73
|
*/
|
|
64
74
|
declare function AceRichText({ document, link }: AceRichTextProps): react.JSX.Element;
|
|
65
75
|
|
|
76
|
+
type AceLinkProps = {
|
|
77
|
+
/** A link field's value: `entry.values.button`. */
|
|
78
|
+
link: AceLinkData;
|
|
79
|
+
/** What to show instead of the editor's words, which are the default (or the address when there are none). */
|
|
80
|
+
children?: ReactNode;
|
|
81
|
+
className?: string;
|
|
82
|
+
/** Draws the link; the default is a plain `<a>`. Give one that uses your router's link to keep navigation client-side. */
|
|
83
|
+
as?: AceRichTextLink;
|
|
84
|
+
};
|
|
85
|
+
/** A link field, drawn as a link. Nothing is drawn if the address can't be trusted. */
|
|
86
|
+
declare function AceLink({ link, children, className, as: Link }: AceLinkProps): react.JSX.Element | null;
|
|
87
|
+
|
|
66
88
|
type AceTextAreaProps = {
|
|
67
89
|
/** The text area field's value. Missing or blank draws nothing. */
|
|
68
90
|
value: string | undefined;
|
|
@@ -85,4 +107,4 @@ declare function AceTextArea({ value, multiline }: AceTextAreaProps): react.JSX.
|
|
|
85
107
|
*/
|
|
86
108
|
declare function usePreviewRefresh(adminOrigin: string | null | undefined, refresh?: () => Promise<void>): void;
|
|
87
109
|
|
|
88
|
-
export { AceImage, AceRichText, type AceRichTextLink, AceTextArea, usePreviewRefresh };
|
|
110
|
+
export { AceImage, AceLink, AceRichText, type AceRichTextLink, AceTextArea, usePreviewRefresh };
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { AceImage, AceRichText, AceTextArea, usePreviewRefresh } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { AceImage, AceLink, AceRichText, AceTextArea, usePreviewRefresh } from './chunk-2MXND4HG.js';
|
|
2
|
+
import './chunk-FVR55GIU.js';
|
|
3
3
|
import './chunk-YN7LJWGI.js';
|
|
4
4
|
import './chunk-M5WR5TCR.js';
|
|
5
5
|
import './chunk-YLO6IAXF.js';
|
package/docs/api-reference.md
CHANGED
|
@@ -29,6 +29,13 @@ Everything below is exported from `@sandboxsonny/ace-kit` unless a path is given
|
|
|
29
29
|
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
|
30
30
|
| `imageAttributes(image, { sizes, lazy? })` | The attributes for an `<img>`: `src`, `srcSet`, `sizes`, `width`, `height`, `alt`, `loading`, `decoding`, `fetchPriority`. |
|
|
31
31
|
|
|
32
|
+
## Links and dates
|
|
33
|
+
|
|
34
|
+
| | |
|
|
35
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `linkAttributes(link)` | What to draw for a link field: `{ href, label }` (`label` is `null` when the editor gave no words), or `null` if the address can't be trusted. |
|
|
37
|
+
| `formatAceDate(date, format?)` | A date field's day (`"2026-10-13"`) as words, in the language asked for (`{ locale: "en-GB" }`), or `null` if it isn't a real day. Takes the options of `Intl.DateTimeFormat`; the default is `dateStyle: "long"`. A day has no time of day, so it never shifts with a time zone. |
|
|
38
|
+
|
|
32
39
|
## Menus
|
|
33
40
|
|
|
34
41
|
| | |
|
|
@@ -60,7 +67,7 @@ Everything below is exported from `@sandboxsonny/ace-kit` unless a path is given
|
|
|
60
67
|
|
|
61
68
|
## Types
|
|
62
69
|
|
|
63
|
-
`AceEntry<Values>`, `AceImageData`, `AceMenu`, `AceMenuItem`, `AceNavigationLink`, `AceRichTextDocument`, `AceRichTextNode`, `AceRichTextMark`, `AceContentTypeSchema`, `AceFieldSchema`, `AceAddress`, `AcePage`, `AceClient`, `AceClientOptions`, `AceImageAttributes`, `AceImageOptions`, and the ones generation fills in: `AceContentTypes`, `AceContentTypeHandle`, `AceValuesOf`.
|
|
70
|
+
`AceEntry<Values>`, `AceImageData`, `AceLinkData`, `AceLinkAttributes`, `AceDateFormat`, `AceMenu`, `AceMenuItem`, `AceNavigationLink`, `AceRichTextDocument`, `AceRichTextNode`, `AceRichTextMark`, `AceContentTypeSchema`, `AceFieldSchema`, `AceAddress`, `AcePage`, `AceClient`, `AceClientOptions`, `AceImageAttributes`, `AceImageOptions`, and the ones generation fills in: `AceContentTypes`, `AceContentTypeHandle`, `AceValuesOf`.
|
|
64
71
|
|
|
65
72
|
## Astro
|
|
66
73
|
|
|
@@ -76,7 +83,7 @@ Everything below is exported from `@sandboxsonny/ace-kit` unless a path is given
|
|
|
76
83
|
| `applyPage(response, page)` | Puts a page's status and headers on a response. |
|
|
77
84
|
| `ace` | The client these use. |
|
|
78
85
|
|
|
79
|
-
Components, by file: `AceImage.astro`, `AceRichText.astro`, `AceTextArea.astro`, `AceMenu.astro`, `AceNotice.astro`, `AcePreviewRefresh.astro`.
|
|
86
|
+
Components, by file: `AceImage.astro`, `AceLink.astro`, `AceRichText.astro`, `AceTextArea.astro`, `AceMenu.astro`, `AceNotice.astro`, `AcePreviewRefresh.astro`.
|
|
80
87
|
|
|
81
88
|
## React Router 7
|
|
82
89
|
|
|
@@ -84,11 +91,11 @@ Components, by file: `AceImage.astro`, `AceRichText.astro`, `AceTextArea.astro`,
|
|
|
84
91
|
|
|
85
92
|
`@sandboxsonny/ace-kit/react-router` (browser-safe):
|
|
86
93
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
| `AceRichText`, `AceTextArea`, `AceImage`, `AceNotice` | Components; rich text links are router links.
|
|
90
|
-
| `createMeta({ siteName? })`, `meta`
|
|
91
|
-
| `useAceMenu()`, `useAcePreview()`
|
|
94
|
+
| | |
|
|
95
|
+
| ---------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
96
|
+
| `AceRichText`, `AceTextArea`, `AceImage`, `AceLink`, `AceNotice` | Components; rich text links and `AceLink` are router links for pages of the site. |
|
|
97
|
+
| `createMeta({ siteName? })`, `meta` | The route's `meta`. |
|
|
98
|
+
| `useAceMenu()`, `useAcePreview()` | Read the header menu; keep a preview fresh. Need `createRootLoader`. |
|
|
92
99
|
|
|
93
100
|
`@sandboxsonny/ace-kit/react-router/server` (server only):
|
|
94
101
|
|
|
@@ -102,4 +109,4 @@ Components, by file: `AceImage.astro`, `AceRichText.astro`, `AceTextArea.astro`,
|
|
|
102
109
|
|
|
103
110
|
## React (`@sandboxsonny/ace-kit/react`)
|
|
104
111
|
|
|
105
|
-
`AceImage`, `AceRichText` (with `link`), `usePreviewRefresh(adminOrigin, refresh?)`.
|
|
112
|
+
`AceImage`, `AceLink` (with `as`), `AceRichText` (with `link`), `usePreviewRefresh(adminOrigin, refresh?)`.
|
package/docs/astro.md
CHANGED
|
@@ -69,6 +69,7 @@ Import them by file:
|
|
|
69
69
|
```astro
|
|
70
70
|
---
|
|
71
71
|
import AceImage from "@sandboxsonny/ace-kit/astro/AceImage.astro";
|
|
72
|
+
import AceLink from "@sandboxsonny/ace-kit/astro/AceLink.astro";
|
|
72
73
|
import AceRichText from "@sandboxsonny/ace-kit/astro/AceRichText.astro";
|
|
73
74
|
import AceTextArea from "@sandboxsonny/ace-kit/astro/AceTextArea.astro";
|
|
74
75
|
import AceMenu from "@sandboxsonny/ace-kit/astro/AceMenu.astro";
|
|
@@ -78,11 +79,13 @@ import AcePreviewRefresh from "@sandboxsonny/ace-kit/astro/AcePreviewRefresh.ast
|
|
|
78
79
|
<AceMenu handle="main-menu" />
|
|
79
80
|
<AceImage image={entry.values.hero} sizes="(min-width: 64rem) 64rem, 100vw" lazy={false} />
|
|
80
81
|
<AceRichText document={entry.values.body} />
|
|
82
|
+
<AceLink link={entry.values.button} class="button" />
|
|
81
83
|
<AcePreviewRefresh />
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
- **AceMenu** draws a whole menu as a `<nav>` with nested lists, marking the current page with `aria-current="page"`. Props: `handle`, `fallback`, `label` (what a screen reader calls it) and `class`. It is deliberately unstyled (`.ace-menu`, `.ace-menu__list`, `.ace-menu__sublist` are the hooks); to control the markup, use `getMenuLinks` and draw it yourself.
|
|
85
87
|
- **AceImage** writes an `<img>` with `src`, `srcset`, `sizes`, `width`, `height`, `alt`, `loading` and `decoding`. Set `sizes` to how wide the image is shown, and `lazy={false}` for the image at the top of the page.
|
|
88
|
+
- **AceLink** draws a link field as an `<a>` with the editor's words (or the address when there are none; put your own words in the slot to replace them). Nothing is drawn if the address can't be trusted, so check for a missing link yourself if you need a fallback. A date field is plain text: `formatAceDate(entry.values.published, { locale: "en-GB" })` gives "13 October 2026", and `<time datetime={entry.values.published}>` takes the raw value.
|
|
86
89
|
- **AceRichText** draws a rich text field as HTML. It outputs bare elements (`h2`, `p`, `ul`, `a`...); style them from a wrapping element. Astro scopes styles, so reach them with `:global`, e.g. `.rich-text :global(h2) { ... }`.
|
|
87
90
|
- **AceTextArea** draws a Textarea field (plain text): `multiline` (the default) makes paragraphs of blocks and a `<br>` of a new line; `multiline={false}` is one line of text with no element around it.
|
|
88
91
|
- **AcePreviewRefresh** goes once in your layout. It does nothing unless the page was opened by the CMS's editor.
|
package/docs/react-router.md
CHANGED
|
@@ -93,6 +93,7 @@ export default function App() {
|
|
|
93
93
|
From `@sandboxsonny/ace-kit/react-router` (these are safe in the browser):
|
|
94
94
|
|
|
95
95
|
- **AceRichText** draws a rich text field. Links to pages of this site are router `Link`s, so navigating doesn't reload the page. It renders bare elements; style them from a wrapper.
|
|
96
|
+
- **AceLink** draws a link field. A link to a page of this site is a router `Link`; any other is a plain `<a>`. It shows the editor's words (or the address), or your children instead, and draws nothing for an address that can't be trusted. Dates are text: format them with `formatAceDate` from `@sandboxsonny/ace-kit`.
|
|
96
97
|
- **AceTextArea** draws a Textarea field (plain text): `multiline` (the default) makes paragraphs and line breaks, `multiline={false}` is one line of text.
|
|
97
98
|
- **AceImage** is an `<img>` with `src`, `srcset`, `sizes`, size and alt text. Set `sizes` to how wide it is shown, and `lazy={false}` for the image at the top of the page.
|
|
98
99
|
- **AceNotice** is the heading and message for a page that isn't an entry. Give it a `className`.
|
package/docs/react.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Needs `react` 18 or newer. The components work with any React framework (React Router, Next.js, Remix, plain Vite). **On React Router 7, use the [React Router adapter](./react-router.md) instead of wiring this up yourself.** Fetch with the [client](./getting-started.md#the-client) on the server (a loader, a server component, `getServerSideProps`), then draw.
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
|
-
import { AceImage, AceRichText } from "@sandboxsonny/ace-kit/react";
|
|
6
|
+
import { AceImage, AceLink, AceRichText } from "@sandboxsonny/ace-kit/react";
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
## AceImage
|
|
@@ -14,6 +14,18 @@ import { AceImage, AceRichText } from "@sandboxsonny/ace-kit/react";
|
|
|
14
14
|
|
|
15
15
|
An `<img>` with `src`, `srcset`, `sizes`, `width`, `height` and `alt`. Any other `<img>` prop (`className`, `style`, `onLoad`...) is passed through. `lazy` defaults to `true`; turn it off for the image at the top of the page.
|
|
16
16
|
|
|
17
|
+
## AceLink
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
{
|
|
21
|
+
entry.values.button ? <AceLink link={entry.values.button} className="button" /> : null;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
A link field as an `<a>` with the editor's words, or the address when there are none; children replace the words. Nothing is drawn if the address can't be trusted. Give it your router's link with `as` so navigation stays in the browser (`as={({ href, children, className }) => <Link to={href} className={className}>{children}</Link>}`).
|
|
26
|
+
|
|
27
|
+
A date field is text: `formatAceDate(entry.values.published, { locale: "en-GB" })` gives "13 October 2026" whatever the server's time zone, and `<time dateTime={entry.values.published}>` takes the raw value.
|
|
28
|
+
|
|
17
29
|
## AceRichText
|
|
18
30
|
|
|
19
31
|
```tsx
|
package/docs/typed-content.md
CHANGED
|
@@ -35,15 +35,17 @@ about?.values.headline; // string
|
|
|
35
35
|
cms.getEntry("pagez", "about"); // type error: no such content type
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
| Field | Type
|
|
39
|
-
| -------------------------------- |
|
|
40
|
-
| Text, Textarea | `string`
|
|
41
|
-
| Number | `number`
|
|
42
|
-
| Checkbox | `boolean`
|
|
43
|
-
| Select | a union of its options, e.g. `"formal" \| "casual"`
|
|
44
|
-
| Image | `AceImageData`
|
|
45
|
-
| Rich text | `AceRichTextDocument`
|
|
46
|
-
|
|
|
38
|
+
| Field | Type |
|
|
39
|
+
| -------------------------------- | ---------------------------------------------------------- |
|
|
40
|
+
| Text, Textarea | `string` |
|
|
41
|
+
| Number | `number` |
|
|
42
|
+
| Checkbox | `boolean` |
|
|
43
|
+
| Select | a union of its options, e.g. `"formal" \| "casual"` |
|
|
44
|
+
| Image | `AceImageData` |
|
|
45
|
+
| Rich text | `AceRichTextDocument` |
|
|
46
|
+
| Date | `string`, a day written `YYYY-MM-DD` (see `formatAceDate`) |
|
|
47
|
+
| Link | `AceLinkData`: `{ href, label? }` |
|
|
48
|
+
| a kind this version doesn't know | `unknown` |
|
|
47
49
|
|
|
48
50
|
A field is optional unless it is marked required in the CMS. Before you generate, handles are plain `string` and values are `Record<string, unknown>`, so nothing breaks.
|
|
49
51
|
|
package/package.json
CHANGED