@mxfb/tools 1.0.5 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/components/Date/index.d.ts +45 -0
  2. package/components/Date/index.js +65 -0
  3. package/components/Date/styles.module.css +0 -0
  4. package/components/Date/types.d.ts +39 -0
  5. package/components/Date/types.js +37 -0
  6. package/components/Embed/SpotifyEmbed/index.d.ts +13 -0
  7. package/components/Embed/SpotifyEmbed/index.js +38 -0
  8. package/components/Embed/SpotifyEmbed/styles.module.css +0 -0
  9. package/components/Embed/VimeoEmbed/index.d.ts +14 -0
  10. package/components/Embed/VimeoEmbed/index.js +40 -0
  11. package/components/Embed/VimeoEmbed/styles.module.css +0 -0
  12. package/components/Embed/YoutubeEmbed/index.d.ts +18 -0
  13. package/components/Embed/YoutubeEmbed/index.js +45 -0
  14. package/components/Embed/YoutubeEmbed/styles.module.css +0 -0
  15. package/components/Embed/index.d.ts +44 -0
  16. package/components/Embed/index.js +71 -0
  17. package/components/Embed/styles.module.css +0 -0
  18. package/components/Embed/types.d.ts +22 -0
  19. package/components/Embed/types.js +7 -0
  20. package/components/JsonEditor/index.d.ts +227 -0
  21. package/components/JsonEditor/index.js +281 -0
  22. package/components/JsonEditor/styles.module.css +0 -0
  23. package/components/JsonEditor/types.d.ts +5 -0
  24. package/components/JsonEditor/types.js +1 -0
  25. package/components/JsonEditor/utils.d.ts +3 -0
  26. package/components/JsonEditor/utils.js +18 -0
  27. package/components/ListLoader/index.controlled.d.ts +87 -0
  28. package/components/ListLoader/index.controlled.js +108 -0
  29. package/components/ListLoader/index.d.ts +76 -0
  30. package/components/ListLoader/index.js +162 -0
  31. package/components/ListLoader/styles.module.css +0 -0
  32. package/components/index.d.ts +4 -0
  33. package/components/index.js +4 -0
  34. package/components/public-classnames.d.ts +6 -0
  35. package/components/public-classnames.js +6 -0
  36. package/package.json +64 -2
@@ -0,0 +1,45 @@
1
+ import type { FunctionComponent } from 'react';
2
+ import type { WithClassName } from '../utils/types.js';
3
+ /**
4
+ * Props for the {@link DateComponent} component.
5
+ *
6
+ * @property date - The moment to render. An invalid `Date` renders nothing at all.
7
+ * @property format - The template, in `formatDate`'s own grammar:
8
+ * `'{{YYYY}}-{{MM}}-{{DD}}'`. Everything outside `{{…}}` is a literal.
9
+ * @property locale - Handed to `formatDate`, which localises month and weekday names.
10
+ * @property dateTime - The machine-readable value the `<time>` element carries. Left
11
+ * out, the date's ISO string is used; `null` leaves the attribute off, for a precision
12
+ * HTML has no form for — a month without its year, a day without its month.
13
+ */
14
+ export type Props = WithClassName<{
15
+ date: Date;
16
+ format: string;
17
+ locale?: string;
18
+ dateTime?: string | null;
19
+ }>;
20
+ /**
21
+ * `formatDate`, rendered one element per piece of the template rather than as a string.
22
+ *
23
+ * ### CSS elements
24
+ * - `part` — one per token, carrying as a modifier the unit it renders: `day`,
25
+ * `weekday`, `month`, `year`, `hours`, `minutes`, `seconds`, `milliseconds`,
26
+ * `meridiem`, `ordinal`. Carries `data-token` with the token itself.
27
+ * - `literal` — one per run of text between two tokens, separators included.
28
+ *
29
+ * @param props - Component properties.
30
+ * @see {@link Props}
31
+ * @returns A `<time>` element holding one element per piece of the template.
32
+ *
33
+ * @remarks
34
+ * Not one token's meaning is implemented here: each is handed back to `formatDate` on
35
+ * its own, so what `MMMM` says in French stays that function's business, and the text
36
+ * rendered is character-for-character what it returns for the same template.
37
+ *
38
+ * An unknown token comes out a literal, braces included, which is what `formatDate` does
39
+ * with one — a typo shows rather than silently rendering nothing.
40
+ *
41
+ * The root carries no modifier, the component holding no state of its own; and a token
42
+ * being a name rather than a value a stylesheet computes with, it goes to a `data-`
43
+ * attribute and not to a custom property.
44
+ */
45
+ export declare const DateComponent: FunctionComponent<Props>;
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { clss } from '@design-edito/tools/agnostic/css/clss/index.js';
3
+ import { formatDate } from '@design-edito/tools/agnostic/time/dates/format-date/index.js';
4
+ import { date as publicClassName } from '../public-classnames.js';
5
+ import { mergeClassNames } from '../utils/index.js';
6
+ import { dateTokenUnits } from './types.js';
7
+ import cssModule from './styles.module.css';
8
+ const templateToken = /\{\{([^\{\}]+)\}\}/gv;
9
+ function isDateToken(raw) {
10
+ return Object.hasOwn(dateTokenUnits, raw);
11
+ }
12
+ function toPieces(format) {
13
+ const pieces = [];
14
+ let cursor = 0;
15
+ for (const match of format.matchAll(templateToken)) {
16
+ const raw = match[1] ?? '';
17
+ if (match.index > cursor)
18
+ pieces.push({ kind: 'literal', text: format.slice(cursor, match.index) });
19
+ if (isDateToken(raw)) {
20
+ pieces.push({ kind: 'token', token: raw });
21
+ }
22
+ else {
23
+ pieces.push({ kind: 'literal', text: match[0] });
24
+ }
25
+ cursor = match.index + match[0].length;
26
+ }
27
+ if (cursor < format.length)
28
+ pieces.push({ kind: 'literal', text: format.slice(cursor) });
29
+ return pieces;
30
+ }
31
+ /**
32
+ * `formatDate`, rendered one element per piece of the template rather than as a string.
33
+ *
34
+ * ### CSS elements
35
+ * - `part` — one per token, carrying as a modifier the unit it renders: `day`,
36
+ * `weekday`, `month`, `year`, `hours`, `minutes`, `seconds`, `milliseconds`,
37
+ * `meridiem`, `ordinal`. Carries `data-token` with the token itself.
38
+ * - `literal` — one per run of text between two tokens, separators included.
39
+ *
40
+ * @param props - Component properties.
41
+ * @see {@link Props}
42
+ * @returns A `<time>` element holding one element per piece of the template.
43
+ *
44
+ * @remarks
45
+ * Not one token's meaning is implemented here: each is handed back to `formatDate` on
46
+ * its own, so what `MMMM` says in French stays that function's business, and the text
47
+ * rendered is character-for-character what it returns for the same template.
48
+ *
49
+ * An unknown token comes out a literal, braces included, which is what `formatDate` does
50
+ * with one — a typo shows rather than silently rendering nothing.
51
+ *
52
+ * The root carries no modifier, the component holding no state of its own; and a token
53
+ * being a name rather than a value a stylesheet computes with, it goes to a `data-`
54
+ * attribute and not to a custom property.
55
+ */
56
+ export const DateComponent = ({ className, date, format, locale, dateTime }) => {
57
+ if (Number.isNaN(date.getTime()))
58
+ return null;
59
+ const machineValue = dateTime === undefined ? date.toISOString() : dateTime;
60
+ const c = clss(publicClassName, { cssModule });
61
+ const rootClss = mergeClassNames(c(), className);
62
+ return _jsx("time", { className: rootClss, dateTime: machineValue ?? undefined, children: toPieces(format).map((piece, index) => piece.kind === 'literal'
63
+ ? _jsx("span", { className: c('literal'), children: piece.text }, index)
64
+ : _jsx("span", { className: c('part', dateTokenUnits[piece.token]), "data-token": piece.token, children: formatDate(date, `{{${piece.token}}}`, locale) }, index)) });
65
+ };
File without changes
@@ -0,0 +1,39 @@
1
+ /**
2
+ * What each token of a template is a token *of*.
3
+ *
4
+ * A part's modifier names the unit rather than the token, because what a stylesheet has
5
+ * an opinion about is the year, whether the template asked for `YY` or `YYYY`. The token
6
+ * itself stays on the element, as `data-token`, for the once in a while when the padding
7
+ * is the thing being styled.
8
+ *
9
+ * A table rather than a bare union: a consumer writing that stylesheet needs the values
10
+ * at runtime, and a type has nothing to hand them.
11
+ */
12
+ export declare const dateTokenUnits: {
13
+ readonly D: "day";
14
+ readonly DD: "day";
15
+ readonly d: "day";
16
+ readonly dd: "day";
17
+ readonly ddd: "weekday";
18
+ readonly dddd: "weekday";
19
+ readonly M: "month";
20
+ readonly MM: "month";
21
+ readonly MMM: "month";
22
+ readonly MMMM: "month";
23
+ readonly YY: "year";
24
+ readonly YYYY: "year";
25
+ readonly H: "hours";
26
+ readonly HH: "hours";
27
+ readonly h: "hours";
28
+ readonly hh: "hours";
29
+ readonly m: "minutes";
30
+ readonly mm: "minutes";
31
+ readonly s: "seconds";
32
+ readonly ss: "seconds";
33
+ readonly ms: "milliseconds";
34
+ readonly A: "meridiem";
35
+ readonly a: "meridiem";
36
+ readonly th: "ordinal";
37
+ };
38
+ /** The unit a part names, which is the modifier a stylesheet targets. */
39
+ export type DateUnit = typeof dateTokenUnits[keyof typeof dateTokenUnits];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * What each token of a template is a token *of*.
3
+ *
4
+ * A part's modifier names the unit rather than the token, because what a stylesheet has
5
+ * an opinion about is the year, whether the template asked for `YY` or `YYYY`. The token
6
+ * itself stays on the element, as `data-token`, for the once in a while when the padding
7
+ * is the thing being styled.
8
+ *
9
+ * A table rather than a bare union: a consumer writing that stylesheet needs the values
10
+ * at runtime, and a type has nothing to hand them.
11
+ */
12
+ export const dateTokenUnits = {
13
+ D: 'day',
14
+ DD: 'day',
15
+ d: 'day',
16
+ dd: 'day',
17
+ ddd: 'weekday',
18
+ dddd: 'weekday',
19
+ M: 'month',
20
+ MM: 'month',
21
+ MMM: 'month',
22
+ MMMM: 'month',
23
+ YY: 'year',
24
+ YYYY: 'year',
25
+ H: 'hours',
26
+ HH: 'hours',
27
+ h: 'hours',
28
+ hh: 'hours',
29
+ m: 'minutes',
30
+ mm: 'minutes',
31
+ s: 'seconds',
32
+ ss: 'seconds',
33
+ ms: 'milliseconds',
34
+ A: 'meridiem',
35
+ a: 'meridiem',
36
+ th: 'ordinal'
37
+ };
@@ -0,0 +1,13 @@
1
+ import type { FunctionComponent } from 'react';
2
+ import type { PlatformProps } from '../types.js';
3
+ /**
4
+ * A Spotify track, album, playlist, episode or show, embedded from its canonical URL.
5
+ *
6
+ * ### Root element modifiers
7
+ * - `--link` — nothing embeddable could be read, so a link is rendered instead.
8
+ *
9
+ * @param props - Component properties.
10
+ * @see {@link PlatformProps}
11
+ * @returns An `<iframe>` on `open.spotify.com/embed/…`, or an `<a>` on the URL as given.
12
+ */
13
+ export declare const SpotifyEmbed: FunctionComponent<PlatformProps>;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { clss } from '@design-edito/tools/agnostic/css/clss/index.js';
3
+ import { spotifyEmbed as publicClassName } from '../../public-classnames.js';
4
+ import { mergeClassNames } from '../../utils/index.js';
5
+ import cssModule from './styles.module.css';
6
+ // `/intl-fr/track/ID` is the shape a shared link carries these days, hence the locale.
7
+ function embedPath(raw) {
8
+ let url;
9
+ try {
10
+ url = new URL(raw);
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ const match = /^(?:\/intl-[a-z]{2})?\/(track|album|playlist|episode|show)\/([A-Za-z0-9]+)/v
16
+ .exec(url.pathname);
17
+ if (match === null)
18
+ return null;
19
+ return `${match[1]}/${match[2]}`;
20
+ }
21
+ /**
22
+ * A Spotify track, album, playlist, episode or show, embedded from its canonical URL.
23
+ *
24
+ * ### Root element modifiers
25
+ * - `--link` — nothing embeddable could be read, so a link is rendered instead.
26
+ *
27
+ * @param props - Component properties.
28
+ * @see {@link PlatformProps}
29
+ * @returns An `<iframe>` on `open.spotify.com/embed/…`, or an `<a>` on the URL as given.
30
+ */
31
+ export const SpotifyEmbed = ({ className, url, title }) => {
32
+ const path = embedPath(url);
33
+ const c = clss(publicClassName, { cssModule });
34
+ const rootClss = mergeClassNames(c(null, { link: path === null }), className);
35
+ if (path === null)
36
+ return _jsx("a", { className: rootClss, href: url, children: title });
37
+ return _jsx("iframe", { className: rootClss, src: `https://open.spotify.com/embed/${path}`, title: title, loading: "lazy", allow: "autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" });
38
+ };
@@ -0,0 +1,14 @@
1
+ import type { FunctionComponent } from 'react';
2
+ import type { PlatformProps } from '../types.js';
3
+ /**
4
+ * A Vimeo video, embedded from its canonical URL.
5
+ *
6
+ * ### Root element modifiers
7
+ * - `--link` — no video id could be read, so a link is rendered instead of a frame.
8
+ *
9
+ * @param props - Component properties.
10
+ * @see {@link PlatformProps}
11
+ * @returns An `<iframe>` on `player.vimeo.com/video/<id>`, or an `<a>` on the URL as
12
+ * given.
13
+ */
14
+ export declare const VimeoEmbed: FunctionComponent<PlatformProps>;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { clss } from '@design-edito/tools/agnostic/css/clss/index.js';
3
+ import { vimeoEmbed as publicClassName } from '../../public-classnames.js';
4
+ import { mergeClassNames } from '../../utils/index.js';
5
+ import cssModule from './styles.module.css';
6
+ // The id is the last run of digits in the path — `/123`, `/video/123`, `/channels/x/123`.
7
+ // An unlisted video carries a hash after it, which the player needs as `h`.
8
+ function videoId(raw) {
9
+ let url;
10
+ try {
11
+ url = new URL(raw);
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ const match = /\/(\d+)(?:\/([A-Za-z0-9]+))?\/?$/v.exec(url.pathname);
17
+ if (match?.[1] === undefined)
18
+ return null;
19
+ return { id: match[1], hash: match[2] ?? null };
20
+ }
21
+ /**
22
+ * A Vimeo video, embedded from its canonical URL.
23
+ *
24
+ * ### Root element modifiers
25
+ * - `--link` — no video id could be read, so a link is rendered instead of a frame.
26
+ *
27
+ * @param props - Component properties.
28
+ * @see {@link PlatformProps}
29
+ * @returns An `<iframe>` on `player.vimeo.com/video/<id>`, or an `<a>` on the URL as
30
+ * given.
31
+ */
32
+ export const VimeoEmbed = ({ className, url, title }) => {
33
+ const video = videoId(url);
34
+ const c = clss(publicClassName, { cssModule });
35
+ const rootClss = mergeClassNames(c(null, { link: video === null }), className);
36
+ if (video === null)
37
+ return _jsx("a", { className: rootClss, href: url, children: title });
38
+ const query = video.hash === null ? '' : `?h=${video.hash}`;
39
+ return _jsx("iframe", { className: rootClss, src: `https://player.vimeo.com/video/${video.id}${query}`, title: title, loading: "lazy", allow: "autoplay; fullscreen; picture-in-picture" });
40
+ };
File without changes
@@ -0,0 +1,18 @@
1
+ import type { FunctionComponent } from 'react';
2
+ import type { PlatformProps } from '../types.js';
3
+ /**
4
+ * A YouTube video, embedded from its canonical URL.
5
+ *
6
+ * ### Root element modifiers
7
+ * - `--link` — no video id could be read, so a link is rendered instead of a frame.
8
+ *
9
+ * @param props - Component properties.
10
+ * @see {@link PlatformProps}
11
+ * @returns An `<iframe>` on `youtube.com/embed/<id>`, or an `<a>` on the URL as given.
12
+ *
13
+ * @remarks
14
+ * `watch?v=`, `youtu.be/`, `/shorts/`, `/embed/`, `/live/` and `/v/` all carry an id.
15
+ * Anything else — a channel, a playlist URL, a search — has none to find, which is a
16
+ * state and not a failure: the component says so with a modifier and links out.
17
+ */
18
+ export declare const YoutubeEmbed: FunctionComponent<PlatformProps>;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { clss } from '@design-edito/tools/agnostic/css/clss/index.js';
3
+ import { youtubeEmbed as publicClassName } from '../../public-classnames.js';
4
+ import { mergeClassNames } from '../../utils/index.js';
5
+ import cssModule from './styles.module.css';
6
+ function videoId(raw) {
7
+ let url;
8
+ try {
9
+ url = new URL(raw);
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ if (url.hostname === 'youtu.be') {
15
+ const fromPath = url.pathname.slice(1).split('/')[0] ?? '';
16
+ return fromPath === '' ? null : fromPath;
17
+ }
18
+ const fromQuery = url.searchParams.get('v');
19
+ if (fromQuery !== null && fromQuery !== '')
20
+ return fromQuery;
21
+ return /^\/(?:shorts|embed|live|v)\/([^\/]+)/v.exec(url.pathname)?.[1] ?? null;
22
+ }
23
+ /**
24
+ * A YouTube video, embedded from its canonical URL.
25
+ *
26
+ * ### Root element modifiers
27
+ * - `--link` — no video id could be read, so a link is rendered instead of a frame.
28
+ *
29
+ * @param props - Component properties.
30
+ * @see {@link PlatformProps}
31
+ * @returns An `<iframe>` on `youtube.com/embed/<id>`, or an `<a>` on the URL as given.
32
+ *
33
+ * @remarks
34
+ * `watch?v=`, `youtu.be/`, `/shorts/`, `/embed/`, `/live/` and `/v/` all carry an id.
35
+ * Anything else — a channel, a playlist URL, a search — has none to find, which is a
36
+ * state and not a failure: the component says so with a modifier and links out.
37
+ */
38
+ export const YoutubeEmbed = ({ className, url, title }) => {
39
+ const id = videoId(url);
40
+ const c = clss(publicClassName, { cssModule });
41
+ const rootClss = mergeClassNames(c(null, { link: id === null }), className);
42
+ if (id === null)
43
+ return _jsx("a", { className: rootClss, href: url, children: title });
44
+ return _jsx("iframe", { className: rootClss, src: `https://www.youtube.com/embed/${id}`, title: title, loading: "lazy", allow: "accelerometer; clipboard-write; encrypted-media; fullscreen; picture-in-picture" });
45
+ };
@@ -0,0 +1,44 @@
1
+ import type { FunctionComponent } from 'react';
2
+ import type { WithClassName } from '../utils/types.js';
3
+ import { type EmbedPlatform } from './types.js';
4
+ /**
5
+ * Props for the {@link Embed} component.
6
+ *
7
+ * @property url - The canonical URL, as a reader would have copied it. The host it names
8
+ * decides which player renders it.
9
+ * @property title - What the frame is called, for assistive technology, and the text of
10
+ * the link it falls back to.
11
+ * @property platform - Forces a player, for a URL whose host does not say which one it
12
+ * is — a custom domain, a shortener. Left out, the host decides.
13
+ */
14
+ export type Props = WithClassName<{
15
+ url: string;
16
+ title: string;
17
+ platform?: EmbedPlatform;
18
+ }>;
19
+ /**
20
+ * A media URL, rendered by whichever player its host calls for.
21
+ *
22
+ * ### Root element modifiers
23
+ * - `--youtube`, `--spotify`, `--vimeo` — which player is rendering, one case out of a
24
+ * few rather than a yes-or-no.
25
+ * - `--link` — the host is not one of {@link embedPlatforms}, so a link is rendered.
26
+ *
27
+ * ### CSS elements
28
+ * - `player` — the platform player, whichever it is. It carries its own public class
29
+ * too, so a stylesheet can target every embed at once or one host in particular.
30
+ * - `link` — the fallback link, present only under `--link`.
31
+ *
32
+ * ### Data attributes on the root element
33
+ * - `data-platform` — the recognised host. Absent when there is none.
34
+ *
35
+ * @param props - Component properties.
36
+ * @see {@link Props}
37
+ * @returns A `<div>` holding the platform player, or a link when no player fits.
38
+ *
39
+ * @remarks
40
+ * An unrecognised host is a state and not a failure: nothing throws and nothing is
41
+ * logged, the component renders the link and says so with a modifier. Same inside each
42
+ * player for a URL of the right host with no id to read.
43
+ */
44
+ export declare const Embed: FunctionComponent<Props>;
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { clss } from '@design-edito/tools/agnostic/css/clss/index.js';
3
+ import { embed as publicClassName } from '../public-classnames.js';
4
+ import { mergeClassNames } from '../utils/index.js';
5
+ import { SpotifyEmbed } from './SpotifyEmbed/index.js';
6
+ import { VimeoEmbed } from './VimeoEmbed/index.js';
7
+ import { YoutubeEmbed } from './YoutubeEmbed/index.js';
8
+ import { embedPlatforms } from './types.js';
9
+ import cssModule from './styles.module.css';
10
+ // WIP — no action or instruction surface yet. Whether a player is worth driving from the
11
+ // outside (play, mute, a viewport behaviour) is a question for the day one of these hosts
12
+ // gives us an API to drive it with; an iframe answers to nothing from here.
13
+ const platformHostnames = {
14
+ youtube: [/(^|\.)youtube\.com$/v, /(^|\.)youtube-nocookie\.com$/v, /^youtu\.be$/v],
15
+ spotify: [/(^|\.)spotify\.com$/v],
16
+ vimeo: [/(^|\.)vimeo\.com$/v]
17
+ };
18
+ const platformEmbeds = {
19
+ youtube: YoutubeEmbed,
20
+ spotify: SpotifyEmbed,
21
+ vimeo: VimeoEmbed
22
+ };
23
+ function toPlatform(raw) {
24
+ let url;
25
+ try {
26
+ url = new URL(raw);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ const found = embedPlatforms
32
+ .find(platform => platformHostnames[platform].some(hostname => hostname.test(url.hostname)));
33
+ return found ?? null;
34
+ }
35
+ /**
36
+ * A media URL, rendered by whichever player its host calls for.
37
+ *
38
+ * ### Root element modifiers
39
+ * - `--youtube`, `--spotify`, `--vimeo` — which player is rendering, one case out of a
40
+ * few rather than a yes-or-no.
41
+ * - `--link` — the host is not one of {@link embedPlatforms}, so a link is rendered.
42
+ *
43
+ * ### CSS elements
44
+ * - `player` — the platform player, whichever it is. It carries its own public class
45
+ * too, so a stylesheet can target every embed at once or one host in particular.
46
+ * - `link` — the fallback link, present only under `--link`.
47
+ *
48
+ * ### Data attributes on the root element
49
+ * - `data-platform` — the recognised host. Absent when there is none.
50
+ *
51
+ * @param props - Component properties.
52
+ * @see {@link Props}
53
+ * @returns A `<div>` holding the platform player, or a link when no player fits.
54
+ *
55
+ * @remarks
56
+ * An unrecognised host is a state and not a failure: nothing throws and nothing is
57
+ * logged, the component renders the link and says so with a modifier. Same inside each
58
+ * player for a URL of the right host with no id to read.
59
+ */
60
+ export const Embed = ({ className, url, title, platform }) => {
61
+ const resolved = platform ?? toPlatform(url);
62
+ const c = clss(publicClassName, { cssModule });
63
+ const rootClss = mergeClassNames(c(null, {
64
+ ...resolved === null ? {} : { [resolved]: true },
65
+ link: resolved === null
66
+ }), className);
67
+ const PlatformEmbed = resolved === null ? null : platformEmbeds[resolved];
68
+ return _jsx("div", { className: rootClss, "data-platform": resolved ?? undefined, children: PlatformEmbed === null
69
+ ? _jsx("a", { className: c('link'), href: url, children: title })
70
+ : _jsx(PlatformEmbed, { className: c('player'), url: url, title: title }) });
71
+ };
File without changes
@@ -0,0 +1,22 @@
1
+ import type { WithClassName } from '../utils/types.js';
2
+ /**
3
+ * The platforms with a player of their own.
4
+ *
5
+ * An `as const` array rather than a union: recognising a URL's host is done against these
6
+ * values at runtime, and a consumer validating what it stores needs them too.
7
+ */
8
+ export declare const embedPlatforms: readonly ["youtube", "spotify", "vimeo"];
9
+ /** One of {@link embedPlatforms}. */
10
+ export type EmbedPlatform = typeof embedPlatforms[number];
11
+ /**
12
+ * What every platform player takes.
13
+ *
14
+ * @property url - The canonical URL, as a reader would have copied it. Each player cuts
15
+ * its own embed URL out of it.
16
+ * @property title - What the frame is called, for assistive technology, and the text of
17
+ * the link it falls back to.
18
+ */
19
+ export type PlatformProps = WithClassName<{
20
+ url: string;
21
+ title: string;
22
+ }>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The platforms with a player of their own.
3
+ *
4
+ * An `as const` array rather than a union: recognising a URL's host is done against these
5
+ * values at runtime, and a consumer validating what it stores needs them too.
6
+ */
7
+ export const embedPlatforms = ['youtube', 'spotify', 'vimeo'];