@bison-lab/payload-blocks 3.3.0 → 3.4.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 +119 -18
- package/dist/admin.d.mts +5 -2
- package/dist/admin.d.mts.map +1 -1
- package/dist/admin.mjs +362 -4
- package/dist/admin.mjs.map +1 -1
- package/dist/index.d.mts +101 -19
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +147 -23
- package/dist/index.mjs.map +1 -1
- package/dist/link-CnwLtm47.mjs +41 -0
- package/dist/link-CnwLtm47.mjs.map +1 -0
- package/dist/react.d.mts +19 -8
- package/dist/react.d.mts.map +1 -1
- package/dist/react.mjs +89 -86
- package/dist/react.mjs.map +1 -1
- package/dist/rich-text.d.mts +38 -14
- package/dist/rich-text.d.mts.map +1 -1
- package/dist/rich-text.mjs +81 -24
- package/dist/rich-text.mjs.map +1 -1
- package/dist/{seam-BDFZlV3F.mjs → seam-CmLb3BSo.mjs} +47 -2
- package/dist/seam-CmLb3BSo.mjs.map +1 -0
- package/dist/{types-D9rg0BoW.d.mts → types-ODMRyOpH.d.mts} +99 -17
- package/dist/types-ODMRyOpH.d.mts.map +1 -0
- package/package.json +1 -1
- package/dist/seam-BDFZlV3F.mjs.map +0 -1
- package/dist/types-D9rg0BoW.d.mts.map +0 -1
package/dist/admin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.mjs","names":[],"sources":["../src/admin/min-rows-array-field.tsx"],"sourcesContent":["import { ArrayField, toast, useField, useTranslation } from \"@payloadcms/ui\";\nimport type { ArrayFieldClientComponent, ArrayFieldClientProps } from \"payload\";\nimport { useCallback, useRef } from \"react\";\n\n/**\n * Payload's array field, with *Remove* refused once the rows are down to\n * `minRows`.\n *\n * This wraps the stock `ArrayField` rather than re-implementing it, so every\n * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put\n * and a Payload upgrade carries it along. The catch is where the row menu\n * lives: Payload portals it into `document.body`, out of reach of any CSS or\n * form context this wrapper could scope. What still reaches the wrapper is\n * the React event: synthetic events bubble through portals along the\n * component tree, so a capture handler here sees the click on *Remove*\n * before the menu's own handler does, and can stop it.\n *\n * Because the menu is portaled, the DOM cannot say whether a given *Remove*\n * belongs to this array or to an array nested inside one of its rows. The\n * trigger that opened the menu *is* in the DOM, though, and every menu opens\n * from its trigger. So the handler remembers, per menu opening, whether the\n * trigger sat directly in this field, and only gates the *Remove* that follows.\n */\nexport const MinRowsArrayField: ArrayFieldClientComponent = (props) => {\n const { field, path: pathFromProps } = props;\n const minRows = field.minRows ?? (field.required ? 1 : 0);\n\n const { rows = [] } = useField({\n hasRows: true,\n potentiallyStalePath: pathFromProps,\n });\n const { i18n, t } = useTranslation();\n\n const rootRef = useRef<HTMLDivElement>(null);\n const menuIsOurs = useRef(false);\n\n const onClickCapture = useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const trigger = target.closest(\".array-actions__button\");\n if (trigger) {\n // The nearest array or blocks field around the trigger is the field\n // whose menu is opening. Ours is the first one inside the wrapper.\n const owner = trigger.closest(\".array-field, .blocks-field\");\n menuIsOurs.current =\n owner !== null && owner === rootRef.current?.querySelector(\".array-field\");\n return;\n }\n\n if (!target.closest(\".array-actions__remove\")) return;\n if (!menuIsOurs.current || rows.length > minRows) return;\n\n event.preventDefault();\n event.stopPropagation();\n toast.error(\n t(\"validation:requiresAtLeast\", {\n count: minRows,\n label: rowLabel(field, minRows, i18n.language) ??\n t(minRows > 1 ? \"general:rows\" : \"general:row\"),\n }),\n );\n },\n [field, i18n.language, minRows, rows.length, t],\n );\n\n return (\n <div ref={rootRef} onClickCapture={onClickCapture}>\n <ArrayField {...props} />\n </div>\n );\n};\n\n/**\n * The word for a row, as Payload's own \"requires at least\" banner picks it:\n * the plural label above one, the singular otherwise, the field label failing\n * both. Client-side labels are static — a string or a per-language record.\n */\nfunction rowLabel(\n field: ArrayFieldClientProps[\"field\"],\n minRows: number,\n language: string,\n): string | undefined {\n const label = minRows > 1 ? field.labels?.plural : field.labels?.singular;\n return staticLabel(label, language) ?? staticLabel(field.label, language);\n}\n\nfunction staticLabel(label: unknown, language: string): string | undefined {\n if (typeof label === \"string\") return label;\n if (label && typeof label === \"object\") {\n const byLanguage = label as Record<string, string | undefined>;\n return byLanguage[language] ?? byLanguage.en ?? Object.values(byLanguage)[0];\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,qBAAgD,UAAU;CACrE,MAAM,EAAE,OAAO,MAAM,kBAAkB;CACvC,MAAM,UAAU,MAAM,YAAY,MAAM,WAAW,IAAI;CAEvD,MAAM,EAAE,OAAO,EAAE,KAAK,SAAS;EAC7B,SAAS;EACT,sBAAsB;EACvB,CAAC;CACF,MAAM,EAAE,MAAM,MAAM,gBAAgB;CAEpC,MAAM,UAAU,OAAuB,KAAK;CAC5C,MAAM,aAAa,OAAO,MAAM;AAiChC,QACE,oBAAC,OAAD;EAAK,KAAK;EAAS,gBAhCE,aACpB,UAA4C;GAC3C,MAAM,SAAS,MAAM;AACrB,OAAI,EAAE,kBAAkB,SAAU;GAElC,MAAM,UAAU,OAAO,QAAQ,yBAAyB;AACxD,OAAI,SAAS;IAGX,MAAM,QAAQ,QAAQ,QAAQ,8BAA8B;AAC5D,eAAW,UACT,UAAU,QAAQ,UAAU,QAAQ,SAAS,cAAc,eAAe;AAC5E;;AAGF,OAAI,CAAC,OAAO,QAAQ,yBAAyB,CAAE;AAC/C,OAAI,CAAC,WAAW,WAAW,KAAK,SAAS,QAAS;AAElD,SAAM,gBAAgB;AACtB,SAAM,iBAAiB;AACvB,SAAM,MACJ,EAAE,8BAA8B;IAC9B,OAAO;IACP,OAAO,SAAS,OAAO,SAAS,KAAK,SAAS,IAC5C,EAAE,UAAU,IAAI,iBAAiB,cAAc;IAClD,CAAC,CACH;KAEH;GAAC;GAAO,KAAK;GAAU;GAAS,KAAK;GAAQ;GAAE,CAChD;YAIG,oBAAC,YAAD,EAAY,GAAI,OAAS,CAAA;EACrB,CAAA;;;;;;;AASV,SAAS,SACP,OACA,SACA,UACoB;AAEpB,QAAO,YADO,UAAU,IAAI,MAAM,QAAQ,SAAS,MAAM,QAAQ,UACvC,SAAS,IAAI,YAAY,MAAM,OAAO,SAAS;;AAG3E,SAAS,YAAY,OAAgB,UAAsC;AACzE,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,aAAa;AACnB,SAAO,WAAW,aAAa,WAAW,MAAM,OAAO,OAAO,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"admin.mjs","names":[],"sources":["../src/admin/link-field.tsx","../src/admin/min-rows-array-field.tsx"],"sourcesContent":["import {\n FieldDescription,\n FieldError,\n FieldLabel,\n useConfig,\n useField,\n useFieldPath,\n} from \"@payloadcms/ui\";\nimport type { ClientField, RowFieldClientComponent } from \"payload\";\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\";\n\nimport { linkTypeOf, type LinkPageDoc, type LinkType } from \"../fields/link\";\n\n/**\n * The link picker: one box in place of a link's `type`, `page` and `href`.\n *\n * Type to search the pages collection by its title field; each result shows\n * its path. Paste anything that can only be a destination (`http`, `mailto:`,\n * `tel:`, or a site path starting with `/`) and the one offer is to link to\n * it. Choosing writes the stored shape through Payload's `useField`, so the\n * document saves exactly what the stock fields would have saved, and the\n * chosen state shows a Page or External chip with a clear button.\n *\n * The search asks the collection's REST endpoint for `_status: published`\n * rows, so the draft rule is the server's and is applied on every keystroke.\n * A stored page is fetched by id without that filter, which is how the\n * picker knows to warn that a page chosen earlier has since been unpublished\n * or deleted.\n *\n * Painted with Payload's own theme variables and nothing else, so it reads\n * in both admin themes without a stylesheet of its own; the rules ride along\n * in a `<style href precedence>`, which React 19 hoists into the head and\n * dedupes across instances. Payload 3's admin is React 19 (`@payloadcms/ui`\n * peers `react ^19`), so that is the only React this entry runs under.\n */\n\nconst LABEL = \"Goes to\";\nconst PLACEHOLDER = \"Search pages, or paste a URL\";\nconst DESCRIPTION =\n \"Published pages only. Paste a URL, or a path like /find-a-doctor, to link anywhere else.\";\nconst NO_MATCH = \"No published page matches. Paste a URL to link outside the site.\";\nconst UNPUBLISHED =\n \"This page is no longer published. The site shows this link as plain text, and this document cannot be published until you pick another page or publish that one again.\";\nconst MISSING =\n \"This page no longer exists. Pick another page or paste a URL before publishing.\";\n\nconst SEARCH_DELAY_MS = 200;\nconst SEARCH_LIMIT = 8;\n\n/** Text an editor could only mean as a destination, never as a page title. */\nconst URL_LIKE = /^(https?:\\/\\/|mailto:|tel:|\\/)/i;\n\ntype Option = { kind: \"page\"; page: LinkPageDoc } | { kind: \"url\"; href: string };\n\n/**\n * A row has no name, so its `path` ends in `_index-N` and its fields sit at\n * the parent's level, the way Payload's `getFieldPaths` places them.\n */\nfunction siblingPaths(rowPath: string) {\n const base = rowPath\n .split(\".\")\n .filter((segment) => !segment.startsWith(\"_index-\"))\n .join(\".\");\n const at = (name: string) => (base ? `${base}.${name}` : name);\n return { type: at(\"type\"), page: at(\"page\"), href: at(\"href\") };\n}\n\nfunction pagePath(page: LinkPageDoc): string {\n return page.slug ? `/${page.slug}` : \"\";\n}\n\nexport const LinkField: RowFieldClientComponent = ({ field, path, readOnly }) => {\n // Payload re-renders a custom field with a stale `path` prop while rows are\n // reordered; the context, when there is one, is the row's current path.\n const rowPath = useFieldPath() ?? path;\n const paths = useMemo(() => siblingPaths(rowPath), [rowPath]);\n\n const type = useField<LinkType | null>({ path: paths.type });\n const page = useField<LinkPageDoc | number | string | null>({ path: paths.page });\n const href = useField<string | null>({ path: paths.href });\n\n const pageField = field.fields.find(\n (f): f is Extract<ClientField, { type: \"relationship\" }> =>\n f.type === \"relationship\" && f.name === \"page\",\n );\n const collection =\n typeof pageField?.relationTo === \"string\" ? pageField.relationTo : \"pages\";\n const required = Boolean(pageField?.required);\n\n const { config } = useConfig();\n const titleKey =\n config.collections.find((c) => c.slug === collection)?.admin?.useAsTitle ?? \"id\";\n const endpoint = `${config.serverURL}${config.routes.api}/${collection}`;\n\n const id = useId();\n const inputId = `${id}-input`;\n const listId = `${id}-list`;\n const inputRef = useRef<HTMLInputElement>(null);\n\n const [query, setQuery] = useState(\"\");\n const [open, setOpen] = useState(false);\n const [loading, setLoading] = useState(false);\n const [results, setResults] = useState<LinkPageDoc[]>([]);\n const [active, setActive] = useState(-1);\n const [focusNext, setFocusNext] = useState(false);\n /** The stored page, looked up; `missing` when the id no longer answers. */\n const [stored, setStored] = useState<{ doc: LinkPageDoc | null; missing: boolean } | null>(null);\n /** Pages seen in search results, so choosing one needs no second request. */\n const known = useRef(new Map<string, LinkPageDoc>());\n const searchSeq = useRef(0);\n\n // Which destination the row holds, by the same reading as the stock fields'\n // conditions and `resolveLink`; `null` is nothing chosen yet.\n const linkType = linkTypeOf({ type: type.value, page: page.value, href: href.value });\n const kind: LinkType | null =\n linkType === \"external\" ? (href.value ? \"external\" : null) : page.value ? \"page\" : null;\n\n const pageValue = page.value;\n const pageId = pageValue && typeof pageValue === \"object\" ? null : pageValue;\n\n useEffect(() => {\n if (kind !== \"page\") {\n setStored(null);\n return;\n }\n if (pageValue && typeof pageValue === \"object\") {\n setStored({ doc: pageValue, missing: false });\n return;\n }\n const cached = known.current.get(String(pageId));\n if (cached) {\n setStored({ doc: cached, missing: false });\n return;\n }\n let cancelled = false;\n fetch(`${endpoint}/${pageId}?depth=0`, { credentials: \"include\" })\n .then(async (response) => {\n if (cancelled) return;\n if (!response.ok) {\n setStored({ doc: null, missing: true });\n return;\n }\n const doc = (await response.json()) as LinkPageDoc;\n known.current.set(String(doc.id), doc);\n if (!cancelled) setStored({ doc, missing: false });\n })\n .catch(() => {\n if (!cancelled) setStored({ doc: null, missing: true });\n });\n return () => {\n cancelled = true;\n };\n }, [kind, pageValue, pageId, endpoint]);\n\n const trimmed = query.trim();\n const urlLike = URL_LIKE.test(trimmed);\n\n useEffect(() => {\n if (!open || urlLike) return;\n const seq = (searchSeq.current += 1);\n setLoading(true);\n const timer = setTimeout(() => {\n const params = new URLSearchParams({\n depth: \"0\",\n limit: String(SEARCH_LIMIT),\n sort: titleKey,\n \"where[_status][equals]\": \"published\",\n });\n if (trimmed) params.set(`where[${titleKey}][like]`, trimmed);\n fetch(`${endpoint}?${params.toString()}`, { credentials: \"include\" })\n .then((response) => (response.ok ? response.json() : { docs: [] }))\n .then((body: { docs?: LinkPageDoc[] }) => {\n if (seq !== searchSeq.current) return;\n const docs = body.docs ?? [];\n for (const doc of docs) known.current.set(String(doc.id), doc);\n setResults(docs);\n setLoading(false);\n })\n .catch(() => {\n if (seq !== searchSeq.current) return;\n setResults([]);\n setLoading(false);\n });\n }, SEARCH_DELAY_MS);\n return () => clearTimeout(timer);\n }, [open, urlLike, trimmed, endpoint, titleKey]);\n\n useEffect(() => {\n if (focusNext && inputRef.current) {\n inputRef.current.focus();\n setFocusNext(false);\n }\n }, [focusNext, kind]);\n\n const options: Option[] = urlLike\n ? [{ kind: \"url\", href: trimmed }]\n : results.map((doc) => ({ kind: \"page\", page: doc }));\n\n function choose(option: Option) {\n if (option.kind === \"page\") {\n known.current.set(String(option.page.id), option.page);\n type.setValue(\"page\");\n page.setValue(option.page.id);\n href.setValue(null);\n } else {\n type.setValue(\"external\");\n href.setValue(option.href);\n page.setValue(null);\n }\n setQuery(\"\");\n setOpen(false);\n setActive(-1);\n }\n\n function clear() {\n type.setValue(\"page\");\n page.setValue(null);\n href.setValue(null);\n setStored(null);\n setFocusNext(true);\n }\n\n function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n if (!open) {\n setOpen(true);\n return;\n }\n setActive((i) => (options.length ? (i + 1) % options.length : -1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActive((i) =>\n options.length ? (i <= 0 ? options.length - 1 : i - 1) : -1,\n );\n } else if (event.key === \"Enter\") {\n if (!open) return;\n event.preventDefault();\n const pick = options[active] ?? options[0];\n if (pick) choose(pick);\n } else if (event.key === \"Escape\") {\n if (!open) return;\n event.preventDefault();\n event.stopPropagation();\n setOpen(false);\n setActive(-1);\n }\n }\n\n const error = linkType === \"external\" ? href : page;\n const activeId = active >= 0 && open ? `${id}-option-${active}` : undefined;\n\n return (\n <div className=\"field-type bl-link-field\">\n <style href=\"bl-link-field\" precedence=\"default\">\n {CSS}\n </style>\n <FieldLabel htmlFor={inputId} label={LABEL} required={required} />\n <div className=\"bl-link-field__control\">\n {kind === \"page\" ? (\n <>\n <span className=\"bl-link-field__kind\">Page</span>\n <span className=\"bl-link-field__value\">\n {stored?.doc ? (\n <>\n {String(stored.doc[titleKey as keyof LinkPageDoc] ?? stored.doc.id)}\n <span className=\"bl-link-field__path\">{pagePath(stored.doc)}</span>\n </>\n ) : stored?.missing ? (\n `Page ${String(pageId)}`\n ) : (\n \"…\"\n )}\n </span>\n </>\n ) : kind === \"external\" ? (\n <>\n <span className=\"bl-link-field__kind\">External</span>\n <span className=\"bl-link-field__value\">{href.value}</span>\n </>\n ) : (\n <input\n ref={inputRef}\n id={inputId}\n type=\"text\"\n role=\"combobox\"\n className=\"bl-link-field__input\"\n placeholder={PLACEHOLDER}\n autoComplete=\"off\"\n disabled={readOnly}\n value={query}\n aria-expanded={open}\n aria-controls={listId}\n aria-autocomplete=\"list\"\n aria-activedescendant={activeId}\n onChange={(event) => {\n setQuery(event.target.value);\n setActive(-1);\n setOpen(true);\n }}\n onFocus={() => setOpen(true)}\n onBlur={() => setOpen(false)}\n onKeyDown={onKeyDown}\n />\n )}\n {kind && !readOnly ? (\n <button\n type=\"button\"\n className=\"bl-link-field__clear\"\n aria-label=\"Clear\"\n onClick={clear}\n >\n ×\n </button>\n ) : null}\n {open && !kind ? (\n <ul id={listId} role=\"listbox\" className=\"bl-link-field__menu\">\n {options.map((option, i) => (\n <li\n key={option.kind === \"page\" ? String(option.page.id) : option.href}\n id={`${id}-option-${i}`}\n role=\"option\"\n aria-selected={i === active}\n className=\"bl-link-field__option\"\n onMouseDown={(event) => {\n event.preventDefault();\n choose(option);\n }}\n onMouseEnter={() => setActive(i)}\n >\n {option.kind === \"page\" ? (\n <>\n <span>\n {String(option.page[titleKey as keyof LinkPageDoc] ?? option.page.id)}\n </span>\n <span className=\"bl-link-field__path\">{pagePath(option.page)}</span>\n </>\n ) : (\n <>\n <span>Link to this URL</span>\n <span className=\"bl-link-field__path\">{option.href}</span>\n </>\n )}\n </li>\n ))}\n {options.length === 0 && !loading ? (\n <li role=\"presentation\" className=\"bl-link-field__empty\">\n {NO_MATCH}\n </li>\n ) : null}\n </ul>\n ) : null}\n </div>\n {kind === \"page\" && stored && (stored.missing || stored.doc?._status === \"draft\") ? (\n <div role=\"status\" className=\"bl-link-field__warning\">\n {stored.missing ? MISSING : UNPUBLISHED}\n </div>\n ) : null}\n <FieldDescription description={DESCRIPTION} path={paths.page} />\n <FieldError\n path={error.path}\n showError={error.showError}\n message={error.errorMessage}\n />\n </div>\n );\n};\n\n/** Theme variables only: a literal colour would be right in one theme and wrong in the other. */\nconst CSS = `\n.bl-link-field__control{position:relative;display:flex;align-items:center;gap:calc(var(--base) * .4);min-height:calc(var(--base) * 2);padding:calc(var(--base) * .25) calc(var(--base) * .6);border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-input-bg);color:var(--theme-elevation-800);font-family:var(--font-body)}\n.bl-link-field__control:focus-within{border-color:var(--theme-elevation-400);box-shadow:0 0 0 1px var(--theme-elevation-400)}\n.bl-link-field__input{flex:1;min-width:0;border:0;background:transparent;color:inherit;font:inherit;outline:none}\n.bl-link-field__input::placeholder{color:var(--theme-elevation-400)}\n.bl-link-field__input:disabled{color:var(--theme-elevation-400)}\n.bl-link-field__kind{flex:none;padding:2px 6px;border-radius:var(--style-radius-s);background:var(--theme-elevation-100);color:var(--theme-elevation-600);font-size:10.5px;letter-spacing:.05em;text-transform:uppercase}\n.bl-link-field__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}\n.bl-link-field__path{margin-left:calc(var(--base) * .4);color:var(--theme-elevation-500);font-family:var(--font-mono);font-size:11.5px;font-weight:400}\n.bl-link-field__clear{flex:none;width:calc(var(--base) * 1.2);height:calc(var(--base) * 1.2);border:0;border-radius:var(--style-radius-s);background:transparent;color:var(--theme-elevation-500);font-size:18px;line-height:1;cursor:pointer}\n.bl-link-field__clear:hover,.bl-link-field__clear:focus-visible{background:var(--theme-elevation-100);color:var(--theme-elevation-800);outline:none}\n.bl-link-field__menu{position:absolute;left:-1px;right:-1px;top:calc(100% + 4px);z-index:10;margin:0;padding:calc(var(--base) * .2) 0;list-style:none;border:1px solid var(--theme-elevation-150);border-radius:var(--style-radius-s);background:var(--theme-elevation-0);box-shadow:0 8px 24px var(--theme-overlay)}\n.bl-link-field__option{display:flex;justify-content:space-between;gap:calc(var(--base) * .6);padding:calc(var(--base) * .35) calc(var(--base) * .6);cursor:pointer}\n.bl-link-field__option[aria-selected=\"true\"]{background:var(--theme-elevation-100)}\n.bl-link-field__empty{padding:calc(var(--base) * .5) calc(var(--base) * .6);color:var(--theme-elevation-500)}\n.bl-link-field__warning{display:flex;align-items:baseline;gap:calc(var(--base) * .3);margin-top:calc(var(--base) * .3);color:var(--theme-warning-600);font-size:12px}\n.bl-link-field__warning::before{content:\"\";flex:none;width:8px;height:8px;border-radius:50%;background:var(--theme-warning-500);transform:translateY(-1px)}\n`;\n","import { ArrayField, toast, useField, useTranslation } from \"@payloadcms/ui\";\nimport type { ArrayFieldClientComponent, ArrayFieldClientProps } from \"payload\";\nimport { useCallback, useRef } from \"react\";\n\n/**\n * Payload's array field, with *Remove* refused once the rows are down to\n * `minRows`.\n *\n * This wraps the stock `ArrayField` rather than re-implementing it, so every\n * upstream behaviour (sorting, copy/paste, collapse, localisation) stays put\n * and a Payload upgrade carries it along. The catch is where the row menu\n * lives: Payload portals it into `document.body`, out of reach of any CSS or\n * form context this wrapper could scope. What still reaches the wrapper is\n * the React event: synthetic events bubble through portals along the\n * component tree, so a capture handler here sees the click on *Remove*\n * before the menu's own handler does, and can stop it.\n *\n * Because the menu is portaled, the DOM cannot say whether a given *Remove*\n * belongs to this array or to an array nested inside one of its rows. The\n * trigger that opened the menu *is* in the DOM, though, and every menu opens\n * from its trigger. So the handler remembers, per menu opening, whether the\n * trigger sat directly in this field, and only gates the *Remove* that follows.\n */\nexport const MinRowsArrayField: ArrayFieldClientComponent = (props) => {\n const { field, path: pathFromProps } = props;\n const minRows = field.minRows ?? (field.required ? 1 : 0);\n\n const { rows = [] } = useField({\n hasRows: true,\n potentiallyStalePath: pathFromProps,\n });\n const { i18n, t } = useTranslation();\n\n const rootRef = useRef<HTMLDivElement>(null);\n const menuIsOurs = useRef(false);\n\n const onClickCapture = useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n\n const trigger = target.closest(\".array-actions__button\");\n if (trigger) {\n // The nearest array or blocks field around the trigger is the field\n // whose menu is opening. Ours is the first one inside the wrapper.\n const owner = trigger.closest(\".array-field, .blocks-field\");\n menuIsOurs.current =\n owner !== null && owner === rootRef.current?.querySelector(\".array-field\");\n return;\n }\n\n if (!target.closest(\".array-actions__remove\")) return;\n if (!menuIsOurs.current || rows.length > minRows) return;\n\n event.preventDefault();\n event.stopPropagation();\n toast.error(\n t(\"validation:requiresAtLeast\", {\n count: minRows,\n label: rowLabel(field, minRows, i18n.language) ??\n t(minRows > 1 ? \"general:rows\" : \"general:row\"),\n }),\n );\n },\n [field, i18n.language, minRows, rows.length, t],\n );\n\n return (\n <div ref={rootRef} onClickCapture={onClickCapture}>\n <ArrayField {...props} />\n </div>\n );\n};\n\n/**\n * The word for a row, as Payload's own \"requires at least\" banner picks it:\n * the plural label above one, the singular otherwise, the field label failing\n * both. Client-side labels are static — a string or a per-language record.\n */\nfunction rowLabel(\n field: ArrayFieldClientProps[\"field\"],\n minRows: number,\n language: string,\n): string | undefined {\n const label = minRows > 1 ? field.labels?.plural : field.labels?.singular;\n return staticLabel(label, language) ?? staticLabel(field.label, language);\n}\n\nfunction staticLabel(label: unknown, language: string): string | undefined {\n if (typeof label === \"string\") return label;\n if (label && typeof label === \"object\") {\n const byLanguage = label as Record<string, string | undefined>;\n return byLanguage[language] ?? byLanguage.en ?? Object.values(byLanguage)[0];\n }\n return undefined;\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArrayFieldValidation, Block, CollectionSlug, Condition, Field, GroupField, TextFieldSingleValidation, UploadField } from "payload";
|
|
1
|
+
import { ArrayFieldValidation, Block, CollectionSlug, Condition, Field, GroupField, RowField, TextFieldSingleValidation, UploadField } from "payload";
|
|
2
2
|
|
|
3
3
|
//#region src/blocks/hero/config.d.ts
|
|
4
4
|
/**
|
|
@@ -65,7 +65,7 @@ declare const StatsBandBlock: Block;
|
|
|
65
65
|
* backgrounds. `LinkBlock` is the plain bar item beside it.
|
|
66
66
|
*
|
|
67
67
|
* Until SPI-52 lands in the Spinal Simplicity repo, no site renders its header
|
|
68
|
-
* from these rows; the Storybook `Blocks/
|
|
68
|
+
* from these rows; the Storybook `CMS Blocks/MegaMenu` fixture is the only
|
|
69
69
|
* header built from them.
|
|
70
70
|
*/
|
|
71
71
|
/** One approved value, as a select option. */
|
|
@@ -127,26 +127,114 @@ interface ImageFieldOptions extends Partial<MediaUploadField> {
|
|
|
127
127
|
declare function imageField(overrides?: ImageFieldOptions): MediaUploadField;
|
|
128
128
|
//#endregion
|
|
129
129
|
//#region src/fields/link.d.ts
|
|
130
|
-
|
|
130
|
+
/**
|
|
131
|
+
* The import-map path of the link picker, `LinkField` in
|
|
132
|
+
* `@bison-lab/payload-blocks/admin`.
|
|
133
|
+
*
|
|
134
|
+
* It sits on the row that holds a link's `type`, `page` and `href`, and
|
|
135
|
+
* replaces those three inputs with one box: type to search published pages,
|
|
136
|
+
* or paste a URL. Referenced here by package specifier, like
|
|
137
|
+
* `MIN_ROWS_ARRAY_FIELD`; a site picks it up with `payload generate:importmap`.
|
|
138
|
+
* Until it does, Payload logs the missing entry and renders the row as
|
|
139
|
+
* nothing (a custom `Field` with no component behind it is an empty element,
|
|
140
|
+
* not the stock fields), so the step is part of adopting this release.
|
|
141
|
+
*/
|
|
142
|
+
declare const LINK_FIELD = "@bison-lab/payload-blocks/admin#LinkField";
|
|
143
|
+
type LinkType = "page" | "external";
|
|
144
|
+
/**
|
|
145
|
+
* A page as the `page` relationship populates it: what `resolveLink` reads,
|
|
146
|
+
* and what the picker shows. A site's generated `Page` is assignable to it.
|
|
147
|
+
* `_status` is absent on a collection without drafts, which counts as
|
|
148
|
+
* published.
|
|
149
|
+
*/
|
|
150
|
+
interface LinkPageDoc {
|
|
151
|
+
id: number | string;
|
|
152
|
+
title?: string | null;
|
|
153
|
+
slug?: string | null;
|
|
154
|
+
_status?: ("draft" | "published") | null;
|
|
155
|
+
}
|
|
156
|
+
/** Where a link goes: the three fields the picker writes. */
|
|
157
|
+
interface LinkDestination {
|
|
158
|
+
/** `page` unless the editor pasted a URL. Rows saved before links had a type carry only `href`. */
|
|
159
|
+
type?: LinkType | null;
|
|
160
|
+
/** The page's id, or the page itself once a `depth >= 1` read populates it. */
|
|
161
|
+
page?: LinkPageDoc | number | string | null;
|
|
162
|
+
/** The pasted URL of an external link. */
|
|
163
|
+
href?: string | null;
|
|
164
|
+
}
|
|
165
|
+
/** The shape `linkField`/`linkFields` produce once Payload generates types. */
|
|
166
|
+
interface LinkValue extends LinkDestination {
|
|
167
|
+
label?: string | null;
|
|
168
|
+
newTab?: boolean | null;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Turns a link's destination into the `href` a renderer emits, or `null`
|
|
172
|
+
* when there is nothing to point at, in which case the renderer shows the
|
|
173
|
+
* label as text. A site passes its own (`RenderBlocks`' `resolveLink`) when
|
|
174
|
+
* a page's path is not `/<slug>`: the package has no way to know a site's
|
|
175
|
+
* routes, the same reason it takes an `imageComponent`.
|
|
176
|
+
*/
|
|
177
|
+
type ResolveLink = (link: LinkDestination) => string | null;
|
|
178
|
+
/**
|
|
179
|
+
* The package's resolver: a published, populated page is `/<slug>`; an
|
|
180
|
+
* external link is its `href` as typed.
|
|
181
|
+
*
|
|
182
|
+
* A page that did not populate is `null`: Payload hands the public read the
|
|
183
|
+
* bare id when the reader may not see the page, which is what an unpublished
|
|
184
|
+
* page looks like from the site. A populated page whose `_status` is `draft`
|
|
185
|
+
* is `null` too, for a read that can see drafts. A row from before links had
|
|
186
|
+
* a `type` is an external link by `linkTypeOf`, here, in the stock fields'
|
|
187
|
+
* conditions and in the picker alike: the site keeps rendering its `href`,
|
|
188
|
+
* the admin shows it as an External chip, and the site's migration to
|
|
189
|
+
* `type: external` only makes the stored row say what every reader already
|
|
190
|
+
* assumes.
|
|
191
|
+
*/
|
|
192
|
+
declare function resolveLink(link: LinkDestination | null | undefined): string | null;
|
|
193
|
+
/**
|
|
194
|
+
* Which of the two stored destinations a row is using. `type` decides; a row
|
|
195
|
+
* with no `type` yet (saved before links had one) is read by what it holds,
|
|
196
|
+
* an `href` making it external, the same reading `resolveLink` and the picker
|
|
197
|
+
* make, so all three agree on every row.
|
|
198
|
+
*/
|
|
199
|
+
declare function linkTypeOf(link: LinkDestination | null | undefined): LinkType;
|
|
200
|
+
interface LinkDestinationOptions {
|
|
201
|
+
/** Require a destination. Defaults to `false`. */
|
|
202
|
+
required?: boolean;
|
|
203
|
+
/** The collection a page link points into. Defaults to `pages`. */
|
|
204
|
+
pagesCollection?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Where a link goes, as stored: `type`, `page` and `href` in one row.
|
|
208
|
+
*
|
|
209
|
+
* The row carries the picker (`LINK_FIELD`), which replaces all three inputs
|
|
210
|
+
* with one box. The stored fields are the website template's shape and stay
|
|
211
|
+
* editable on their own: `page` shows for a page link, `href` for an external
|
|
212
|
+
* one, and `required` binds whichever is active, since Payload skips
|
|
213
|
+
* validation on a field whose condition is false. `page` offers published
|
|
214
|
+
* rows only, and Payload re-checks that on publish, so a page that was
|
|
215
|
+
* unpublished after being picked has to be picked again or republished.
|
|
216
|
+
*/
|
|
217
|
+
declare function linkDestinationFields({
|
|
218
|
+
required,
|
|
219
|
+
pagesCollection
|
|
220
|
+
}?: LinkDestinationOptions): RowField[];
|
|
221
|
+
interface LinkFieldsOptions extends LinkDestinationOptions {
|
|
131
222
|
/** Require the label and destination. Defaults to `false`. */
|
|
132
223
|
required?: boolean;
|
|
133
224
|
/** Wording for the visible text field. Defaults to "Label". */
|
|
134
225
|
labelFieldLabel?: string;
|
|
135
226
|
}
|
|
136
227
|
/**
|
|
137
|
-
* The
|
|
138
|
-
*
|
|
139
|
-
* `href` is plain text rather than a relationship to a `pages` row: a block in
|
|
140
|
-
* this package has to work on a site whose page collection is named something
|
|
141
|
-
* else, or which links out more often than in. Resolving an internal reference
|
|
142
|
-
* is the site's job.
|
|
228
|
+
* The fields a call to action is made of, unwrapped: the destination row,
|
|
229
|
+
* then the label and the new-tab flag.
|
|
143
230
|
*
|
|
144
231
|
* `newTab` drives `target="_blank"` *and* the matching `rel`, which is why no
|
|
145
232
|
* renderer takes one without the other.
|
|
146
233
|
*/
|
|
147
234
|
declare function linkFields({
|
|
148
235
|
required,
|
|
149
|
-
labelFieldLabel
|
|
236
|
+
labelFieldLabel,
|
|
237
|
+
pagesCollection
|
|
150
238
|
}?: LinkFieldsOptions): Field[];
|
|
151
239
|
interface LinkFieldOptions extends LinkFieldsOptions {
|
|
152
240
|
/** Field name. Defaults to `link`. */
|
|
@@ -161,12 +249,6 @@ declare function linkField({
|
|
|
161
249
|
admin,
|
|
162
250
|
...rest
|
|
163
251
|
}?: LinkFieldOptions): GroupField;
|
|
164
|
-
/** The shape `linkField`/`linkFields` produce once Payload generates types. */
|
|
165
|
-
interface LinkValue {
|
|
166
|
-
label?: string | null;
|
|
167
|
-
href?: string | null;
|
|
168
|
-
newTab?: boolean | null;
|
|
169
|
-
}
|
|
170
252
|
//#endregion
|
|
171
253
|
//#region src/fields/heading.d.ts
|
|
172
254
|
interface HeadingFieldsOptions {
|
|
@@ -359,12 +441,12 @@ interface FaqColumnsBlockData extends BlockRow<"faqColumns"> {
|
|
|
359
441
|
title?: string | null;
|
|
360
442
|
description?: string | null;
|
|
361
443
|
items?: FaqColumnsItemData[] | null;
|
|
444
|
+
/** A link whose visible text is `linkText`, not `label`; the destination is `linkFields()`' shape. */
|
|
362
445
|
cta?: {
|
|
363
446
|
title?: string | null;
|
|
364
447
|
linkText?: string | null;
|
|
365
|
-
href?: string | null;
|
|
366
448
|
newTab?: boolean | null;
|
|
367
|
-
};
|
|
449
|
+
} & LinkDestination;
|
|
368
450
|
sticky?: boolean | null;
|
|
369
451
|
type?: ("single" | "multiple") | null;
|
|
370
452
|
collapsible?: boolean | null;
|
|
@@ -541,5 +623,5 @@ declare function sampleImage({
|
|
|
541
623
|
alt
|
|
542
624
|
}: SampleImageOptions): MediaDoc;
|
|
543
625
|
//#endregion
|
|
544
|
-
export { type BisonBlockData, type BisonBlockType, type BlockSamples, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, LinkBlock, type LinkFieldOptions, type LinkFieldsOptions, type LinkValue, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, type MediaDoc, type MediaValue, type MegaMenuBlockData, type MegaMenuBlockOptions, type MegaMenuColumnData, type MegaMenuLinkData, type MegaMenuOption, type MegaMenuSectionData, type MegaMenuWidthValue, NapBlock, type NapBlockData, type NapDepartmentData, type NavLinkBlockData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, type SampleImageOptions, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, StatsBandBlock, type StatsBandBlockData, type StatsBandItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, blockSamples, emptyRows, headingFields, imageField, linkField, linkFields, megaMenuBlock, megaMenuSampleOptions, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
|
|
626
|
+
export { type BisonBlockData, type BisonBlockType, type BlockSamples, FaqColumnsBlock, type FaqColumnsBlockData, type FaqColumnsItemData, type HeadingFieldsOptions, HeroBlock, type HeroBlockData, type ImageFieldOptions, LINK_FIELD, LinkBlock, type LinkDestination, type LinkDestinationOptions, type LinkFieldOptions, type LinkFieldsOptions, type LinkPageDoc, type LinkType, type LinkValue, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, type MediaDoc, type MediaValue, type MegaMenuBlockData, type MegaMenuBlockOptions, type MegaMenuColumnData, type MegaMenuLinkData, type MegaMenuOption, type MegaMenuSectionData, type MegaMenuWidthValue, NapBlock, type NapBlockData, type NapDepartmentData, type NavLinkBlockData, ProcessStepsBlock, type ProcessStepsBlockData, type ProcessStepsItemData, type ResolveLink, type ResolvedMedia, RichTextBlock, type RichTextBlockData, type RichTextContent, type SampleImageOptions, ShowcasePanelsBlock, type ShowcasePanelsBlockData, type ShowcasePanelsItemData, StatsBandBlock, type StatsBandBlockData, type StatsBandItemData, TestimonialMasonryBlock, type TestimonialMasonryBlockData, type TestimonialMasonryItemData, blockSamples, emptyRows, headingFields, imageField, linkDestinationFields, linkField, linkFields, linkTypeOf, megaMenuBlock, megaMenuSampleOptions, resolveLink, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
|
|
545
627
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks/hero/config.ts","../src/blocks/rich-text/config.ts","../src/blocks/showcase-panels/config.ts","../src/blocks/process-steps/config.ts","../src/blocks/faq-columns/config.ts","../src/blocks/testimonial-masonry/config.ts","../src/blocks/nap/config.ts","../src/blocks/stats-band/config.ts","../src/blocks/mega-menu/config.ts","../src/fields/image.ts","../src/fields/link.ts","../src/fields/heading.ts","../src/fields/min-rows.ts","../src/media.ts","../src/types.ts","../src/samples.ts","../src/blocks/mega-menu/sample.ts","../src/sample-image.ts"],"mappings":";;;;;AAcA;;;;;;;cAAa,SAAA,EAAW,KAAA;;;;cCXX,aAAA,EAAe,KAAA;;;;cCGf,mBAAA,EAAqB,KAAA;;;;cCArB,iBAAA,EAAmB,KAAA;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks/hero/config.ts","../src/blocks/rich-text/config.ts","../src/blocks/showcase-panels/config.ts","../src/blocks/process-steps/config.ts","../src/blocks/faq-columns/config.ts","../src/blocks/testimonial-masonry/config.ts","../src/blocks/nap/config.ts","../src/blocks/stats-band/config.ts","../src/blocks/mega-menu/config.ts","../src/fields/image.ts","../src/fields/link.ts","../src/fields/heading.ts","../src/fields/min-rows.ts","../src/media.ts","../src/types.ts","../src/samples.ts","../src/blocks/mega-menu/sample.ts","../src/sample-image.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -19,30 +19,125 @@ function imageField(overrides = {}) {
|
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/fields/link.ts
|
|
21
21
|
/**
|
|
22
|
-
* The
|
|
22
|
+
* The import-map path of the link picker, `LinkField` in
|
|
23
|
+
* `@bison-lab/payload-blocks/admin`.
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* It sits on the row that holds a link's `type`, `page` and `href`, and
|
|
26
|
+
* replaces those three inputs with one box: type to search published pages,
|
|
27
|
+
* or paste a URL. Referenced here by package specifier, like
|
|
28
|
+
* `MIN_ROWS_ARRAY_FIELD`; a site picks it up with `payload generate:importmap`.
|
|
29
|
+
* Until it does, Payload logs the missing entry and renders the row as
|
|
30
|
+
* nothing (a custom `Field` with no component behind it is an empty element,
|
|
31
|
+
* not the stock fields), so the step is part of adopting this release.
|
|
32
|
+
*/
|
|
33
|
+
const LINK_FIELD = "@bison-lab/payload-blocks/admin#LinkField";
|
|
34
|
+
/**
|
|
35
|
+
* The package's resolver: a published, populated page is `/<slug>`; an
|
|
36
|
+
* external link is its `href` as typed.
|
|
37
|
+
*
|
|
38
|
+
* A page that did not populate is `null`: Payload hands the public read the
|
|
39
|
+
* bare id when the reader may not see the page, which is what an unpublished
|
|
40
|
+
* page looks like from the site. A populated page whose `_status` is `draft`
|
|
41
|
+
* is `null` too, for a read that can see drafts. A row from before links had
|
|
42
|
+
* a `type` is an external link by `linkTypeOf`, here, in the stock fields'
|
|
43
|
+
* conditions and in the picker alike: the site keeps rendering its `href`,
|
|
44
|
+
* the admin shows it as an External chip, and the site's migration to
|
|
45
|
+
* `type: external` only makes the stored row say what every reader already
|
|
46
|
+
* assumes.
|
|
47
|
+
*/
|
|
48
|
+
function resolveLink(link) {
|
|
49
|
+
if (!link) return null;
|
|
50
|
+
if (linkTypeOf(link) === "page") {
|
|
51
|
+
const page = link.page;
|
|
52
|
+
if (!page || typeof page !== "object") return null;
|
|
53
|
+
if (page._status === "draft") return null;
|
|
54
|
+
return page.slug ? `/${page.slug}` : null;
|
|
55
|
+
}
|
|
56
|
+
return link.href ? link.href : null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Which of the two stored destinations a row is using. `type` decides; a row
|
|
60
|
+
* with no `type` yet (saved before links had one) is read by what it holds,
|
|
61
|
+
* an `href` making it external, the same reading `resolveLink` and the picker
|
|
62
|
+
* make, so all three agree on every row.
|
|
63
|
+
*/
|
|
64
|
+
function linkTypeOf(link) {
|
|
65
|
+
if (link?.type === "external") return "external";
|
|
66
|
+
if (link?.type === "page") return "page";
|
|
67
|
+
return link?.href ? "external" : "page";
|
|
68
|
+
}
|
|
69
|
+
const whenPage = (_data, siblingData) => linkTypeOf(siblingData) === "page";
|
|
70
|
+
const whenExternal = (_data, siblingData) => linkTypeOf(siblingData) === "external";
|
|
71
|
+
/**
|
|
72
|
+
* Where a link goes, as stored: `type`, `page` and `href` in one row.
|
|
73
|
+
*
|
|
74
|
+
* The row carries the picker (`LINK_FIELD`), which replaces all three inputs
|
|
75
|
+
* with one box. The stored fields are the website template's shape and stay
|
|
76
|
+
* editable on their own: `page` shows for a page link, `href` for an external
|
|
77
|
+
* one, and `required` binds whichever is active, since Payload skips
|
|
78
|
+
* validation on a field whose condition is false. `page` offers published
|
|
79
|
+
* rows only, and Payload re-checks that on publish, so a page that was
|
|
80
|
+
* unpublished after being picked has to be picked again or republished.
|
|
81
|
+
*/
|
|
82
|
+
function linkDestinationFields({ required = false, pagesCollection = "pages" } = {}) {
|
|
83
|
+
return [{
|
|
84
|
+
type: "row",
|
|
85
|
+
admin: { components: { Field: LINK_FIELD } },
|
|
86
|
+
fields: [
|
|
87
|
+
{
|
|
88
|
+
name: "type",
|
|
89
|
+
type: "radio",
|
|
90
|
+
label: "Goes to",
|
|
91
|
+
defaultValue: "page",
|
|
92
|
+
options: [{
|
|
93
|
+
label: "Page",
|
|
94
|
+
value: "page"
|
|
95
|
+
}, {
|
|
96
|
+
label: "URL",
|
|
97
|
+
value: "external"
|
|
98
|
+
}],
|
|
99
|
+
admin: { layout: "horizontal" }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "page",
|
|
103
|
+
type: "relationship",
|
|
104
|
+
relationTo: pagesCollection,
|
|
105
|
+
required,
|
|
106
|
+
filterOptions: { _status: { equals: "published" } },
|
|
107
|
+
admin: { condition: whenPage }
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "href",
|
|
111
|
+
type: "text",
|
|
112
|
+
label: "URL",
|
|
113
|
+
required,
|
|
114
|
+
admin: {
|
|
115
|
+
condition: whenExternal,
|
|
116
|
+
description: "A full URL (\"https://example.com\"), \"mailto:\" or \"tel:\", or a site path (\"/contact\")."
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The fields a call to action is made of, unwrapped: the destination row,
|
|
124
|
+
* then the label and the new-tab flag.
|
|
28
125
|
*
|
|
29
126
|
* `newTab` drives `target="_blank"` *and* the matching `rel`, which is why no
|
|
30
127
|
* renderer takes one without the other.
|
|
31
128
|
*/
|
|
32
|
-
function linkFields({ required = false, labelFieldLabel = "Label" } = {}) {
|
|
129
|
+
function linkFields({ required = false, labelFieldLabel = "Label", pagesCollection } = {}) {
|
|
33
130
|
return [
|
|
131
|
+
...linkDestinationFields({
|
|
132
|
+
required,
|
|
133
|
+
pagesCollection
|
|
134
|
+
}),
|
|
34
135
|
{
|
|
35
136
|
name: "label",
|
|
36
137
|
type: "text",
|
|
37
138
|
label: labelFieldLabel,
|
|
38
139
|
required
|
|
39
140
|
},
|
|
40
|
-
{
|
|
41
|
-
name: "href",
|
|
42
|
-
type: "text",
|
|
43
|
-
required,
|
|
44
|
-
admin: { description: "A site path (\"/contact\") or a full URL (\"https://example.com\")." }
|
|
45
|
-
},
|
|
46
141
|
{
|
|
47
142
|
name: "newTab",
|
|
48
143
|
type: "checkbox",
|
|
@@ -395,10 +490,7 @@ const FaqColumnsBlock = {
|
|
|
395
490
|
name: "linkText",
|
|
396
491
|
type: "text"
|
|
397
492
|
},
|
|
398
|
-
|
|
399
|
-
name: "href",
|
|
400
|
-
type: "text"
|
|
401
|
-
},
|
|
493
|
+
...linkDestinationFields(),
|
|
402
494
|
{
|
|
403
495
|
name: "newTab",
|
|
404
496
|
type: "checkbox",
|
|
@@ -1148,6 +1240,7 @@ const faqColumnsSample = {
|
|
|
1148
1240
|
cta: {
|
|
1149
1241
|
title: "Still have questions?",
|
|
1150
1242
|
linkText: "Call the front desk",
|
|
1243
|
+
type: "external",
|
|
1151
1244
|
href: "tel:+13035550142",
|
|
1152
1245
|
newTab: false
|
|
1153
1246
|
},
|
|
@@ -1202,12 +1295,19 @@ const heroSample = {
|
|
|
1202
1295
|
alt: "A bright, open treatment room"
|
|
1203
1296
|
}),
|
|
1204
1297
|
links: [{
|
|
1298
|
+
type: "page",
|
|
1299
|
+
page: {
|
|
1300
|
+
id: "sample-page-book",
|
|
1301
|
+
title: "Book an assessment",
|
|
1302
|
+
slug: "book",
|
|
1303
|
+
_status: "published"
|
|
1304
|
+
},
|
|
1205
1305
|
label: "Book an assessment",
|
|
1206
|
-
href: "/book",
|
|
1207
1306
|
newTab: false
|
|
1208
1307
|
}, {
|
|
1209
|
-
|
|
1308
|
+
type: "external",
|
|
1210
1309
|
href: "https://example.com/approach",
|
|
1310
|
+
label: "See our approach",
|
|
1211
1311
|
newTab: true
|
|
1212
1312
|
}]
|
|
1213
1313
|
};
|
|
@@ -1238,7 +1338,10 @@ const megaMenuSampleOptions = {
|
|
|
1238
1338
|
* Spinal Simplicity's Patients panel as a saved row: two featured territories
|
|
1239
1339
|
* in a 75% column, two rail sections in a divided 25% column, overview and
|
|
1240
1340
|
* CTA. Every field is set at least once; the Advanced fields are set but off,
|
|
1241
|
-
* so the percent widths are what renders.
|
|
1341
|
+
* so the percent widths are what renders. Most links are site paths typed as
|
|
1342
|
+
* external links; Testimonials and the overview are page links carried
|
|
1343
|
+
* inline, as `depth: 1` would populate them, so the preview needs no pages
|
|
1344
|
+
* collection.
|
|
1242
1345
|
*/
|
|
1243
1346
|
const megaMenuSample = {
|
|
1244
1347
|
id: "sample-mega-menu",
|
|
@@ -1260,6 +1363,7 @@ const megaMenuSample = {
|
|
|
1260
1363
|
links: [{
|
|
1261
1364
|
id: "lumbar",
|
|
1262
1365
|
label: "Low Back Pain",
|
|
1366
|
+
type: "external",
|
|
1263
1367
|
href: "/patients/low-back-pain",
|
|
1264
1368
|
newTab: false,
|
|
1265
1369
|
description: "Persistent low back pain from lumbar instability, often with leg pain that limits standing or walking.",
|
|
@@ -1267,6 +1371,7 @@ const megaMenuSample = {
|
|
|
1267
1371
|
}, {
|
|
1268
1372
|
id: "si",
|
|
1269
1373
|
label: "Hip Pain",
|
|
1374
|
+
type: "external",
|
|
1270
1375
|
href: "/patients/hip-pain",
|
|
1271
1376
|
newTab: false,
|
|
1272
1377
|
description: "Pain centered on the sacroiliac joint, often worse with sitting or climbing stairs.",
|
|
@@ -1287,18 +1392,26 @@ const megaMenuSample = {
|
|
|
1287
1392
|
{
|
|
1288
1393
|
id: "stories",
|
|
1289
1394
|
label: "Testimonials",
|
|
1290
|
-
|
|
1395
|
+
type: "page",
|
|
1396
|
+
page: {
|
|
1397
|
+
id: "sample-page-stories",
|
|
1398
|
+
title: "Testimonials",
|
|
1399
|
+
slug: "patients/stories",
|
|
1400
|
+
_status: "published"
|
|
1401
|
+
},
|
|
1291
1402
|
newTab: false
|
|
1292
1403
|
},
|
|
1293
1404
|
{
|
|
1294
1405
|
id: "research",
|
|
1295
1406
|
label: "Research",
|
|
1407
|
+
type: "external",
|
|
1296
1408
|
href: "/patients/research",
|
|
1297
1409
|
newTab: false
|
|
1298
1410
|
},
|
|
1299
1411
|
{
|
|
1300
1412
|
id: "path",
|
|
1301
1413
|
label: "Path to Relief",
|
|
1414
|
+
type: "external",
|
|
1302
1415
|
href: "/patients/path-to-relief",
|
|
1303
1416
|
newTab: false
|
|
1304
1417
|
}
|
|
@@ -1311,6 +1424,7 @@ const megaMenuSample = {
|
|
|
1311
1424
|
links: [{
|
|
1312
1425
|
id: "faqs",
|
|
1313
1426
|
label: "FAQs",
|
|
1427
|
+
type: "external",
|
|
1314
1428
|
href: "/patients/faqs",
|
|
1315
1429
|
newTab: false,
|
|
1316
1430
|
description: "Short answers to the questions patients ask first.",
|
|
@@ -1318,6 +1432,7 @@ const megaMenuSample = {
|
|
|
1318
1432
|
}, {
|
|
1319
1433
|
id: "contact",
|
|
1320
1434
|
label: "Contact",
|
|
1435
|
+
type: "external",
|
|
1321
1436
|
href: "https://example.com/contact",
|
|
1322
1437
|
newTab: true,
|
|
1323
1438
|
icon: "mail"
|
|
@@ -1327,11 +1442,18 @@ const megaMenuSample = {
|
|
|
1327
1442
|
footer: {
|
|
1328
1443
|
overview: {
|
|
1329
1444
|
label: "All patient resources",
|
|
1330
|
-
|
|
1445
|
+
type: "page",
|
|
1446
|
+
page: {
|
|
1447
|
+
id: "sample-page-patients",
|
|
1448
|
+
title: "Patients",
|
|
1449
|
+
slug: "patients",
|
|
1450
|
+
_status: "published"
|
|
1451
|
+
},
|
|
1331
1452
|
newTab: false
|
|
1332
1453
|
},
|
|
1333
1454
|
cta: {
|
|
1334
1455
|
label: "Find a Doctor",
|
|
1456
|
+
type: "external",
|
|
1335
1457
|
href: "/find-a-doctor",
|
|
1336
1458
|
newTab: false
|
|
1337
1459
|
}
|
|
@@ -1345,6 +1467,7 @@ const navLinkSample = {
|
|
|
1345
1467
|
id: "sample-nav-link",
|
|
1346
1468
|
blockType: "navLink",
|
|
1347
1469
|
label: "Contact",
|
|
1470
|
+
type: "external",
|
|
1348
1471
|
href: "/contact",
|
|
1349
1472
|
newTab: false
|
|
1350
1473
|
};
|
|
@@ -1714,8 +1837,9 @@ const blockSamples = {
|
|
|
1714
1837
|
}
|
|
1715
1838
|
],
|
|
1716
1839
|
link: {
|
|
1717
|
-
|
|
1840
|
+
type: "external",
|
|
1718
1841
|
href: "https://example.com/reviews",
|
|
1842
|
+
label: "Read every review",
|
|
1719
1843
|
newTab: true
|
|
1720
1844
|
},
|
|
1721
1845
|
minItemsForFade: 6,
|
|
@@ -1727,6 +1851,6 @@ const blockSamples = {
|
|
|
1727
1851
|
navLink: navLinkSample
|
|
1728
1852
|
};
|
|
1729
1853
|
//#endregion
|
|
1730
|
-
export { FaqColumnsBlock, HeroBlock, LinkBlock, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, StatsBandBlock, TestimonialMasonryBlock, blockSamples, emptyRows, headingFields, imageField, linkField, linkFields, megaMenuBlock, megaMenuSampleOptions, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
|
|
1854
|
+
export { FaqColumnsBlock, HeroBlock, LINK_FIELD, LinkBlock, MEGA_MENU_WIDTHS, MIN_ROWS_ARRAY_FIELD, NapBlock, ProcessStepsBlock, RichTextBlock, ShowcasePanelsBlock, StatsBandBlock, TestimonialMasonryBlock, blockSamples, emptyRows, headingFields, imageField, linkDestinationFields, linkField, linkFields, linkTypeOf, megaMenuBlock, megaMenuSampleOptions, resolveLink, resolveMedia, sampleImage, validateColumnWidths, validateRemWidth };
|
|
1731
1855
|
|
|
1732
1856
|
//# sourceMappingURL=index.mjs.map
|