@panneau/fields 5.0.0-alpha.4 → 5.0.0-alpha.6
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 +67 -21
- package/dist/duration/DurationField.css +11 -0
- package/dist/duration/DurationField.d.ts +26 -0
- package/dist/duration/DurationField.d.ts.map +1 -0
- package/dist/duration/DurationField.js +129 -0
- package/dist/duration/DurationField.js.map +1 -0
- package/dist/duration/DurationField.module.js +12 -0
- package/dist/duration/DurationField.module.js.map +1 -0
- package/dist/duration/index.d.ts +2 -0
- package/dist/duration/index.d.ts.map +1 -0
- package/dist/duration/index.js +1 -0
- package/dist/embed/EmbedField.css +60 -0
- package/dist/embed/EmbedField.d.ts +20 -0
- package/dist/embed/EmbedField.d.ts.map +1 -0
- package/dist/embed/EmbedField.js +130 -0
- package/dist/embed/EmbedField.js.map +1 -0
- package/dist/embed/EmbedField.module.js +22 -0
- package/dist/embed/EmbedField.module.js.map +1 -0
- package/dist/embed/index.d.ts +3 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/json/JsonField.css +7 -0
- package/dist/json/JsonField.d.ts +22 -0
- package/dist/json/JsonField.d.ts.map +1 -0
- package/dist/json/JsonField.js +125 -0
- package/dist/json/JsonField.js.map +1 -0
- package/dist/json/JsonField.module.js +8 -0
- package/dist/json/JsonField.module.js.map +1 -0
- package/dist/json/index.d.ts +2 -0
- package/dist/json/index.d.ts.map +1 -0
- package/dist/json/index.js +1 -0
- package/dist/localized/LocalizedField.d.ts.map +1 -1
- package/dist/localized/LocalizedField.js +1 -0
- package/dist/localized/LocalizedField.js.map +1 -1
- package/dist/location/LocationField.css +54 -0
- package/dist/location/LocationField.d.ts +31 -0
- package/dist/location/LocationField.d.ts.map +1 -0
- package/dist/location/LocationField.js +350 -0
- package/dist/location/LocationField.js.map +1 -0
- package/dist/location/LocationField.module.js +22 -0
- package/dist/location/LocationField.module.js.map +1 -0
- package/dist/location/LocationMap.d.ts +25 -0
- package/dist/location/LocationMap.d.ts.map +1 -0
- package/dist/location/LocationMap.js +115 -0
- package/dist/location/LocationMap.js.map +1 -0
- package/dist/location/index.d.ts +5 -0
- package/dist/location/index.d.ts.map +1 -0
- package/dist/location/index.js +4 -0
- package/dist/location/leafletCss.d.ts +2 -0
- package/dist/location/leafletCss.d.ts.map +1 -0
- package/dist/location/leafletCss.js +6 -0
- package/dist/location/leafletCss.js.map +1 -0
- package/dist/location/nominatim.d.ts +17 -0
- package/dist/location/nominatim.d.ts.map +1 -0
- package/dist/location/nominatim.js +50 -0
- package/dist/location/nominatim.js.map +1 -0
- package/dist/location/types.d.ts +40 -0
- package/dist/location/types.d.ts.map +1 -0
- package/dist/range/RangeField.d.ts +22 -0
- package/dist/range/RangeField.d.ts.map +1 -0
- package/dist/range/RangeField.js +132 -0
- package/dist/range/RangeField.js.map +1 -0
- package/dist/range/index.d.ts +2 -0
- package/dist/range/index.d.ts.map +1 -0
- package/dist/range/index.js +1 -0
- package/dist/relation/RelationField.d.ts.map +1 -1
- package/dist/relation/RelationField.js +24 -24
- package/dist/relation/RelationField.js.map +1 -1
- package/dist/shared/DefinitionField.d.ts +3 -1
- package/dist/shared/DefinitionField.d.ts.map +1 -1
- package/dist/shared/DefinitionField.js +80 -72
- package/dist/shared/DefinitionField.js.map +1 -1
- package/dist/shared/builtIn.d.ts +1 -0
- package/dist/shared/builtIn.d.ts.map +1 -1
- package/dist/shared/builtIn.js +16 -1
- package/dist/shared/builtIn.js.map +1 -1
- package/dist/shared/check.d.ts +27 -0
- package/dist/shared/check.d.ts.map +1 -0
- package/dist/shared/check.js +18 -0
- package/dist/shared/check.js.map +1 -0
- package/dist/shared/context.d.ts +18 -3
- package/dist/shared/context.d.ts.map +1 -1
- package/dist/shared/context.js +31 -19
- package/dist/shared/context.js.map +1 -1
- package/dist/shared/embed.d.ts +19 -0
- package/dist/shared/embed.d.ts.map +1 -0
- package/dist/shared/embed.js +14 -0
- package/dist/shared/embed.js.map +1 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/slug/SlugField.css +18 -0
- package/dist/slug/SlugField.d.ts +31 -3
- package/dist/slug/SlugField.d.ts.map +1 -1
- package/dist/slug/SlugField.js +231 -159
- package/dist/slug/SlugField.js.map +1 -1
- package/dist/slug/SlugField.module.js +14 -0
- package/dist/slug/SlugField.module.js.map +1 -0
- package/dist/slug/index.d.ts +1 -1
- package/dist/slug/index.d.ts.map +1 -1
- package/dist/slug/index.js +1 -1
- package/dist/tags/TagsField.d.ts +17 -0
- package/dist/tags/TagsField.d.ts.map +1 -0
- package/dist/tags/TagsField.js +120 -0
- package/dist/tags/TagsField.js.map +1 -0
- package/dist/tags/index.d.ts +2 -0
- package/dist/tags/index.d.ts.map +1 -0
- package/dist/tags/index.js +1 -0
- package/dist/tel/TelField.d.ts +11 -0
- package/dist/tel/TelField.d.ts.map +1 -0
- package/dist/tel/TelField.js +87 -0
- package/dist/tel/TelField.js.map +1 -0
- package/dist/tel/index.d.ts +2 -0
- package/dist/tel/index.d.ts.map +1 -0
- package/dist/tel/index.js +1 -0
- package/package.json +41 -4
package/README.md
CHANGED
|
@@ -20,27 +20,34 @@ import { PanneauRoot } from '@panneau/ui';
|
|
|
20
20
|
|
|
21
21
|
Every field takes `value`, `onChange`, `onBlur`, `errors`, `disabled`, `readOnly`, `required`, `label` and `description`, plus `className` and `xxxClassName` props for its inner elements. Fields keep no value of their own, and an empty value is `null`.
|
|
22
22
|
|
|
23
|
-
| Import | Components | Value
|
|
24
|
-
| --------------------------- | -------------------------------------------- |
|
|
25
|
-
| `@panneau/fields/text` | `TextField` | `string`
|
|
26
|
-
| `@panneau/fields/textarea` | `TextAreaField` | `string`
|
|
27
|
-
| `@panneau/fields/number` | `NumberField` | `number`
|
|
28
|
-
| `@panneau/fields/boolean` | `BooleanField` (checkbox, switch) | `boolean`
|
|
29
|
-
| `@panneau/fields/date` | `DateField` | `'2026-10-15'`
|
|
30
|
-
| `@panneau/fields/datetime` | `DateTimeField` | `'2026-10-15T13:30:00Z'`, or without an offset
|
|
31
|
-
| `@panneau/fields/time` | `TimeField` | `'09:30'`
|
|
32
|
-
| `@panneau/fields/select` | `SelectField` (dropdown, radios, checkboxes) | an option's value, or a list with `multiple`
|
|
33
|
-
| `@panneau/fields/email` | `EmailField` | `string`
|
|
34
|
-
| `@panneau/fields/url` | `UrlField` | `string`, with `https://` added on blur
|
|
35
|
-
| `@panneau/fields/color` | `ColorField` | `'#1d4ed8'`
|
|
36
|
-
| `@panneau/fields/slug` | `SlugField`, `slugify()`
|
|
37
|
-
| `@panneau/fields/
|
|
38
|
-
| `@panneau/fields/
|
|
39
|
-
| `@panneau/fields/
|
|
40
|
-
| `@panneau/fields/
|
|
41
|
-
| `@panneau/fields/
|
|
42
|
-
| `@panneau/fields/
|
|
43
|
-
| `@panneau/fields/
|
|
23
|
+
| Import | Components | Value |
|
|
24
|
+
| --------------------------- | -------------------------------------------- | ------------------------------------------------------------------- |
|
|
25
|
+
| `@panneau/fields/text` | `TextField` | `string` |
|
|
26
|
+
| `@panneau/fields/textarea` | `TextAreaField` | `string` |
|
|
27
|
+
| `@panneau/fields/number` | `NumberField` | `number` |
|
|
28
|
+
| `@panneau/fields/boolean` | `BooleanField` (checkbox, switch) | `boolean` |
|
|
29
|
+
| `@panneau/fields/date` | `DateField` | `'2026-10-15'` |
|
|
30
|
+
| `@panneau/fields/datetime` | `DateTimeField` | `'2026-10-15T13:30:00Z'`, or without an offset |
|
|
31
|
+
| `@panneau/fields/time` | `TimeField` | `'09:30'` |
|
|
32
|
+
| `@panneau/fields/select` | `SelectField` (dropdown, radios, checkboxes) | an option's value, or a list with `multiple` |
|
|
33
|
+
| `@panneau/fields/email` | `EmailField` | `string` |
|
|
34
|
+
| `@panneau/fields/url` | `UrlField` | `string`, with `https://` added on blur |
|
|
35
|
+
| `@panneau/fields/color` | `ColorField` | `'#1d4ed8'` |
|
|
36
|
+
| `@panneau/fields/slug` | `SlugField`, `slugify()`, `sourceText()` | `string`, following a `source` until edited, checked with `onCheck` |
|
|
37
|
+
| `@panneau/fields/tel` | `TelField` | `string`, as typed |
|
|
38
|
+
| `@panneau/fields/range` | `RangeField` (a slider) | `number`, `null` until moved |
|
|
39
|
+
| `@panneau/fields/duration` | `DurationField`, `formatDuration()` | seconds, typed in hours, minutes and seconds |
|
|
40
|
+
| `@panneau/fields/json` | `JsonField`, `parseJson()` | an object or an array; the text while it doesn't parse |
|
|
41
|
+
| `@panneau/fields/tags` | `TagsField` | a list of strings, free or among suggestions |
|
|
42
|
+
| `@panneau/fields/embed` | `EmbedField` | `{ url, title, thumbnailUrl, … }`, from the backend (below) |
|
|
43
|
+
| `@panneau/fields/location` | `LocationField`, `createNominatimSource()` | `{ lat, lng, address }`, on a map (below) |
|
|
44
|
+
| `@panneau/fields/localized` | `LocalizedField` | `{ fr: …, en: … }` |
|
|
45
|
+
| `@panneau/fields/group` | `GroupField` | an object |
|
|
46
|
+
| `@panneau/fields/repeater` | `RepeaterField` (items or typed blocks) | a list of objects; blocks have a `type` |
|
|
47
|
+
| `@panneau/fields/relation` | `RelationField`, `createRelationSource()` | `{ id, label }`, or a list with `multiple` |
|
|
48
|
+
| `@panneau/fields/file` | `FileField` | a file, or a list with `multiple` (below) |
|
|
49
|
+
| `@panneau/fields/uppy` | `createUppyUploadSource()` | the upload source on Uppy 6 (below) |
|
|
50
|
+
| `@panneau/fields/locales` | `en`, `fr` | deprecated: use `@panneau/intl` |
|
|
44
51
|
|
|
45
52
|
## From a definition
|
|
46
53
|
|
|
@@ -107,3 +114,42 @@ const uploadSource =
|
|
|
107
114
|
Strings go through react-intl, with English defaults. Add the messages of every Panneau package to the app's with `withPanneauMessages()` from [`@panneau/intl`](../intl), or use `fieldsPlugin`, which carries the French messages of every package (`ui`, `fields` and `forms`).
|
|
108
115
|
|
|
109
116
|
The package layout, scripts and API reports are explained in [`docs/packages.md`](../../docs/packages.md).
|
|
117
|
+
|
|
118
|
+
## Checking a value
|
|
119
|
+
|
|
120
|
+
A field with `check` in the definition, such as a `slug`, checks its value with the backend as it is typed ([decision 0032](../../docs/decisions/0032-field-checks-and-slugs.md)). The slug field then shows the URL the backend gives, says when the slug is taken with a free one to use, and is read-only when the backend says the field doesn't apply to the item. Give forms a check source for the item they edit:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { createFieldCheckSource } from '@panneau/fields';
|
|
124
|
+
|
|
125
|
+
<FormView form={form} checkSource={createFieldCheckSource(adapter, 'pages', item?.id ?? null)} />;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Without one, fields don't check, and the slug shows its fixed `settings.prefix`.
|
|
129
|
+
|
|
130
|
+
## Embeds and places
|
|
131
|
+
|
|
132
|
+
An `embed` field shows the title and the thumbnail of a URL's video, sound or post. It gets them from the backend (`GET /embeds`), through the embed source of the provider; without one, it keeps the URL alone:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { createEmbedSource } from '@panneau/fields';
|
|
136
|
+
|
|
137
|
+
<FieldsProvider embedSource={createEmbedSource(adapter)}>…</FieldsProvider>;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A `location` field places a point on a Leaflet map, loaded when the field is first shown. The tiles are OpenStreetMap's by default. `maps` sets other tiles and an address search, a `GeocodingSource`: `createNominatimSource()` searches OpenStreetMap's Nominatim, whose [usage policy](https://operations.osmfoundation.org/policies/nominatim/) suits small volumes; a product with more uses its own service. Without a geocoding source, the field has no address search, and the latitude and longitude are typed or picked on the map.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { createNominatimSource } from '@panneau/fields';
|
|
144
|
+
|
|
145
|
+
<FieldsProvider
|
|
146
|
+
maps={{
|
|
147
|
+
tiles: { url: 'https://tiles.example.com/{z}/{x}/{y}.png', attribution: '© Example' },
|
|
148
|
+
geocoding: createNominatimSource({ email: 'dev@example.com', countryCodes: ['ca'] }),
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
…
|
|
152
|
+
</FieldsProvider>;
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`<Panneau>` and `createPanneauApp()` give the embed source of their adapter, and take `maps`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FieldEntryProps, FieldProps } from '../shared/types';
|
|
2
|
+
export interface DurationFieldProps extends FieldProps<number> {
|
|
3
|
+
/** `seconds` (the default) shows hours, minutes and seconds; `minutes` hides the seconds. */
|
|
4
|
+
precision?: 'seconds' | 'minutes';
|
|
5
|
+
}
|
|
6
|
+
/** Hours, minutes and seconds of a number of seconds. */
|
|
7
|
+
export declare function splitDuration(seconds: number): {
|
|
8
|
+
hours: number;
|
|
9
|
+
minutes: number;
|
|
10
|
+
seconds: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A duration with its units, without the empty ones: `1 h 5 min 30 s`, `3 min`. With a precision
|
|
14
|
+
* of minutes, the seconds are rounded away.
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatDuration(seconds: number, { precision }?: {
|
|
17
|
+
precision?: 'seconds' | 'minutes';
|
|
18
|
+
}): string;
|
|
19
|
+
/**
|
|
20
|
+
* A duration, in seconds, typed in hours, minutes and seconds. It is `null` while every part is
|
|
21
|
+
* empty.
|
|
22
|
+
*/
|
|
23
|
+
export declare function DurationField({ value, onChange, onBlur, precision, label, description, errors, disabled, readOnly, required, name, className, }: DurationFieldProps): import("react").JSX.Element;
|
|
24
|
+
/** The `duration` type: a number of seconds, with setting `precision` (`seconds` or `minutes`). */
|
|
25
|
+
export declare function DurationFieldEntry(props: FieldEntryProps): import("react").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=DurationField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DurationField.d.ts","sourceRoot":"","sources":["../../src/duration/DurationField.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGnE,MAAM,WAAW,kBAAmB,SAAQ,UAAU,CAAC,MAAM,CAAC;IAC1D,6FAA6F;IAC7F,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACrC;AAED,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACnB,CAOA;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC1B,OAAO,EAAE,MAAM,EACf,EAAE,SAAqB,EAAE,GAAE;IAAE,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAO,GACtE,MAAM,CAQR;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAqB,EACrB,KAAK,EACL,WAAW,EACX,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,SAAS,GACZ,EAAE,kBAAkB,+BAyCpB;AAED,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,+BAWxD"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getFieldProps, getSetting, isNumber, isString } from "../shared/entry.js";
|
|
2
|
+
import { messages } from "../shared/messages.js";
|
|
3
|
+
import DurationField_module_default from "./DurationField.module.js";
|
|
4
|
+
import { c } from "react/compiler-runtime";
|
|
5
|
+
import { Fieldset, NumberInput, usePanneauIntl } from "@panneau/ui";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/duration/DurationField.tsx
|
|
8
|
+
/** Hours, minutes and seconds of a number of seconds. */
|
|
9
|
+
function splitDuration(seconds) {
|
|
10
|
+
const total = Math.max(0, Math.round(seconds));
|
|
11
|
+
return {
|
|
12
|
+
hours: Math.floor(total / 3600),
|
|
13
|
+
minutes: Math.floor(total % 3600 / 60),
|
|
14
|
+
seconds: total % 60
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A duration with its units, without the empty ones: `1 h 5 min 30 s`, `3 min`. With a precision
|
|
19
|
+
* of minutes, the seconds are rounded away.
|
|
20
|
+
*/
|
|
21
|
+
function formatDuration(seconds, { precision = "seconds" } = {}) {
|
|
22
|
+
const parts = splitDuration(precision === "minutes" ? Math.round(seconds / 60) * 60 : seconds);
|
|
23
|
+
const shown = [
|
|
24
|
+
parts.hours > 0 ? `${parts.hours} h` : null,
|
|
25
|
+
parts.minutes > 0 ? `${parts.minutes} min` : null,
|
|
26
|
+
precision === "seconds" && parts.seconds > 0 ? `${parts.seconds} s` : null
|
|
27
|
+
].filter((part) => part !== null);
|
|
28
|
+
return shown.length > 0 ? shown.join(" ") : precision === "minutes" ? "0 min" : "0 s";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A duration, in seconds, typed in hours, minutes and seconds. It is `null` while every part is
|
|
32
|
+
* empty.
|
|
33
|
+
*/
|
|
34
|
+
function DurationField({ value, onChange, onBlur, precision = "seconds", label, description, errors, disabled, readOnly, required, name, className }) {
|
|
35
|
+
const intl = usePanneauIntl();
|
|
36
|
+
const parts = value !== null && value !== void 0 ? splitDuration(value) : null;
|
|
37
|
+
const change = (part, next) => {
|
|
38
|
+
const updated = {
|
|
39
|
+
...parts ?? {
|
|
40
|
+
hours: 0,
|
|
41
|
+
minutes: 0,
|
|
42
|
+
seconds: 0
|
|
43
|
+
},
|
|
44
|
+
[part]: next ?? 0
|
|
45
|
+
};
|
|
46
|
+
const total = updated.hours * 3600 + updated.minutes * 60 + updated.seconds;
|
|
47
|
+
onChange?.(total === 0 && next === null ? null : total);
|
|
48
|
+
};
|
|
49
|
+
const input = (part_0) => /* @__PURE__ */ jsx(NumberInput, {
|
|
50
|
+
"aria-label": intl.formatMessage(messages[part_0]),
|
|
51
|
+
value: parts?.[part_0] ?? null,
|
|
52
|
+
onChange: (next_0) => change(part_0, next_0),
|
|
53
|
+
onBlur: () => onBlur?.(),
|
|
54
|
+
minValue: 0,
|
|
55
|
+
...part_0 !== "hours" ? { maxValue: 59 } : {},
|
|
56
|
+
formatOptions: { maximumFractionDigits: 0 },
|
|
57
|
+
suffix: intl.formatMessage(messages[`${part_0}Unit`]),
|
|
58
|
+
isDisabled: disabled,
|
|
59
|
+
isReadOnly: readOnly,
|
|
60
|
+
...name !== void 0 ? { name: `${name}[${part_0}]` } : {},
|
|
61
|
+
className: DurationField_module_default.part
|
|
62
|
+
});
|
|
63
|
+
return /* @__PURE__ */ jsx(Fieldset, {
|
|
64
|
+
legend: label,
|
|
65
|
+
description,
|
|
66
|
+
errors,
|
|
67
|
+
isRequired: required,
|
|
68
|
+
isDisabled: disabled,
|
|
69
|
+
className,
|
|
70
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
71
|
+
className: DurationField_module_default.parts,
|
|
72
|
+
children: [
|
|
73
|
+
input("hours"),
|
|
74
|
+
input("minutes"),
|
|
75
|
+
precision === "seconds" ? input("seconds") : null
|
|
76
|
+
]
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** The `duration` type: a number of seconds, with setting `precision` (`seconds` or `minutes`). */
|
|
81
|
+
function DurationFieldEntry(props) {
|
|
82
|
+
const $ = c(13);
|
|
83
|
+
let t0;
|
|
84
|
+
if ($[0] !== props.field) {
|
|
85
|
+
t0 = getSetting(props.field, "precision", isString);
|
|
86
|
+
$[0] = props.field;
|
|
87
|
+
$[1] = t0;
|
|
88
|
+
} else t0 = $[1];
|
|
89
|
+
const precision = t0;
|
|
90
|
+
let t1;
|
|
91
|
+
if ($[2] !== props) {
|
|
92
|
+
t1 = getFieldProps(props);
|
|
93
|
+
$[2] = props;
|
|
94
|
+
$[3] = t1;
|
|
95
|
+
} else t1 = $[3];
|
|
96
|
+
let fieldProps;
|
|
97
|
+
if ($[4] !== t1) {
|
|
98
|
+
const { placeholder: _placeholder, ...t2 } = t1;
|
|
99
|
+
fieldProps = t2;
|
|
100
|
+
$[4] = t1;
|
|
101
|
+
$[5] = fieldProps;
|
|
102
|
+
} else fieldProps = $[5];
|
|
103
|
+
const t2 = isNumber(props.value) ? props.value : null;
|
|
104
|
+
let t3;
|
|
105
|
+
if ($[6] !== precision) {
|
|
106
|
+
t3 = precision === "minutes" || precision === "seconds" ? { precision } : {};
|
|
107
|
+
$[6] = precision;
|
|
108
|
+
$[7] = t3;
|
|
109
|
+
} else t3 = $[7];
|
|
110
|
+
let t4;
|
|
111
|
+
if ($[8] !== fieldProps || $[9] !== props.onChange || $[10] !== t2 || $[11] !== t3) {
|
|
112
|
+
t4 = /* @__PURE__ */ jsx(DurationField, {
|
|
113
|
+
...fieldProps,
|
|
114
|
+
value: t2,
|
|
115
|
+
onChange: props.onChange,
|
|
116
|
+
...t3
|
|
117
|
+
});
|
|
118
|
+
$[8] = fieldProps;
|
|
119
|
+
$[9] = props.onChange;
|
|
120
|
+
$[10] = t2;
|
|
121
|
+
$[11] = t3;
|
|
122
|
+
$[12] = t4;
|
|
123
|
+
} else t4 = $[12];
|
|
124
|
+
return t4;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { DurationField, DurationFieldEntry, formatDuration, splitDuration };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=DurationField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DurationField.js","names":["part","next","styles","_c"],"sources":["../../src/duration/DurationField.tsx"],"sourcesContent":["import { Fieldset, NumberInput, usePanneauIntl } from '@panneau/ui';\n\nimport { getFieldProps, getSetting, isNumber, isString } from '../shared/entry';\nimport { messages } from '../shared/messages';\nimport type { FieldEntryProps, FieldProps } from '../shared/types';\nimport styles from './DurationField.module.css';\n\nexport interface DurationFieldProps extends FieldProps<number> {\n /** `seconds` (the default) shows hours, minutes and seconds; `minutes` hides the seconds. */\n precision?: 'seconds' | 'minutes';\n}\n\n/** Hours, minutes and seconds of a number of seconds. */\nexport function splitDuration(seconds: number): {\n hours: number;\n minutes: number;\n seconds: number;\n} {\n const total = Math.max(0, Math.round(seconds));\n return {\n hours: Math.floor(total / 3600),\n minutes: Math.floor((total % 3600) / 60),\n seconds: total % 60,\n };\n}\n\n/**\n * A duration with its units, without the empty ones: `1 h 5 min 30 s`, `3 min`. With a precision\n * of minutes, the seconds are rounded away.\n */\nexport function formatDuration(\n seconds: number,\n { precision = 'seconds' }: { precision?: 'seconds' | 'minutes' } = {},\n): string {\n const parts = splitDuration(precision === 'minutes' ? Math.round(seconds / 60) * 60 : seconds);\n const shown = [\n parts.hours > 0 ? `${parts.hours} h` : null,\n parts.minutes > 0 ? `${parts.minutes} min` : null,\n precision === 'seconds' && parts.seconds > 0 ? `${parts.seconds} s` : null,\n ].filter((part) => part !== null);\n return shown.length > 0 ? shown.join(' ') : precision === 'minutes' ? '0 min' : '0 s';\n}\n\n/**\n * A duration, in seconds, typed in hours, minutes and seconds. It is `null` while every part is\n * empty.\n */\nexport function DurationField({\n value,\n onChange,\n onBlur,\n precision = 'seconds',\n label,\n description,\n errors,\n disabled,\n readOnly,\n required,\n name,\n className,\n}: DurationFieldProps) {\n const intl = usePanneauIntl();\n const parts = value !== null && value !== undefined ? splitDuration(value) : null;\n const change = (part: 'hours' | 'minutes' | 'seconds', next: number | null) => {\n const updated = { ...(parts ?? { hours: 0, minutes: 0, seconds: 0 }), [part]: next ?? 0 };\n const total = updated.hours * 3600 + updated.minutes * 60 + updated.seconds;\n // Clearing the last part that had a value empties the field\n onChange?.(total === 0 && next === null ? null : total);\n };\n const input = (part: 'hours' | 'minutes' | 'seconds') => (\n <NumberInput\n aria-label={intl.formatMessage(messages[part])}\n value={parts?.[part] ?? null}\n onChange={(next) => change(part, next)}\n onBlur={() => onBlur?.()}\n minValue={0}\n {...(part !== 'hours' ? { maxValue: 59 } : {})}\n formatOptions={{ maximumFractionDigits: 0 }}\n suffix={intl.formatMessage(messages[`${part}Unit`])}\n isDisabled={disabled}\n isReadOnly={readOnly}\n {...(name !== undefined ? { name: `${name}[${part}]` } : {})}\n className={styles.part}\n />\n );\n return (\n <Fieldset\n legend={label}\n description={description}\n errors={errors}\n isRequired={required}\n isDisabled={disabled}\n className={className}\n >\n <div className={styles.parts}>\n {input('hours')}\n {input('minutes')}\n {precision === 'seconds' ? input('seconds') : null}\n </div>\n </Fieldset>\n );\n}\n\n/** The `duration` type: a number of seconds, with setting `precision` (`seconds` or `minutes`). */\nexport function DurationFieldEntry(props: FieldEntryProps) {\n const precision = getSetting(props.field, 'precision', isString);\n const { placeholder: _placeholder, ...fieldProps } = getFieldProps(props);\n return (\n <DurationField\n {...fieldProps}\n value={isNumber(props.value) ? props.value : null}\n onChange={props.onChange}\n {...(precision === 'minutes' || precision === 'seconds' ? { precision } : {})}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "./DurationField.css";
|
|
2
|
+
//#region src/duration/DurationField.module.css
|
|
3
|
+
var parts = "panneau-duration-field-parts";
|
|
4
|
+
var part = "panneau-duration-field-part";
|
|
5
|
+
var DurationField_module_default = {
|
|
6
|
+
parts,
|
|
7
|
+
part
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { DurationField_module_default as default, part, parts };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=DurationField.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DurationField.module.js","names":[],"sources":["../../src/duration/DurationField.module.css"],"sourcesContent":["/* Hours, minutes and seconds side by side. */\n.parts {\n display: flex;\n flex-wrap: wrap;\n gap: var(--panneau-space-2);\n}\n\n.part {\n flex: 0 1 7rem;\n min-width: 5rem;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/duration/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,aAAa,EACb,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,cAAc,EACd,aAAa,GAChB,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DurationField, DurationFieldEntry, formatDuration, splitDuration } from "./DurationField.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* The URL, then the preview of what it embeds. */
|
|
2
|
+
.panneau-embed-field-embed {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: var(--panneau-space-2);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.panneau-embed-field-status {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: var(--panneau-space-2);
|
|
12
|
+
font-size: var(--panneau-font-size-sm);
|
|
13
|
+
color: var(--panneau-color-text-muted);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.panneau-embed-field-preview {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: var(--panneau-space-3);
|
|
20
|
+
padding: var(--panneau-space-2);
|
|
21
|
+
color: var(--panneau-color-text);
|
|
22
|
+
text-decoration: none;
|
|
23
|
+
background-color: var(--panneau-color-surface-sunken);
|
|
24
|
+
border: var(--panneau-border-width) solid var(--panneau-color-border);
|
|
25
|
+
border-radius: var(--panneau-radius-md);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.panneau-embed-field-preview:hover {
|
|
29
|
+
border-color: var(--panneau-color-border-control);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.panneau-embed-field-preview:focus-visible {
|
|
33
|
+
outline: var(--panneau-focus-width) solid var(--panneau-color-focus);
|
|
34
|
+
outline-offset: 1px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.panneau-embed-field-thumbnail {
|
|
38
|
+
flex: none;
|
|
39
|
+
width: 7rem;
|
|
40
|
+
aspect-ratio: 16 / 9;
|
|
41
|
+
object-fit: cover;
|
|
42
|
+
border-radius: var(--panneau-radius-sm);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.panneau-embed-field-text {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
gap: 0.125rem;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.panneau-embed-field-title {
|
|
53
|
+
font-weight: var(--panneau-font-weight-medium);
|
|
54
|
+
overflow-wrap: anywhere;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.panneau-embed-field-provider {
|
|
58
|
+
font-size: var(--panneau-font-size-sm);
|
|
59
|
+
color: var(--panneau-color-text-muted);
|
|
60
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { EmbedValue } from '@panneau/protocol';
|
|
2
|
+
import type { EmbedSource } from '../shared/embed';
|
|
3
|
+
import type { FieldEntryProps, FieldProps } from '../shared/types';
|
|
4
|
+
export type { EmbedValue };
|
|
5
|
+
export interface EmbedFieldProps extends FieldProps<EmbedValue> {
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
/** Where the preview comes from. Defaults to that of the `FieldsProvider`. */
|
|
8
|
+
embedSource?: EmbedSource | null;
|
|
9
|
+
}
|
|
10
|
+
/** Whether a value is an embed: an object with a `url`. */
|
|
11
|
+
export declare function isEmbed(value: unknown): value is EmbedValue;
|
|
12
|
+
/**
|
|
13
|
+
* A URL of a video, a sound or a post, with the preview the backend gets from its provider's
|
|
14
|
+
* oEmbed: its thumbnail, title and provider. The URL is kept as soon as it is typed, and its
|
|
15
|
+
* preview follows. Without an embed source, the value is the URL alone.
|
|
16
|
+
*/
|
|
17
|
+
export declare function EmbedField({ value, onChange, onBlur, embedSource: embedSourceProp, errors, disabled, readOnly, required, ...props }: EmbedFieldProps): import("react").JSX.Element;
|
|
18
|
+
/** The `embed` type: its preview comes from the embed source of the `FieldsProvider`. */
|
|
19
|
+
export declare function EmbedFieldEntry(props: FieldEntryProps): import("react").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=EmbedField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbedField.d.ts","sourceRoot":"","sources":["../../src/embed/EmbedField.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGnE,YAAY,EAAE,UAAU,EAAE,CAAC;AAI3B,MAAM,WAAW,eAAgB,SAAQ,UAAU,CAAC,UAAU,CAAC;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED,2DAA2D;AAC3D,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAM3D;AAOD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACvB,KAAK,EACL,QAAQ,EACR,MAAM,EACN,WAAW,EAAE,eAAe,EAC5B,MAAW,EACX,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,GAAG,KAAK,EACX,EAAE,eAAe,+BA6EjB;AAED,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,+BAQrD"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { useFieldsContext } from "../shared/context.js";
|
|
2
|
+
import { getFieldProps, isString } from "../shared/entry.js";
|
|
3
|
+
import { messages } from "../shared/messages.js";
|
|
4
|
+
import EmbedField_module_default from "./EmbedField.module.js";
|
|
5
|
+
import { c } from "react/compiler-runtime";
|
|
6
|
+
import { Spinner, TextInput, usePanneauIntl } from "@panneau/ui";
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
//#region src/embed/EmbedField.tsx
|
|
10
|
+
var RESOLVE_DELAY = 500;
|
|
11
|
+
/** Whether a value is an embed: an object with a `url`. */
|
|
12
|
+
function isEmbed(value) {
|
|
13
|
+
return value !== null && typeof value === "object" && isString(value.url);
|
|
14
|
+
}
|
|
15
|
+
/** Whether a value has been resolved: it has more than its URL. */
|
|
16
|
+
function isResolved(value) {
|
|
17
|
+
return Object.keys(value).some((key) => key !== "url");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A URL of a video, a sound or a post, with the preview the backend gets from its provider's
|
|
21
|
+
* oEmbed: its thumbnail, title and provider. The URL is kept as soon as it is typed, and its
|
|
22
|
+
* preview follows. Without an embed source, the value is the URL alone.
|
|
23
|
+
*/
|
|
24
|
+
function EmbedField({ value, onChange, onBlur, embedSource: embedSourceProp, errors = [], disabled, readOnly, required, ...props }) {
|
|
25
|
+
const intl = usePanneauIntl();
|
|
26
|
+
const context = useFieldsContext();
|
|
27
|
+
const embedSource = embedSourceProp === void 0 ? context.embedSource : embedSourceProp;
|
|
28
|
+
const url = value?.url ?? "";
|
|
29
|
+
const [status, setStatus] = useState(null);
|
|
30
|
+
const pending = value !== null && value !== void 0 && !isResolved(value) ? value.url : null;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (embedSource === null || pending === null || !URL.canParse(pending)) return;
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timeout = window.setTimeout(() => {
|
|
35
|
+
setStatus({
|
|
36
|
+
url: pending,
|
|
37
|
+
state: "loading"
|
|
38
|
+
});
|
|
39
|
+
embedSource.resolve(pending, { signal: controller.signal }).then((embed) => {
|
|
40
|
+
setStatus(null);
|
|
41
|
+
onChange?.({
|
|
42
|
+
...embed,
|
|
43
|
+
url: pending
|
|
44
|
+
});
|
|
45
|
+
}).catch(() => {
|
|
46
|
+
if (!controller.signal.aborted) setStatus({
|
|
47
|
+
url: pending,
|
|
48
|
+
state: "failed"
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}, RESOLVE_DELAY);
|
|
52
|
+
return () => {
|
|
53
|
+
window.clearTimeout(timeout);
|
|
54
|
+
controller.abort();
|
|
55
|
+
};
|
|
56
|
+
}, [embedSource, pending]);
|
|
57
|
+
const current = status !== null && status.url === url ? status.state : null;
|
|
58
|
+
const shownErrors = current === "failed" && errors.length === 0 ? [intl.formatMessage(messages.embedFailed)] : errors;
|
|
59
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
60
|
+
className: EmbedField_module_default.embed,
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsx(TextInput, {
|
|
63
|
+
...props,
|
|
64
|
+
type: "url",
|
|
65
|
+
inputMode: "url",
|
|
66
|
+
autoComplete: "off",
|
|
67
|
+
value: url,
|
|
68
|
+
onChange: (next) => onChange?.(next.trim() === "" ? null : { url: next.trim() }),
|
|
69
|
+
onBlur: () => onBlur?.(),
|
|
70
|
+
errors: shownErrors,
|
|
71
|
+
isDisabled: disabled,
|
|
72
|
+
isReadOnly: readOnly,
|
|
73
|
+
isRequired: required
|
|
74
|
+
}),
|
|
75
|
+
current === "loading" ? /* @__PURE__ */ jsxs("div", {
|
|
76
|
+
className: EmbedField_module_default.status,
|
|
77
|
+
role: "status",
|
|
78
|
+
children: [/* @__PURE__ */ jsx(Spinner, { size: "sm" }), intl.formatMessage(messages.embedLoading)]
|
|
79
|
+
}) : null,
|
|
80
|
+
value !== null && value !== void 0 && isResolved(value) ? /* @__PURE__ */ jsxs("a", {
|
|
81
|
+
className: EmbedField_module_default.preview,
|
|
82
|
+
href: value.url,
|
|
83
|
+
target: "_blank",
|
|
84
|
+
rel: "noreferrer",
|
|
85
|
+
children: [value.thumbnailUrl !== void 0 ? /* @__PURE__ */ jsx("img", {
|
|
86
|
+
className: EmbedField_module_default.thumbnail,
|
|
87
|
+
src: value.thumbnailUrl,
|
|
88
|
+
alt: ""
|
|
89
|
+
}) : null, /* @__PURE__ */ jsxs("span", {
|
|
90
|
+
className: EmbedField_module_default.text,
|
|
91
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
92
|
+
className: EmbedField_module_default.title,
|
|
93
|
+
children: value.title ?? value.url
|
|
94
|
+
}), value.provider !== void 0 ? /* @__PURE__ */ jsx("span", {
|
|
95
|
+
className: EmbedField_module_default.provider,
|
|
96
|
+
children: value.provider
|
|
97
|
+
}) : null]
|
|
98
|
+
})]
|
|
99
|
+
}) : null
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** The `embed` type: its preview comes from the embed source of the `FieldsProvider`. */
|
|
104
|
+
function EmbedFieldEntry(props) {
|
|
105
|
+
const $ = c(6);
|
|
106
|
+
let t0;
|
|
107
|
+
if ($[0] !== props) {
|
|
108
|
+
t0 = getFieldProps(props);
|
|
109
|
+
$[0] = props;
|
|
110
|
+
$[1] = t0;
|
|
111
|
+
} else t0 = $[1];
|
|
112
|
+
const t1 = isEmbed(props.value) ? props.value : null;
|
|
113
|
+
let t2;
|
|
114
|
+
if ($[2] !== props.onChange || $[3] !== t0 || $[4] !== t1) {
|
|
115
|
+
t2 = /* @__PURE__ */ jsx(EmbedField, {
|
|
116
|
+
...t0,
|
|
117
|
+
value: t1,
|
|
118
|
+
onChange: props.onChange
|
|
119
|
+
});
|
|
120
|
+
$[2] = props.onChange;
|
|
121
|
+
$[3] = t0;
|
|
122
|
+
$[4] = t1;
|
|
123
|
+
$[5] = t2;
|
|
124
|
+
} else t2 = $[5];
|
|
125
|
+
return t2;
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { EmbedField, EmbedFieldEntry, isEmbed };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=EmbedField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbedField.js","names":["styles","_c"],"sources":["../../src/embed/EmbedField.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nimport type { EmbedValue } from '@panneau/protocol';\nimport { Spinner, TextInput, usePanneauIntl } from '@panneau/ui';\n\nimport { useFieldsContext } from '../shared/context';\nimport type { EmbedSource } from '../shared/embed';\nimport { getFieldProps, isString } from '../shared/entry';\nimport { messages } from '../shared/messages';\nimport type { FieldEntryProps, FieldProps } from '../shared/types';\nimport styles from './EmbedField.module.css';\n\nexport type { EmbedValue };\n\nconst RESOLVE_DELAY = 500;\n\nexport interface EmbedFieldProps extends FieldProps<EmbedValue> {\n placeholder?: string;\n /** Where the preview comes from. Defaults to that of the `FieldsProvider`. */\n embedSource?: EmbedSource | null;\n}\n\n/** Whether a value is an embed: an object with a `url`. */\nexport function isEmbed(value: unknown): value is EmbedValue {\n return (\n value !== null &&\n typeof value === 'object' &&\n isString((value as Record<string, unknown>).url)\n );\n}\n\n/** Whether a value has been resolved: it has more than its URL. */\nfunction isResolved(value: EmbedValue): boolean {\n return Object.keys(value).some((key) => key !== 'url');\n}\n\n/**\n * A URL of a video, a sound or a post, with the preview the backend gets from its provider's\n * oEmbed: its thumbnail, title and provider. The URL is kept as soon as it is typed, and its\n * preview follows. Without an embed source, the value is the URL alone.\n */\nexport function EmbedField({\n value,\n onChange,\n onBlur,\n embedSource: embedSourceProp,\n errors = [],\n disabled,\n readOnly,\n required,\n ...props\n}: EmbedFieldProps) {\n const intl = usePanneauIntl();\n const context = useFieldsContext();\n const embedSource = embedSourceProp === undefined ? context.embedSource : embedSourceProp;\n const url = value?.url ?? '';\n const [status, setStatus] = useState<{ url: string; state: 'loading' | 'failed' } | null>(null);\n\n // Resolves a URL that hasn't been, after a pause in typing\n const pending = value !== null && value !== undefined && !isResolved(value) ? value.url : null;\n useEffect(() => {\n if (embedSource === null || pending === null || !URL.canParse(pending)) {\n return undefined;\n }\n const controller = new AbortController();\n const timeout = window.setTimeout(() => {\n setStatus({ url: pending, state: 'loading' });\n embedSource\n .resolve(pending, { signal: controller.signal })\n .then((embed) => {\n setStatus(null);\n onChange?.({ ...embed, url: pending });\n })\n .catch(() => {\n if (!controller.signal.aborted) {\n setStatus({ url: pending, state: 'failed' });\n }\n });\n }, RESOLVE_DELAY);\n return () => {\n window.clearTimeout(timeout);\n controller.abort();\n };\n // `onChange` changes on every render of a form\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [embedSource, pending]);\n\n const current = status !== null && status.url === url ? status.state : null;\n const shownErrors =\n current === 'failed' && errors.length === 0\n ? [intl.formatMessage(messages.embedFailed)]\n : errors;\n return (\n <div className={styles.embed}>\n <TextInput\n {...props}\n type=\"url\"\n inputMode=\"url\"\n autoComplete=\"off\"\n value={url}\n onChange={(next) => onChange?.(next.trim() === '' ? null : { url: next.trim() })}\n onBlur={() => onBlur?.()}\n errors={shownErrors}\n isDisabled={disabled}\n isReadOnly={readOnly}\n isRequired={required}\n />\n {current === 'loading' ? (\n <div className={styles.status} role=\"status\">\n <Spinner size=\"sm\" />\n {intl.formatMessage(messages.embedLoading)}\n </div>\n ) : null}\n {value !== null && value !== undefined && isResolved(value) ? (\n <a className={styles.preview} href={value.url} target=\"_blank\" rel=\"noreferrer\">\n {value.thumbnailUrl !== undefined ? (\n <img className={styles.thumbnail} src={value.thumbnailUrl} alt=\"\" />\n ) : null}\n <span className={styles.text}>\n <span className={styles.title}>{value.title ?? value.url}</span>\n {value.provider !== undefined ? (\n <span className={styles.provider}>{value.provider}</span>\n ) : null}\n </span>\n </a>\n ) : null}\n </div>\n );\n}\n\n/** The `embed` type: its preview comes from the embed source of the `FieldsProvider`. */\nexport function EmbedFieldEntry(props: FieldEntryProps) {\n return (\n <EmbedField\n {...getFieldProps(props)}\n value={isEmbed(props.value) ? props.value : null}\n onChange={props.onChange}\n />\n );\n}\n"],"mappings":";;;;;;;;;AAcA,IAAM,gBAAgB;;AAStB,SAAgB,QAAQ,OAAqC;CACzD,OACI,UAAU,QACV,OAAO,UAAU,YACjB,SAAU,MAAkC,GAAG;AAEvD;;AAGA,SAAS,WAAW,OAA4B;CAC5C,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,MAAM,QAAQ,QAAQ,KAAK;AACzD;;;;;;AAOA,SAAgB,WAAW,EACvB,OACA,UACA,QACA,aAAa,iBACb,SAAS,CAAA,GACT,UACA,UACA,UACA,GAAG,SACa;CAChB,MAAM,OAAO,eAAe;CAC5B,MAAM,UAAU,iBAAiB;CACjC,MAAM,cAAc,oBAAoB,KAAA,IAAY,QAAQ,cAAc;CAC1E,MAAM,MAAM,OAAO,OAAO;CAC1B,MAAM,CAAC,QAAQ,aAAa,SAA8D,IAAI;CAG9F,MAAM,UAAU,UAAU,QAAQ,UAAU,KAAA,KAAa,CAAC,WAAW,KAAK,IAAI,MAAM,MAAM;CAC1F,gBAAgB;EACZ,IAAI,gBAAgB,QAAQ,YAAY,QAAQ,CAAC,IAAI,SAAS,OAAO,GACjE;EAEJ,MAAM,aAAa,IAAI,gBAAgB;EACvC,MAAM,UAAU,OAAO,iBAAiB;GACpC,UAAU;IAAE,KAAK;IAAS,OAAO;GAAU,CAAC;GAC5C,YACK,QAAQ,SAAS,EAAE,QAAQ,WAAW,OAAO,CAAC,CAAC,CAC/C,MAAM,UAAU;IACb,UAAU,IAAI;IACd,WAAW;KAAE,GAAG;KAAO,KAAK;IAAQ,CAAC;GACzC,CAAC,CAAC,CACD,YAAY;IACT,IAAI,CAAC,WAAW,OAAO,SACnB,UAAU;KAAE,KAAK;KAAS,OAAO;IAAS,CAAC;GAEnD,CAAC;EACT,GAAG,aAAa;EAChB,aAAa;GACT,OAAO,aAAa,OAAO;GAC3B,WAAW,MAAM;EACrB;CAGJ,GAAG,CAAC,aAAa,OAAO,CAAC;CAEzB,MAAM,UAAU,WAAW,QAAQ,OAAO,QAAQ,MAAM,OAAO,QAAQ;CACvE,MAAM,cACF,YAAY,YAAY,OAAO,WAAW,IACpC,CAAC,KAAK,cAAc,SAAS,WAAW,CAAC,IACzC;CACV,OACI,qBAAC,OAAD;EAAK,WAAWA,0BAAO;EAAvB,UAAA;GACI,oBAAC,WAAD;IACI,GAAI;IACJ,MAAK;IACL,WAAU;IACV,cAAa;IACb,OAAO;IACP,WAAW,SAAS,WAAW,KAAK,KAAK,MAAM,KAAK,OAAO,EAAE,KAAK,KAAK,KAAK,EAAE,CAAC;IAC/E,cAAc,SAAS;IACvB,QAAQ;IACR,YAAY;IACZ,YAAY;IACZ,YAAY;GAAS,CAAA;GAExB,YAAY,YACT,qBAAC,OAAD;IAAK,WAAWA,0BAAO;IAAQ,MAAK;IAApC,UAAA,CACI,oBAAC,SAAD,EAAS,MAAK,KAAI,CAAA,GACjB,KAAK,cAAc,SAAS,YAAY,CACxC;GACL,CAAA,IAAA;GACH,UAAU,QAAQ,UAAU,KAAA,KAAa,WAAW,KAAK,IACtD,qBAAC,KAAD;IAAG,WAAWA,0BAAO;IAAS,MAAM,MAAM;IAAK,QAAO;IAAS,KAAI;IAAnE,UAAA,CACK,MAAM,iBAAiB,KAAA,IACpB,oBAAC,OAAD;KAAK,WAAWA,0BAAO;KAAW,KAAK,MAAM;KAAc,KAAI;IAAE,CAAA,IACjE,MACJ,qBAAC,QAAD;KAAM,WAAWA,0BAAO;KAAxB,UAAA,CACI,oBAAC,QAAD;MAAM,WAAWA,0BAAO;MAAQ,UAAA,MAAM,SAAS,MAAM;KAAU,CAAA,GAC9D,MAAM,aAAa,KAAA,IAChB,oBAAC,QAAD;MAAM,WAAWA,0BAAO;MAAW,UAAA,MAAM;KAAe,CAAA,IACxD,IACF;IACP,CAAA,CAAA;GACH,CAAA,IAAA;EACH;;AAEb;;AAGA,SAAO,gBAAA,OAAA;CAAA,MAAA,IAAAC,EAAA,CAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,OAAA;EAGS,KAAA,cAAc,KAAK;EAAC,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CACjB,MAAA,KAAA,QAAQ,MAAK,KAA2B,IAAjB,MAAK,QAA5B;CAAyC,IAAA;CAAA,IAAA,EAAA,OAAA,MAAA,YAAA,EAAA,OAAA,MAAA,EAAA,OAAA,IAAA;EAFpD,KAAA,oBAAC,YAAD;GAAW,GACH;GACG,OAAA;GACG,UAAA,MAAK;EAAS,CAAA;EAC1B,EAAA,KAAA,MAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAJF;AAIE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import "./EmbedField.css";
|
|
2
|
+
//#region src/embed/EmbedField.module.css
|
|
3
|
+
var embed = "panneau-embed-field-embed";
|
|
4
|
+
var status = "panneau-embed-field-status";
|
|
5
|
+
var preview = "panneau-embed-field-preview";
|
|
6
|
+
var thumbnail = "panneau-embed-field-thumbnail";
|
|
7
|
+
var text = "panneau-embed-field-text";
|
|
8
|
+
var title = "panneau-embed-field-title";
|
|
9
|
+
var provider = "panneau-embed-field-provider";
|
|
10
|
+
var EmbedField_module_default = {
|
|
11
|
+
embed,
|
|
12
|
+
status,
|
|
13
|
+
preview,
|
|
14
|
+
thumbnail,
|
|
15
|
+
text,
|
|
16
|
+
title,
|
|
17
|
+
provider
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { EmbedField_module_default as default, embed, preview, provider, status, text, thumbnail, title };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=EmbedField.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbedField.module.js","names":[],"sources":["../../src/embed/EmbedField.module.css"],"sourcesContent":["/* The URL, then the preview of what it embeds. */\n.embed {\n display: flex;\n flex-direction: column;\n gap: var(--panneau-space-2);\n}\n\n.status {\n display: flex;\n align-items: center;\n gap: var(--panneau-space-2);\n font-size: var(--panneau-font-size-sm);\n color: var(--panneau-color-text-muted);\n}\n\n.preview {\n display: flex;\n align-items: center;\n gap: var(--panneau-space-3);\n padding: var(--panneau-space-2);\n color: var(--panneau-color-text);\n text-decoration: none;\n background-color: var(--panneau-color-surface-sunken);\n border: var(--panneau-border-width) solid var(--panneau-color-border);\n border-radius: var(--panneau-radius-md);\n}\n\n.preview:hover {\n border-color: var(--panneau-color-border-control);\n}\n\n.preview:focus-visible {\n outline: var(--panneau-focus-width) solid var(--panneau-color-focus);\n outline-offset: 1px;\n}\n\n.thumbnail {\n flex: none;\n width: 7rem;\n aspect-ratio: 16 / 9;\n object-fit: cover;\n border-radius: var(--panneau-radius-sm);\n}\n\n.text {\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n min-width: 0;\n}\n\n.title {\n font-weight: var(--panneau-font-weight-medium);\n overflow-wrap: anywhere;\n}\n\n.provider {\n font-size: var(--panneau-font-size-sm);\n color: var(--panneau-color-text-muted);\n}\n"],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/embed/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EACV,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,OAAO,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export { builtInFields, fieldsPlugin } from './shared/builtIn';
|
|
|
3
3
|
export { type BoundFieldProps, type FieldBinding, type FieldBindingProps, FieldsProvider, type FieldsContextValue, type FieldsProviderProps, getParentPath, useFieldsContext, } from './shared/context';
|
|
4
4
|
export { findRule, getAccept, getFieldProps, getMaxSize, getRuleNumber, isRequired, } from './shared/entry';
|
|
5
5
|
export { type RelationListAdapter, type RelationReference, type RelationSource, type RelationSourceOptions, createRelationSource, toReferences, useRelationReferences, } from './shared/relation';
|
|
6
|
+
export { type FieldCheckAdapter, type FieldCheckSource, createFieldCheckSource, } from './shared/check';
|
|
7
|
+
export { type EmbedAdapter, type EmbedSource, createEmbedSource } from './shared/embed';
|
|
8
|
+
export type { GeocodingResult, GeocodingSource, MapTiles, MapsConfig } from './location/types';
|
|
9
|
+
export { type NominatimSourceOptions, createNominatimSource } from './location/nominatim';
|
|
6
10
|
export type { FieldEntry, FieldEntryProps, FieldProps } from './shared/types';
|
|
7
11
|
export { MissingFieldEntry } from './missing/MissingField';
|
|
8
12
|
export { type FileFieldBaseProps, FileField, FileFieldEntry, type FileFieldProps, type FileValue, type MultipleFileFieldProps, type SingleFileFieldProps, isFileValue, toFiles, } from './file/FileField';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACH,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,aAAa,EACb,gBAAgB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACH,QAAQ,EACR,SAAS,EACT,aAAa,EACb,UAAU,EACV,aAAa,EACb,UAAU,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACH,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACH,KAAK,kBAAkB,EACvB,SAAS,EACT,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,WAAW,EACX,OAAO,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACH,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACH,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,aAAa,EACb,gBAAgB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACH,QAAQ,EACR,SAAS,EACT,aAAa,EACb,UAAU,EACV,aAAa,EACb,UAAU,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACH,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,oBAAoB,EACpB,YAAY,EACZ,qBAAqB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,sBAAsB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxF,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,KAAK,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACH,KAAK,kBAAkB,EACvB,SAAS,EACT,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,WAAW,EACX,OAAO,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACH,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC"}
|