storyloco 2.3.1 → 2.5.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
CHANGED
|
@@ -16,6 +16,7 @@ A collection of slick Storyblok field plugins built with Svelte 5, TypeScript, a
|
|
|
16
16
|
- **📝 Input** - Comprehensive form input field with support for all HTML input types, checkboxes, radio buttons, selects, and textareas
|
|
17
17
|
- **⏰ Time** - Simple time input field with step control
|
|
18
18
|
- **💷 Currency** - Amount input with a currency selector; configure allowed codes via the `currencies` and `default_currency` options
|
|
19
|
+
- **📅 Dates** - List of dates, each with an optional time, end date and end time, plus a timezone picker; configure via the `min`, `max` and `auto_sort` options
|
|
19
20
|
|
|
20
21
|
### Vite Plugins
|
|
21
22
|
|
|
@@ -59,6 +60,35 @@ import type { SEO } from 'storyloco/seo'
|
|
|
59
60
|
import type { Video, Heading, Link, Input, Asset, SEO } from 'storyloco'
|
|
60
61
|
```
|
|
61
62
|
|
|
63
|
+
### Write Currency Values
|
|
64
|
+
|
|
65
|
+
Map spreadsheet cells or plain numbers to the currency field's stored shape, e.g. for a Management API import:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { to_currency } from 'storyloco/currency/utils'
|
|
69
|
+
|
|
70
|
+
to_currency('£1,234.50') // currency defaults to GBP
|
|
71
|
+
// { plugin: 'uiloco-currency', currency: 'GBP', amount: 1234.5, formatted: '£1,234.50' }
|
|
72
|
+
to_currency('', 'GBP') // null
|
|
73
|
+
to_currency('POA', 'GBP') // throws, as do negatives and too many decimal places
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`format_currency(amount, code)` gives the same `formatted` string the plugin writes.
|
|
77
|
+
|
|
78
|
+
### Display Dates
|
|
79
|
+
|
|
80
|
+
Timed entries carry UTC instants, so the site formats them natively in the field's zone. All-day entries are dates, not moments: use `start_date`/`end_date` as they are.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import type { DateEntry, Dates } from 'storyloco/dates'
|
|
84
|
+
|
|
85
|
+
const show = ({ timezone }: Dates, entry: DateEntry) =>
|
|
86
|
+
entry.utc_start
|
|
87
|
+
? new Intl.DateTimeFormat('en-GB', { timeZone: timezone, dateStyle: 'medium', timeStyle: 'short' })
|
|
88
|
+
.formatRange(new Date(entry.utc_start), new Date(entry.utc_end ?? entry.utc_start))
|
|
89
|
+
: [entry.start_date, entry.end_date].filter(Boolean).join(' – ')
|
|
90
|
+
```
|
|
91
|
+
|
|
62
92
|
### Use Storyblok Client
|
|
63
93
|
|
|
64
94
|
#### 1. Create your Storyblok client
|
|
@@ -289,6 +319,7 @@ export interface StoryblokCustomPlugins {
|
|
|
289
319
|
}
|
|
290
320
|
'loco-time': string
|
|
291
321
|
'loco-currency': import('storyloco/currency').Currency
|
|
322
|
+
'loco-dates': import('storyloco/dates').Dates
|
|
292
323
|
'loco-asset': import('storyloco/asset').Asset
|
|
293
324
|
}
|
|
294
325
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storyloco",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "A collection of slick Storyblok field plugins built with Svelte 5, TypeScript, and Tailwind CSS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"storyblok",
|
|
@@ -29,12 +29,15 @@
|
|
|
29
29
|
"packages/theme/types.ts",
|
|
30
30
|
"packages/shared/dist",
|
|
31
31
|
"packages/vite/dist",
|
|
32
|
+
"packages/vite/src/lib/currency.ts",
|
|
33
|
+
"!packages/vite/dist/**/*.test.*",
|
|
32
34
|
"packages/ticketsource/types.ts",
|
|
33
35
|
"packages/input/types.ts",
|
|
34
36
|
"packages/time/types.ts",
|
|
35
37
|
"packages/seo/types.ts",
|
|
36
38
|
"packages/hubspot_form/types.ts",
|
|
37
|
-
"packages/currency/types.ts"
|
|
39
|
+
"packages/currency/types.ts",
|
|
40
|
+
"packages/dates/types.ts"
|
|
38
41
|
],
|
|
39
42
|
"exports": {
|
|
40
43
|
".": {
|
|
@@ -98,6 +101,14 @@
|
|
|
98
101
|
"./currency": {
|
|
99
102
|
"types": "./packages/currency/types.ts",
|
|
100
103
|
"svelte": "./packages/currency/src/app.svelte.ts"
|
|
104
|
+
},
|
|
105
|
+
"./currency/utils": {
|
|
106
|
+
"types": "./packages/vite/dist/currency.d.ts",
|
|
107
|
+
"import": "./packages/vite/dist/currency.js"
|
|
108
|
+
},
|
|
109
|
+
"./dates": {
|
|
110
|
+
"types": "./packages/dates/types.ts",
|
|
111
|
+
"svelte": "./packages/dates/src/app.svelte.ts"
|
|
101
112
|
}
|
|
102
113
|
},
|
|
103
114
|
"devDependencies": {
|
|
@@ -108,7 +119,7 @@
|
|
|
108
119
|
"kitto": "1.6.0",
|
|
109
120
|
"oxfmt": "0.59.0",
|
|
110
121
|
"typescript": "6.0.3",
|
|
111
|
-
"vite": "
|
|
122
|
+
"vite": "7.2.2"
|
|
112
123
|
},
|
|
113
124
|
"peerDependencies": {
|
|
114
125
|
"@storyblok/svelte": "7.2.2",
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Narrow `Code` to the codes configured in the field's `currencies` option,
|
|
3
|
-
* e.g. `Currency<'GBP' | 'USD'>`.
|
|
4
|
-
*/
|
|
5
|
-
export interface Currency<Code extends string = Uppercase<string>> {
|
|
6
|
-
/** ISO 4217 code, e.g. `GBP` */
|
|
7
|
-
currency: Code
|
|
8
|
-
amount: number | null
|
|
9
|
-
/** `amount` formatted for display, e.g. `£130,000.00`; `null` when there's no amount */
|
|
10
|
-
formatted: string | null
|
|
11
|
-
}
|
|
1
|
+
export type { Currency, CurrencyField } from '../vite/src/lib/currency.js'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DateEntry {
|
|
2
|
+
/** `YYYY-MM-DD`, wall-clock in the field's `timezone` */
|
|
3
|
+
start_date: string
|
|
4
|
+
/** `HH:mm`; `null` makes this an all-day entry */
|
|
5
|
+
start_time: string | null
|
|
6
|
+
/** `YYYY-MM-DD`; `null` means the same day as `start_date` */
|
|
7
|
+
end_date: string | null
|
|
8
|
+
/** `HH:mm`; only set when `start_time` is */
|
|
9
|
+
end_time: string | null
|
|
10
|
+
/** the start as an ISO instant, e.g. `2026-09-15T10:00:00.000Z`; `null` for all-day entries, which are dates, not moments */
|
|
11
|
+
utc_start: string | null
|
|
12
|
+
/** the end as an ISO instant; `null` without an `end_time` */
|
|
13
|
+
utc_end: string | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Dates {
|
|
17
|
+
/** IANA zone the editor picked, e.g. `Europe/London` */
|
|
18
|
+
timezone: string
|
|
19
|
+
dates: DateEntry[]
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow `Code` to the codes configured in the field's `currencies` option,
|
|
3
|
+
* e.g. `Currency<'GBP' | 'USD'>`.
|
|
4
|
+
*/
|
|
5
|
+
export interface Currency<Code extends string = Uppercase<string>> {
|
|
6
|
+
/** ISO 4217 code, e.g. `GBP` */
|
|
7
|
+
currency: Code;
|
|
8
|
+
amount: number | null;
|
|
9
|
+
/** `amount` formatted for display, e.g. `£130,000.00`; `null` when there's no amount */
|
|
10
|
+
formatted: string | null;
|
|
11
|
+
}
|
|
12
|
+
/** The field's value as Storyblok stores it, `plugin` key included — what a Management API write needs. */
|
|
13
|
+
export type CurrencyField<Code extends string = Uppercase<string>> = Currency<Code> & {
|
|
14
|
+
plugin: "uiloco-currency";
|
|
15
|
+
};
|
|
16
|
+
/** `amount` as the plugin displays it, e.g. `£1,234.50`. The plugin formats with this, so the two can't drift. */
|
|
17
|
+
export declare function format_currency(amount: number, currency?: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* A spreadsheet cell or plain number as the `uiloco-currency` field's value:
|
|
20
|
+
* `to_currency('£1,234.50', 'GBP')` → `{ plugin, currency: 'GBP', amount: 1234.5, formatted: '£1,234.50' }`.
|
|
21
|
+
*
|
|
22
|
+
* Takes what a CSV export of money looks like — a leading symbol or code, thousands
|
|
23
|
+
* commas — and throws on anything else rather than guessing: negatives, `POA`, a
|
|
24
|
+
* decimal comma, or more places than the currency has (`1.005` in GBP). An import
|
|
25
|
+
* should stop on a bad row, not write a wrong price. Blank returns `null`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function to_currency<Code extends Uppercase<string> = "GBP">(value: string | number | null | undefined, currency?: Code): CurrencyField<Code> | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** `amount` as the plugin displays it, e.g. `£1,234.50`. The plugin formats with this, so the two can't drift. */
|
|
2
|
+
export function format_currency(amount, currency = "GBP") {
|
|
3
|
+
return new Intl.NumberFormat("en", {
|
|
4
|
+
style: "currency",
|
|
5
|
+
currency,
|
|
6
|
+
currencyDisplay: "narrowSymbol",
|
|
7
|
+
}).format(amount);
|
|
8
|
+
}
|
|
9
|
+
function minor_digits(currency) {
|
|
10
|
+
return (new Intl.NumberFormat("en", { style: "currency", currency }).resolvedOptions()
|
|
11
|
+
.maximumFractionDigits ?? 2);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A spreadsheet cell or plain number as the `uiloco-currency` field's value:
|
|
15
|
+
* `to_currency('£1,234.50', 'GBP')` → `{ plugin, currency: 'GBP', amount: 1234.5, formatted: '£1,234.50' }`.
|
|
16
|
+
*
|
|
17
|
+
* Takes what a CSV export of money looks like — a leading symbol or code, thousands
|
|
18
|
+
* commas — and throws on anything else rather than guessing: negatives, `POA`, a
|
|
19
|
+
* decimal comma, or more places than the currency has (`1.005` in GBP). An import
|
|
20
|
+
* should stop on a bad row, not write a wrong price. Blank returns `null`.
|
|
21
|
+
*/
|
|
22
|
+
export function to_currency(value, currency = "GBP") {
|
|
23
|
+
if (value === null || value === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
const input = String(value).trim();
|
|
26
|
+
if (input === "")
|
|
27
|
+
return null;
|
|
28
|
+
const raw = input
|
|
29
|
+
.replace(/^[^\d\s.,-]{1,3}\s?/, "")
|
|
30
|
+
.replace(/\s?[A-Z]{3}$/, "")
|
|
31
|
+
.replace(/,(?=\d{3}(?:\D|$))/g, "");
|
|
32
|
+
const match = /^(\d+)(?:\.(\d+))?$/.exec(raw);
|
|
33
|
+
if (!match)
|
|
34
|
+
throw new RangeError(`Not a ${currency} amount: ${JSON.stringify(value)}`);
|
|
35
|
+
if ((match[2]?.length ?? 0) > minor_digits(currency)) {
|
|
36
|
+
throw new RangeError(`${JSON.stringify(value)} has more decimal places than ${currency} allows`);
|
|
37
|
+
}
|
|
38
|
+
const amount = Number(raw);
|
|
39
|
+
return {
|
|
40
|
+
plugin: "uiloco-currency",
|
|
41
|
+
currency,
|
|
42
|
+
amount,
|
|
43
|
+
formatted: format_currency(amount, currency),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow `Code` to the codes configured in the field's `currencies` option,
|
|
3
|
+
* e.g. `Currency<'GBP' | 'USD'>`.
|
|
4
|
+
*/
|
|
5
|
+
export interface Currency<Code extends string = Uppercase<string>> {
|
|
6
|
+
/** ISO 4217 code, e.g. `GBP` */
|
|
7
|
+
currency: Code
|
|
8
|
+
amount: number | null
|
|
9
|
+
/** `amount` formatted for display, e.g. `£130,000.00`; `null` when there's no amount */
|
|
10
|
+
formatted: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The field's value as Storyblok stores it, `plugin` key included — what a Management API write needs. */
|
|
14
|
+
export type CurrencyField<Code extends string = Uppercase<string>> = Currency<Code> & {
|
|
15
|
+
plugin: "uiloco-currency"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** `amount` as the plugin displays it, e.g. `£1,234.50`. The plugin formats with this, so the two can't drift. */
|
|
19
|
+
export function format_currency(amount: number, currency: string = "GBP"): string {
|
|
20
|
+
return new Intl.NumberFormat("en", {
|
|
21
|
+
style: "currency",
|
|
22
|
+
currency,
|
|
23
|
+
currencyDisplay: "narrowSymbol",
|
|
24
|
+
}).format(amount)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function minor_digits(currency: string): number {
|
|
28
|
+
return (
|
|
29
|
+
new Intl.NumberFormat("en", { style: "currency", currency }).resolvedOptions()
|
|
30
|
+
.maximumFractionDigits ?? 2
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A spreadsheet cell or plain number as the `uiloco-currency` field's value:
|
|
36
|
+
* `to_currency('£1,234.50', 'GBP')` → `{ plugin, currency: 'GBP', amount: 1234.5, formatted: '£1,234.50' }`.
|
|
37
|
+
*
|
|
38
|
+
* Takes what a CSV export of money looks like — a leading symbol or code, thousands
|
|
39
|
+
* commas — and throws on anything else rather than guessing: negatives, `POA`, a
|
|
40
|
+
* decimal comma, or more places than the currency has (`1.005` in GBP). An import
|
|
41
|
+
* should stop on a bad row, not write a wrong price. Blank returns `null`.
|
|
42
|
+
*/
|
|
43
|
+
export function to_currency<Code extends Uppercase<string> = "GBP">(
|
|
44
|
+
value: string | number | null | undefined,
|
|
45
|
+
currency: Code = "GBP" as Code
|
|
46
|
+
): CurrencyField<Code> | null {
|
|
47
|
+
if (value === null || value === undefined) return null
|
|
48
|
+
const input = String(value).trim()
|
|
49
|
+
if (input === "") return null
|
|
50
|
+
|
|
51
|
+
const raw = input
|
|
52
|
+
.replace(/^[^\d\s.,-]{1,3}\s?/, "")
|
|
53
|
+
.replace(/\s?[A-Z]{3}$/, "")
|
|
54
|
+
.replace(/,(?=\d{3}(?:\D|$))/g, "")
|
|
55
|
+
const match = /^(\d+)(?:\.(\d+))?$/.exec(raw)
|
|
56
|
+
if (!match) throw new RangeError(`Not a ${currency} amount: ${JSON.stringify(value)}`)
|
|
57
|
+
if ((match[2]?.length ?? 0) > minor_digits(currency)) {
|
|
58
|
+
throw new RangeError(`${JSON.stringify(value)} has more decimal places than ${currency} allows`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const amount = Number(raw)
|
|
62
|
+
return {
|
|
63
|
+
plugin: "uiloco-currency",
|
|
64
|
+
currency,
|
|
65
|
+
amount,
|
|
66
|
+
formatted: format_currency(amount, currency),
|
|
67
|
+
}
|
|
68
|
+
}
|