afformative 0.7.0-beta.4 → 0.7.0-beta.5
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 +56 -21
- package/dist/index.cjs +5 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,8 @@ npm i afformative
|
|
|
38
38
|
|
|
39
39
|
## Quick Start
|
|
40
40
|
|
|
41
|
+
Afformative is framework-agnostic, but this section will assume usage with React.
|
|
42
|
+
|
|
41
43
|
A formatter is an object with `format`, `stringify`, and `compare` methods. Formatters must be created using the `createFormatter` function.
|
|
42
44
|
|
|
43
45
|
`createFormatter` accepts a single object parameter. `format` is the only required property, but specifying `stringify` is recommended in most cases.
|
|
@@ -49,6 +51,7 @@ import { ReactNode } from "react"
|
|
|
49
51
|
const dateFormatter = createFormatter<Date, ReactNode>({
|
|
50
52
|
format: value => <time dateTime={value.toISOString()}>{value.toLocaleDateString()}</time>,
|
|
51
53
|
stringify: value => value.toLocaleDateString(),
|
|
54
|
+
compare: value => value.valueOf(),
|
|
52
55
|
})
|
|
53
56
|
|
|
54
57
|
dateFormatter.format(new Date()) // <time dateTime="2026-05-30T09:17:26.263Z">30/05/2026</time>
|
|
@@ -75,11 +78,36 @@ const List = <TItem extends unknown>({ formatter, items }: ListProps<TItem>) =>
|
|
|
75
78
|
)
|
|
76
79
|
```
|
|
77
80
|
|
|
78
|
-
The `stringify` method is useful when you need a plain string representation of a value. For example, a combobox component can use it to match items against the user's typed input.
|
|
81
|
+
The `stringify` method is useful when you need a plain string representation of a value. For example, a combobox component can use it to match items against the user's typed input. The default implementation of `stringify` is simply `String(format(value))`.
|
|
82
|
+
|
|
83
|
+
## Accessing State
|
|
84
|
+
|
|
85
|
+
Create formatters inside hooks to access React context.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const useEnumFormatter = (enumType: string): Formatter<string, ReactNode> => {
|
|
89
|
+
const enumTranslationKeys = useSelector(selectEnumTranslationKeys(enumType))
|
|
90
|
+
const intl = useIntl()
|
|
91
|
+
|
|
92
|
+
return useMemo(
|
|
93
|
+
() =>
|
|
94
|
+
createFormatter<string, ReactNode>({
|
|
95
|
+
format: value => (
|
|
96
|
+
<FormattedMessage defaultMessage={value} id={enumTranslationKeys[value]} />
|
|
97
|
+
),
|
|
98
|
+
stringify: value =>
|
|
99
|
+
intl.formatMessage({ defaultMessage: value, id: enumTranslationKeys[value] }),
|
|
100
|
+
}),
|
|
101
|
+
[intl, enumTranslationKeys],
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Comparing Values
|
|
79
107
|
|
|
80
|
-
|
|
108
|
+
Every formatter exposes a `compare` method which can be directly passed to `Array.prototype.sort`. The default implementation compares the return values of `stringify` via `localeCompare`.
|
|
81
109
|
|
|
82
|
-
|
|
110
|
+
In the following example, `Amount` objects are sorted first by currency, then by value.
|
|
83
111
|
|
|
84
112
|
```tsx
|
|
85
113
|
import { createFormatter } from "afformative"
|
|
@@ -109,7 +137,7 @@ amounts.sort(amountFormatter.compare)
|
|
|
109
137
|
// [{ EUR, 1 }, { EUR, 5 }, { USD, 2 }, { USD, 3 }]
|
|
110
138
|
```
|
|
111
139
|
|
|
112
|
-
## Context
|
|
140
|
+
## Formatter Context
|
|
113
141
|
|
|
114
142
|
You can pass context to all formatter methods. Let's use a dummy table component as an example.
|
|
115
143
|
|
|
@@ -140,7 +168,9 @@ const Table = ({ rows, formatter }: TableProps) => (
|
|
|
140
168
|
)
|
|
141
169
|
```
|
|
142
170
|
|
|
143
|
-
Context allows the users of this table component to write purpose-built formatters, making it possible to take other values in the same row into account.
|
|
171
|
+
Context allows the users of this table component to write purpose-built formatters, making it possible to take other values in the same row into account.
|
|
172
|
+
|
|
173
|
+
For example, the following formatter would change the color of the cell value based on the previous value in the same row.
|
|
144
174
|
|
|
145
175
|
```tsx
|
|
146
176
|
import { createFormatter } from "afformative"
|
|
@@ -165,26 +195,31 @@ This formatter only makes sense in the context of our table component.
|
|
|
165
195
|
|
|
166
196
|
Because `row` and `cellIndex` are passed as context, the formatter still receives only the cell value as its first parameter. This means generic formatters (e.g. a currency formatter) can be passed to the table component without any changes.
|
|
167
197
|
|
|
168
|
-
##
|
|
198
|
+
## Formatter Meta
|
|
199
|
+
|
|
200
|
+
As explained above, context can be used to pass information from the consumer to the formatter. The `meta` property can be used to pass information from the formatter to the consumer instead. The base `FormatterMeta` interface is extensible via declaration merging.
|
|
169
201
|
|
|
170
|
-
|
|
202
|
+
For example, formatters can explicily mark themselves as print-friendly. Consumers may then decide to use `stringify` instead of `format` based on this flag.
|
|
171
203
|
|
|
172
204
|
```tsx
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
205
|
+
declare module "afformative" {
|
|
206
|
+
interface FormatterMeta<TInput, TOutput, TContext> {
|
|
207
|
+
isPrintFriendly?: boolean
|
|
208
|
+
}
|
|
209
|
+
}
|
|
176
210
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
211
|
+
const colorfulFormatter = createFormatter<string, ReactNode>({
|
|
212
|
+
format: value => <Colorful>{value}</Colorful>,
|
|
213
|
+
stringify: value => value,
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
interface PrinterProps {
|
|
217
|
+
content: string
|
|
218
|
+
formatter: Formatter<string, ReactNode>
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const Printer = ({ content, formatter }: PrinterProps) => {
|
|
222
|
+
return formatter.meta?.isPrintFriendly ? formatter.format(content) : formatter.stringify(content)
|
|
188
223
|
}
|
|
189
224
|
```
|
|
190
225
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
//#region src/createFormatter.ts
|
|
3
3
|
const createFormatter = (param) => {
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const stringify = stringifyParam ? (value, ctx) => stringifyParam(value, ctx) : (value, ctx) => String(format(value, ctx));
|
|
4
|
+
const { compare: compareParam, format, meta, stringify: stringifyParam } = param;
|
|
5
|
+
const stringify = stringifyParam ?? ((value, ctx) => String(format(value, ctx)));
|
|
7
6
|
return {
|
|
8
|
-
compare: compareParam
|
|
7
|
+
compare: compareParam ?? ((a, b, ctx) => stringify(a, ctx).localeCompare(stringify(b, ctx))),
|
|
9
8
|
format,
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
meta,
|
|
10
|
+
stringify
|
|
12
11
|
};
|
|
13
12
|
};
|
|
14
13
|
//#endregion
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/createFormatter.ts"],"sourcesContent":["export interface FormatterFormat<TInput, TOutput, TContext = unknown> {\n (value: TInput, ctx?: TContext): TOutput\n}\n\nexport interface FormatterStringify<TInput, TContext = unknown> {\n (value: TInput, ctx?: TContext): string\n}\n\nexport interface FormatterCompare<TInput, TContext = unknown> {\n (a: TInput, b: TInput, ctx?: TContext): number\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars\nexport interface FormatterMeta<TInput, TOutput, TContext = unknown> {}\n\nexport interface Formatter<TInput, TOutput, TContext = unknown> {\n compare: FormatterCompare<TInput, TContext>\n format: FormatterFormat<TInput, TOutput, TContext>\n
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/createFormatter.ts"],"sourcesContent":["export interface FormatterFormat<TInput, TOutput, TContext = unknown> {\n (value: TInput, ctx?: TContext): TOutput\n}\n\nexport interface FormatterStringify<TInput, TContext = unknown> {\n (value: TInput, ctx?: TContext): string\n}\n\nexport interface FormatterCompare<TInput, TContext = unknown> {\n (a: TInput, b: TInput, ctx?: TContext): number\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars\nexport interface FormatterMeta<TInput, TOutput, TContext = unknown> {}\n\nexport interface Formatter<TInput, TOutput, TContext = unknown> {\n compare: FormatterCompare<TInput, TContext>\n format: FormatterFormat<TInput, TOutput, TContext>\n meta?: FormatterMeta<TInput, TOutput, TContext>\n stringify: FormatterStringify<TInput, TContext>\n}\n\nexport interface CreateFormatterParam<TInput, TOutput, TContext = unknown> {\n compare?: FormatterCompare<TInput, TContext>\n format: FormatterFormat<TInput, TOutput, TContext>\n meta?: FormatterMeta<TInput, TOutput, TContext>\n stringify?: FormatterStringify<TInput, TContext>\n}\n\nexport const createFormatter = <TInput, TOutput, TContext = unknown>(\n param: CreateFormatterParam<TInput, TOutput, TContext>,\n): Formatter<TInput, TOutput, TContext> => {\n const { compare: compareParam, format, meta, stringify: stringifyParam } = param\n\n const stringify: FormatterStringify<TInput, TContext> =\n stringifyParam ?? ((value, ctx) => String(format(value, ctx)))\n\n const compare: FormatterCompare<TInput, TContext> =\n compareParam ?? ((a, b, ctx) => stringify(a, ctx).localeCompare(stringify(b, ctx)))\n\n return {\n compare,\n format,\n meta,\n stringify,\n }\n}\n"],"mappings":";;AA6BA,MAAa,mBACX,UACyC;CACzC,MAAM,EAAE,SAAS,cAAc,QAAQ,MAAM,WAAW,mBAAmB;CAE3E,MAAM,YACJ,oBAAoB,OAAO,QAAQ,OAAO,OAAO,OAAO,GAAG,CAAC;CAK9D,OAAO;EACL,SAHA,kBAAkB,GAAG,GAAG,QAAQ,UAAU,GAAG,GAAG,EAAE,cAAc,UAAU,GAAG,GAAG,CAAC;EAIjF;EACA;EACA;CACF;AACF"}
|
package/dist/index.d.cts
CHANGED
|
@@ -12,14 +12,14 @@ interface FormatterMeta<TInput, TOutput, TContext = unknown> {}
|
|
|
12
12
|
interface Formatter<TInput, TOutput, TContext = unknown> {
|
|
13
13
|
compare: FormatterCompare<TInput, TContext>;
|
|
14
14
|
format: FormatterFormat<TInput, TOutput, TContext>;
|
|
15
|
-
stringify: FormatterStringify<TInput, TContext>;
|
|
16
15
|
meta?: FormatterMeta<TInput, TOutput, TContext>;
|
|
16
|
+
stringify: FormatterStringify<TInput, TContext>;
|
|
17
17
|
}
|
|
18
18
|
interface CreateFormatterParam<TInput, TOutput, TContext = unknown> {
|
|
19
19
|
compare?: FormatterCompare<TInput, TContext>;
|
|
20
20
|
format: FormatterFormat<TInput, TOutput, TContext>;
|
|
21
|
-
stringify?: FormatterStringify<TInput, TContext>;
|
|
22
21
|
meta?: FormatterMeta<TInput, TOutput, TContext>;
|
|
22
|
+
stringify?: FormatterStringify<TInput, TContext>;
|
|
23
23
|
}
|
|
24
24
|
declare const createFormatter: <TInput, TOutput, TContext = unknown>(param: CreateFormatterParam<TInput, TOutput, TContext>) => Formatter<TInput, TOutput, TContext>;
|
|
25
25
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -12,14 +12,14 @@ interface FormatterMeta<TInput, TOutput, TContext = unknown> {}
|
|
|
12
12
|
interface Formatter<TInput, TOutput, TContext = unknown> {
|
|
13
13
|
compare: FormatterCompare<TInput, TContext>;
|
|
14
14
|
format: FormatterFormat<TInput, TOutput, TContext>;
|
|
15
|
-
stringify: FormatterStringify<TInput, TContext>;
|
|
16
15
|
meta?: FormatterMeta<TInput, TOutput, TContext>;
|
|
16
|
+
stringify: FormatterStringify<TInput, TContext>;
|
|
17
17
|
}
|
|
18
18
|
interface CreateFormatterParam<TInput, TOutput, TContext = unknown> {
|
|
19
19
|
compare?: FormatterCompare<TInput, TContext>;
|
|
20
20
|
format: FormatterFormat<TInput, TOutput, TContext>;
|
|
21
|
-
stringify?: FormatterStringify<TInput, TContext>;
|
|
22
21
|
meta?: FormatterMeta<TInput, TOutput, TContext>;
|
|
22
|
+
stringify?: FormatterStringify<TInput, TContext>;
|
|
23
23
|
}
|
|
24
24
|
declare const createFormatter: <TInput, TOutput, TContext = unknown>(param: CreateFormatterParam<TInput, TOutput, TContext>) => Formatter<TInput, TOutput, TContext>;
|
|
25
25
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
//#region src/createFormatter.ts
|
|
2
2
|
const createFormatter = (param) => {
|
|
3
|
-
const {
|
|
4
|
-
const
|
|
5
|
-
const stringify = stringifyParam ? (value, ctx) => stringifyParam(value, ctx) : (value, ctx) => String(format(value, ctx));
|
|
3
|
+
const { compare: compareParam, format, meta, stringify: stringifyParam } = param;
|
|
4
|
+
const stringify = stringifyParam ?? ((value, ctx) => String(format(value, ctx)));
|
|
6
5
|
return {
|
|
7
|
-
compare: compareParam
|
|
6
|
+
compare: compareParam ?? ((a, b, ctx) => stringify(a, ctx).localeCompare(stringify(b, ctx))),
|
|
8
7
|
format,
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
meta,
|
|
9
|
+
stringify
|
|
11
10
|
};
|
|
12
11
|
};
|
|
13
12
|
//#endregion
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/createFormatter.ts"],"sourcesContent":["export interface FormatterFormat<TInput, TOutput, TContext = unknown> {\n (value: TInput, ctx?: TContext): TOutput\n}\n\nexport interface FormatterStringify<TInput, TContext = unknown> {\n (value: TInput, ctx?: TContext): string\n}\n\nexport interface FormatterCompare<TInput, TContext = unknown> {\n (a: TInput, b: TInput, ctx?: TContext): number\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars\nexport interface FormatterMeta<TInput, TOutput, TContext = unknown> {}\n\nexport interface Formatter<TInput, TOutput, TContext = unknown> {\n compare: FormatterCompare<TInput, TContext>\n format: FormatterFormat<TInput, TOutput, TContext>\n
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/createFormatter.ts"],"sourcesContent":["export interface FormatterFormat<TInput, TOutput, TContext = unknown> {\n (value: TInput, ctx?: TContext): TOutput\n}\n\nexport interface FormatterStringify<TInput, TContext = unknown> {\n (value: TInput, ctx?: TContext): string\n}\n\nexport interface FormatterCompare<TInput, TContext = unknown> {\n (a: TInput, b: TInput, ctx?: TContext): number\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-unused-vars\nexport interface FormatterMeta<TInput, TOutput, TContext = unknown> {}\n\nexport interface Formatter<TInput, TOutput, TContext = unknown> {\n compare: FormatterCompare<TInput, TContext>\n format: FormatterFormat<TInput, TOutput, TContext>\n meta?: FormatterMeta<TInput, TOutput, TContext>\n stringify: FormatterStringify<TInput, TContext>\n}\n\nexport interface CreateFormatterParam<TInput, TOutput, TContext = unknown> {\n compare?: FormatterCompare<TInput, TContext>\n format: FormatterFormat<TInput, TOutput, TContext>\n meta?: FormatterMeta<TInput, TOutput, TContext>\n stringify?: FormatterStringify<TInput, TContext>\n}\n\nexport const createFormatter = <TInput, TOutput, TContext = unknown>(\n param: CreateFormatterParam<TInput, TOutput, TContext>,\n): Formatter<TInput, TOutput, TContext> => {\n const { compare: compareParam, format, meta, stringify: stringifyParam } = param\n\n const stringify: FormatterStringify<TInput, TContext> =\n stringifyParam ?? ((value, ctx) => String(format(value, ctx)))\n\n const compare: FormatterCompare<TInput, TContext> =\n compareParam ?? ((a, b, ctx) => stringify(a, ctx).localeCompare(stringify(b, ctx)))\n\n return {\n compare,\n format,\n meta,\n stringify,\n }\n}\n"],"mappings":";AA6BA,MAAa,mBACX,UACyC;CACzC,MAAM,EAAE,SAAS,cAAc,QAAQ,MAAM,WAAW,mBAAmB;CAE3E,MAAM,YACJ,oBAAoB,OAAO,QAAQ,OAAO,OAAO,OAAO,GAAG,CAAC;CAK9D,OAAO;EACL,SAHA,kBAAkB,GAAG,GAAG,QAAQ,UAAU,GAAG,GAAG,EAAE,cAAc,UAAU,GAAG,GAAG,CAAC;EAIjF;EACA;EACA;CACF;AACF"}
|