afformative 0.6.3 → 0.7.0-beta.2

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
@@ -11,11 +11,11 @@ A standardized way to format values in your React components.
11
11
  </h3>
12
12
 
13
13
  <p align="center">
14
- Afformative helps UI component libraries visualise arbitrary data in a reusable, plug-and-play fashion. Formatting dates and enumeration translations in your select fields and data grids has never been easier.
14
+ Afformative helps UI component libraries display arbitrary data in a reusable, plug-and-play fashion. Formatting dates and enum translations in your select fields and data grids has never been easier.
15
15
  </p>
16
16
 
17
17
  <p align="center">
18
- <a href="https://github.com/wafflepie/affomative/blob/master/LICENSE">
18
+ <a href="https://github.com/wafflepie/afformative/blob/master/LICENSE">
19
19
  <img src="https://flat.badgen.net/badge/license/MIT/blue" alt="MIT License" />
20
20
  </a>
21
21
 
@@ -26,94 +26,113 @@ Afformative helps UI component libraries visualise arbitrary data in a reusable,
26
26
 
27
27
  ## Installation
28
28
 
29
- Use either of these commands, depending on the package manager you prefer:
29
+ Use one of these commands, depending on your preferred package manager:
30
30
 
31
31
  ```sh
32
32
  yarn add afformative
33
33
 
34
+ pnpm add afformative
35
+
34
36
  npm i afformative
35
37
  ```
36
38
 
37
39
  ## Quick Start
38
40
 
39
- I'll try not to bore you too much, I promise.
40
-
41
- ### The Holy Standard
41
+ A formatter is an object with `format`, `stringify`, and `compare` methods. Formatters must be created using the `createFormatter` function.
42
42
 
43
- > Thou shalt not format thy values without afformative.
43
+ `createFormatter` accepts a single object parameter. `format` is the only required property, but specifying `stringify` is recommended in most cases.
44
44
 
45
- A formatter is an object with a `format` method. Formatters should be created solely using the `makeFormatter` factory.
45
+ ```tsx
46
+ import { createFormatter } from "afformative"
47
+ import { ReactNode } from "react"
46
48
 
47
- ```js
48
- import { makeFormatter } from "afformative"
49
-
50
- const upperCaseFormatter = makeFormatter(value => value.toUpperCase())
49
+ const dateFormatter = createFormatter<Date, ReactNode>({
50
+ format: value => <time dateTime={value.toISOString()}>{value.toLocaleDateString()}</time>,
51
+ stringify: value => value.toLocaleDateString(),
52
+ })
51
53
 
52
- upperCaseFormatter.format("foo") // "FOO"
54
+ dateFormatter.format(new Date()) // <time dateTime="2026-05-30T09:17:26.263Z">30/05/2026</time>
55
+ dateFormatter.stringify(new Date()) // "30/05/2026"
53
56
  ```
54
57
 
55
- Consume formatters in your UI component library using a conventional `formatter` prop.
58
+ Consume formatters in your UI component library via a conventional `formatter` prop.
56
59
 
57
- ```js
58
- import { makeFormatter } from "afformative"
60
+ ```tsx
61
+ import { Formatter } from "afformative"
62
+ import { ReactNode } from "react"
59
63
 
60
- // This is usually the best default formatter.
61
- const identityFormatter = makeFormatter(value => value)
62
-
63
- const Select = ({ formatter = identityFormatter, items, ...otherProps }) => (
64
- <select {...otherProps}>
65
- {items.map(item => {
66
- const text = formatter.format(item)
64
+ interface ListProps<TItem> {
65
+ formatter: Formatter<TItem, ReactNode>
66
+ items: TItem[]
67
+ }
67
68
 
68
- return (
69
- <option key={item} value={item}>
70
- {text}
71
- </option>
72
- )
73
- })}
74
- </select>
69
+ const List = <TItem extends unknown>({ formatter, items }: ListProps<TItem>) => (
70
+ <ul>
71
+ {items.map(item => (
72
+ <li key={formatter.stringify(item)}>{formatter.format(item)}</li>
73
+ ))}
74
+ </ul>
75
75
  )
76
76
  ```
77
77
 
78
- ### Usage Context
79
-
80
- Although formatters can render icons or custom translation components, we often need to access primitive data instead of React elements.
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
79
 
82
- > Lexicographic sorting of items based on translations is a typical real world example, especially when you are using a custom React component for visualising the translation keys alongside the actual translations.
80
+ ## Compare
83
81
 
84
- This is where usage suggestions come into play. Suggestions can be used to tell formatters that a value needs to be rendered with some special care. For example, pass `"primitive"` to tell a formatter that it should return a primitive value, such as a string.
82
+ Every formatter exposes a `compare` method, which can be used to sort values. The default implementation compares the return values of `stringify` via `localeCompare`. In the following example, `Amount` objects are sorted first by currency, then by value.
85
83
 
86
- ```js
87
- import { makeFormatter } from "afformative"
84
+ ```tsx
85
+ import { createFormatter } from "afformative"
86
+ import { ReactNode } from "react"
88
87
 
89
- const booleanFormatter = makeFormatter((value, suggestions) => {
90
- if (suggestions.includes("primitive")) {
91
- return value ? "True" : "False"
92
- }
88
+ interface Amount {
89
+ currency: string
90
+ value: number
91
+ }
93
92
 
94
- return <Icon type={value ? "success" : "failure"} />
93
+ const amountFormatter = createFormatter<Amount, ReactNode>({
94
+ format: ({ currency, value }) => (
95
+ <span className="currency">{`${currency} ${value.toFixed(2)}`}</span>
96
+ ),
97
+ stringify: ({ currency, value }) => `${currency} ${value.toFixed(2)}`,
98
+ compare: (a, b) => a.currency.localeCompare(b.currency) || a.value - b.value,
95
99
  })
96
100
 
97
- booleanFormatter.format(true) // <Icon type="success" />
98
- booleanFormatter.format(true, ["primitive"]) // "True"
101
+ const amounts: Amount[] = [
102
+ { currency: "USD", value: 3 },
103
+ { currency: "EUR", value: 1 },
104
+ { currency: "EUR", value: 5 },
105
+ { currency: "USD", value: 2 },
106
+ ]
107
+
108
+ amounts.sort(amountFormatter.compare)
109
+ // [{ EUR, 1 }, { EUR, 5 }, { USD, 2 }, { USD, 3 }]
99
110
  ```
100
111
 
101
- All formatters also have the `formatAsPrimitive` method, which automatically passes the `"primitive"` suggestion in addition to all other suggestions.
112
+ ## Usage Context
102
113
 
103
- ```js
104
- booleanFormatter.formatAsPrimitive(true) // "True"
105
- booleanFormatter.formatAsPrimitive(true, ["abbreviated"]) // "True"
106
- ```
114
+ You can pass an optional usage context object to all formatter methods. Let's use a dummy table component as an example.
115
+
116
+ ```tsx
117
+ import { Formatter } from "afformative"
118
+ import { ReactNode } from "react"
107
119
 
108
- You can also pass arbitrary data to formatters as the third argument: data context. Let's use a dummy table component as an example.
120
+ interface TableFormatterUsageContext {
121
+ row: number[]
122
+ cellIndex: number
123
+ }
124
+
125
+ interface TableProps {
126
+ rows: number[][]
127
+ formatter: Formatter<number, ReactNode, TableFormatterUsageContext>
128
+ }
109
129
 
110
- ```js
111
- const Table = ({ rows, formatter = identityFormatter }) => (
130
+ const Table = ({ rows, formatter }: TableProps) => (
112
131
  <table>
113
132
  {rows.map(row => (
114
133
  <tr>
115
134
  {row.map((cell, cellIndex) => (
116
- <td>{formatter.format(cell, [], { row, cellIndex })}</td>
135
+ <td>{formatter.format(cell, { row, cellIndex })}</td>
117
136
  ))}
118
137
  </tr>
119
138
  ))}
@@ -121,70 +140,50 @@ const Table = ({ rows, formatter = identityFormatter }) => (
121
140
  )
122
141
  ```
123
142
 
124
- Data 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. For example, the following formatter would change the color of the cell value based on the previous value in the same row.
125
-
126
- ```js
127
- const rowTrendFormatter = makeFormatter((value, suggestions, { row, cellIndex }) => {
128
- if (cellIndex === 0) {
129
- return <span>{value}</span>
130
- }
131
-
132
- const previousValue = row[cellIndex - 1]
133
-
134
- return <span style={{ color: value >= previousValue ? "green" : "red" }}>{value}</span>
135
- })
136
- ```
137
-
138
- Of course, this formatter only makes sense for our table component, nowhere else.
139
-
140
- Because `row` and `cellIndex` are passed as the data context, the formatter still receives just the cell value as its first parameter! This allows us to pass other generic formatters (e.g. a currency formatter) to the table component without having to worry about the value structure.
143
+ Usage 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. For example, the following formatter would change the color of the cell value based on the previous value in the same row.
141
144
 
142
- ### Accessing React Context Reliably
145
+ ```tsx
146
+ import { createFormatter } from "afformative"
147
+ import { ReactNode } from "react"
143
148
 
144
- Use hooks.
149
+ import { TableFormatterUsageContext } from "./Table"
145
150
 
146
- ```js
147
- const useEnumFormatter = enumType => {
148
- // `useSelector` is from `react-redux`, `useIntl` from `react-intl`.
149
- // `makeSelectEnumTranslationKeys` is a made-up Redux selector factory.
150
- const enumTranslationKeys = useSelector(makeSelectEnumTranslationKeys(enumType))
151
- const intl = useIntl()
151
+ const rowTrendFormatter = createFormatter<number, ReactNode, TableFormatterUsageContext>({
152
+ format: (value, { row, cellIndex }) => {
153
+ if (cellIndex === 0) {
154
+ return <span>{value}</span>
155
+ }
152
156
 
153
- return useMemo(
154
- () =>
155
- makeFormatter(value =>
156
- intl.formatMessage({
157
- defaultMessage: value,
158
- id: enumTranslationKeys[value],
159
- }),
160
- ),
161
- [intl, enumTranslationKeys],
162
- )
163
- }
157
+ const previousValue = row[cellIndex - 1]
164
158
 
165
- const someEnumFormatter = useEnumFormatter("someEnum")
159
+ return <span style={{ color: value >= previousValue ? "green" : "red" }}>{value}</span>
160
+ },
161
+ })
166
162
  ```
167
163
 
168
- ### Consuming Formatters
164
+ This formatter only makes sense in the context of our table component.
165
+
166
+ Because `row` and `cellIndex` are passed as the usage 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.
169
167
 
170
- As mentioned earlier, formatters should be passed to components using the conventional `formatter` prop. Alternatively, if you need to pass multiple formatters (e.g. in case of column definitions), pass an array of objects where each object has a `formatter` property.
168
+ ## Accessing React Context
171
169
 
172
- All formatters also expose the `wrap` method. You can use this method to alter the behaviour of the formatter for some specific values.
170
+ Create formatters inside custom hooks to access React context values such as translations or application state.
173
171
 
174
- ```js
175
- const useSnowflakeAwareFormatter = formatter => {
172
+ ```tsx
173
+ const useEnumFormatter = (enumType: string): Formatter<string, ReactNode> => {
174
+ const enumTranslationKeys = useSelector(selectEnumTranslationKeys(enumType))
176
175
  const intl = useIntl()
177
176
 
178
177
  return useMemo(
179
178
  () =>
180
- formatter.wrap((delegate, value) => {
181
- if (isSnowflake(value)) {
182
- return intl.formatMessage(messages.snowflake)
183
- }
184
-
185
- return delegate(value)
179
+ createFormatter<string, ReactNode>({
180
+ format: value => (
181
+ <FormattedMessage defaultMessage={value} id={enumTranslationKeys[value]} />
182
+ ),
183
+ stringify: value =>
184
+ intl.formatMessage({ defaultMessage: value, id: enumTranslationKeys[value] }),
186
185
  }),
187
- [formatter, intl],
186
+ [intl, enumTranslationKeys],
188
187
  )
189
188
  }
190
189
  ```
package/dist/index.cjs ADDED
@@ -0,0 +1,15 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/createFormatter.ts
3
+ const createFormatter = (param) => {
4
+ const { format: formatParam, stringify: stringifyParam, compare: compareParam, ...rest } = param;
5
+ const format = (value, usageContext) => formatParam(value, usageContext ?? {});
6
+ const stringify = stringifyParam ? (value, usageContext) => stringifyParam(value, usageContext ?? {}) : (value, usageContext) => String(format(value, usageContext ?? {}));
7
+ return {
8
+ compare: compareParam ? (a, b, usageContext) => compareParam(a, b, usageContext ?? {}) : (a, b, usageContext) => stringify(a, usageContext ?? {}).localeCompare(stringify(b, usageContext ?? {})),
9
+ format,
10
+ stringify,
11
+ ...rest
12
+ };
13
+ };
14
+ //#endregion
15
+ exports.createFormatter = createFormatter;
@@ -0,0 +1,33 @@
1
+ //#region src/createFormatter.d.ts
2
+ interface FormatterFormatDefinition<TInput, TOutput, TUsageContext extends object = object> {
3
+ (value: TInput, usageContext: Partial<TUsageContext>): TOutput;
4
+ }
5
+ interface FormatterFormat<TInput, TOutput, TUsageContext extends object = object> {
6
+ (value: TInput, usageContext?: Partial<TUsageContext>): TOutput;
7
+ }
8
+ interface FormatterStringifyDefinition<TInput, TUsageContext extends object = object> {
9
+ (value: TInput, usageContext: Partial<TUsageContext>): string;
10
+ }
11
+ interface FormatterStringify<TInput, TUsageContext extends object = object> {
12
+ (value: TInput, usageContext?: Partial<TUsageContext>): string;
13
+ }
14
+ interface FormatterCompareDefinition<TInput, TUsageContext extends object = object> {
15
+ (a: TInput, b: TInput, usageContext: Partial<TUsageContext>): number;
16
+ }
17
+ interface FormatterCompare<TInput, TUsageContext extends object = object> {
18
+ (a: TInput, b: TInput, usageContext?: Partial<TUsageContext>): number;
19
+ }
20
+ interface Formatter<TInput, TOutput, TUsageContext extends object = object> {
21
+ compare: FormatterCompare<TInput, TUsageContext>;
22
+ format: FormatterFormat<TInput, TOutput, TUsageContext>;
23
+ stringify: FormatterStringify<TInput, TUsageContext>;
24
+ name?: string;
25
+ }
26
+ interface CreateFormatterParam<TInput, TOutput, TUsageContext extends object = object> {
27
+ compare?: FormatterCompareDefinition<TInput, TUsageContext>;
28
+ format: FormatterFormatDefinition<TInput, TOutput, TUsageContext>;
29
+ stringify?: FormatterStringifyDefinition<TInput, TUsageContext>;
30
+ }
31
+ declare const createFormatter: <TInput, TOutput, TUsageContext extends object = object>(param: CreateFormatterParam<TInput, TOutput, TUsageContext>) => Formatter<TInput, TOutput, TUsageContext>;
32
+ //#endregion
33
+ export { CreateFormatterParam, Formatter, FormatterCompare, FormatterCompareDefinition, FormatterFormat, FormatterFormatDefinition, FormatterStringify, FormatterStringifyDefinition, createFormatter };
@@ -0,0 +1,33 @@
1
+ //#region src/createFormatter.d.ts
2
+ interface FormatterFormatDefinition<TInput, TOutput, TUsageContext extends object = object> {
3
+ (value: TInput, usageContext: Partial<TUsageContext>): TOutput;
4
+ }
5
+ interface FormatterFormat<TInput, TOutput, TUsageContext extends object = object> {
6
+ (value: TInput, usageContext?: Partial<TUsageContext>): TOutput;
7
+ }
8
+ interface FormatterStringifyDefinition<TInput, TUsageContext extends object = object> {
9
+ (value: TInput, usageContext: Partial<TUsageContext>): string;
10
+ }
11
+ interface FormatterStringify<TInput, TUsageContext extends object = object> {
12
+ (value: TInput, usageContext?: Partial<TUsageContext>): string;
13
+ }
14
+ interface FormatterCompareDefinition<TInput, TUsageContext extends object = object> {
15
+ (a: TInput, b: TInput, usageContext: Partial<TUsageContext>): number;
16
+ }
17
+ interface FormatterCompare<TInput, TUsageContext extends object = object> {
18
+ (a: TInput, b: TInput, usageContext?: Partial<TUsageContext>): number;
19
+ }
20
+ interface Formatter<TInput, TOutput, TUsageContext extends object = object> {
21
+ compare: FormatterCompare<TInput, TUsageContext>;
22
+ format: FormatterFormat<TInput, TOutput, TUsageContext>;
23
+ stringify: FormatterStringify<TInput, TUsageContext>;
24
+ name?: string;
25
+ }
26
+ interface CreateFormatterParam<TInput, TOutput, TUsageContext extends object = object> {
27
+ compare?: FormatterCompareDefinition<TInput, TUsageContext>;
28
+ format: FormatterFormatDefinition<TInput, TOutput, TUsageContext>;
29
+ stringify?: FormatterStringifyDefinition<TInput, TUsageContext>;
30
+ }
31
+ declare const createFormatter: <TInput, TOutput, TUsageContext extends object = object>(param: CreateFormatterParam<TInput, TOutput, TUsageContext>) => Formatter<TInput, TOutput, TUsageContext>;
32
+ //#endregion
33
+ export { CreateFormatterParam, Formatter, FormatterCompare, FormatterCompareDefinition, FormatterFormat, FormatterFormatDefinition, FormatterStringify, FormatterStringifyDefinition, createFormatter };
package/dist/index.mjs ADDED
@@ -0,0 +1,14 @@
1
+ //#region src/createFormatter.ts
2
+ const createFormatter = (param) => {
3
+ const { format: formatParam, stringify: stringifyParam, compare: compareParam, ...rest } = param;
4
+ const format = (value, usageContext) => formatParam(value, usageContext ?? {});
5
+ const stringify = stringifyParam ? (value, usageContext) => stringifyParam(value, usageContext ?? {}) : (value, usageContext) => String(format(value, usageContext ?? {}));
6
+ return {
7
+ compare: compareParam ? (a, b, usageContext) => compareParam(a, b, usageContext ?? {}) : (a, b, usageContext) => stringify(a, usageContext ?? {}).localeCompare(stringify(b, usageContext ?? {})),
8
+ format,
9
+ stringify,
10
+ ...rest
11
+ };
12
+ };
13
+ //#endregion
14
+ export { createFormatter };
package/package.json CHANGED
@@ -1,11 +1,6 @@
1
1
  {
2
2
  "name": "afformative",
3
- "version": "0.6.3",
4
- "license": "MIT",
5
- "main": "dist/afformative.cjs.js",
6
- "module": "dist/afformative.esm.js",
7
- "unpkg": "dist/afformative.umd.js",
8
- "types": "dist/types/index.d.ts",
3
+ "version": "0.7.0-beta.2",
9
4
  "description": "A standardized way to format values in your React components.",
10
5
  "keywords": [
11
6
  "react",
@@ -24,15 +19,49 @@
24
19
  "values"
25
20
  ],
26
21
  "repository": "https://github.com/wafflepie/afformative",
22
+ "license": "MIT",
27
23
  "contributors": [
28
- "Vaclav Jancarik <vaclav.janc@gmail.com>"
24
+ "Vaclav Jancarik <hi@wafflepie.dev>"
25
+ ],
26
+ "sideEffects": false,
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "import": "./dist/index.mjs",
31
+ "require": "./dist/index.cjs"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.mjs",
37
+ "types": "./dist/index.d.cts",
38
+ "files": [
39
+ "dist"
29
40
  ],
30
- "dependencies": {
31
- "@babel/runtime": "^7.12.1"
41
+ "scripts": {
42
+ "build": "tsdown",
43
+ "prepublishOnly": "pnpm run build",
44
+ "release": "bumpp",
45
+ "test": "pnpm test:prettier && pnpm run test:typescript && pnpm run test:eslint && pnpm run test:vitest",
46
+ "test:eslint": "eslint src",
47
+ "test:prettier": "prettier --check src",
48
+ "test:typescript": "tsc --noEmit",
49
+ "test:vitest": "vitest run"
50
+ },
51
+ "devDependencies": {
52
+ "@eslint/js": "^10.0.1",
53
+ "@types/node": "^25.6.2",
54
+ "@typescript/native-preview": "^7.0.0-dev.20260527.2",
55
+ "bumpp": "^11.1.0",
56
+ "eslint": "^10.4.1",
57
+ "prettier": "^3.8.3",
58
+ "tsdown": "^0.22.1",
59
+ "typescript": "^6.0.3",
60
+ "typescript-eslint": "^8.60.0",
61
+ "vitest": "^4.1.7"
32
62
  },
63
+ "packageManager": "pnpm@11.5.0+sha512.dbfcc4f81cf48597afd4bc391ffdf12c11f1a9fb83a395bfa6b0a2d9cc2fd8ffebafdb1ccbd529632153f793904c2615b7f09fe1a345473fd1c35845172a8eb1",
33
64
  "publishConfig": {
34
65
  "access": "public"
35
- },
36
- "sideEffects": false,
37
- "gitHead": "ddc8c8682e2777c730108466792b7670d8a43f06"
66
+ }
38
67
  }
@@ -1,61 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray');
6
-
7
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
-
9
- var _toConsumableArray__default = /*#__PURE__*/_interopDefaultLegacy(_toConsumableArray);
10
-
11
- var ensureFormatSuggestionIntegrity = function ensureFormatSuggestionIntegrity(suggestions) {
12
- return suggestions.includes("primitive") || !suggestions.includes("comparable") ? suggestions : ["primitive"].concat(_toConsumableArray__default["default"](suggestions));
13
- };
14
-
15
- var ensureFormatAsPrimitiveSuggestionIntegrity = function ensureFormatAsPrimitiveSuggestionIntegrity(suggestions) {
16
- return suggestions.includes("primitive") ? suggestions : ["primitive"].concat(_toConsumableArray__default["default"](suggestions));
17
- };
18
-
19
- /**
20
- * Creates a new formatter.
21
- *
22
- * @param format Function used to format the value.
23
- * @param formatterOptions Additional options for the formatter.
24
- */
25
- var makeFormatter = function makeFormatter(format, formatterOptions) {
26
- var formatter = function formatter(props) {
27
- var _format, _props$suggestions;
28
-
29
- return (_format = format(props.children, (_props$suggestions = props.suggestions) !== null && _props$suggestions !== void 0 ? _props$suggestions : [], props)) !== null && _format !== void 0 ? _format : null;
30
- };
31
-
32
- formatter.displayName = formatterOptions === null || formatterOptions === void 0 ? void 0 : formatterOptions.displayName;
33
-
34
- formatter.format = function (value) {
35
- var suggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
36
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
37
- return format(value, ensureFormatSuggestionIntegrity(suggestions), dataContext);
38
- };
39
-
40
- formatter.formatAsPrimitive = function (value) {
41
- var suggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
42
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
43
- return format(value, ensureFormatAsPrimitiveSuggestionIntegrity(suggestions), dataContext);
44
- };
45
-
46
- formatter.wrap = function (nextFormat, nextFormatterOptions) {
47
- var nextFormatter = makeFormatter(function (value, suggestions, dataContext) {
48
- var delegate = function delegate(delegatedValue, delegatedSuggestions, delegatedDataContext) {
49
- return formatter.format(delegatedValue, delegatedSuggestions !== null && delegatedSuggestions !== void 0 ? delegatedSuggestions : suggestions, delegatedDataContext !== null && delegatedDataContext !== void 0 ? delegatedDataContext : dataContext);
50
- };
51
-
52
- return nextFormat(delegate, value, suggestions, dataContext);
53
- }, nextFormatterOptions !== null && nextFormatterOptions !== void 0 ? nextFormatterOptions : formatterOptions);
54
- nextFormatter.innerFormatter = formatter;
55
- return nextFormatter;
56
- };
57
-
58
- return formatter;
59
- };
60
-
61
- exports.makeFormatter = makeFormatter;
@@ -1,53 +0,0 @@
1
- import _toConsumableArray from '@babel/runtime/helpers/esm/toConsumableArray';
2
-
3
- var ensureFormatSuggestionIntegrity = function ensureFormatSuggestionIntegrity(suggestions) {
4
- return suggestions.includes("primitive") || !suggestions.includes("comparable") ? suggestions : ["primitive"].concat(_toConsumableArray(suggestions));
5
- };
6
-
7
- var ensureFormatAsPrimitiveSuggestionIntegrity = function ensureFormatAsPrimitiveSuggestionIntegrity(suggestions) {
8
- return suggestions.includes("primitive") ? suggestions : ["primitive"].concat(_toConsumableArray(suggestions));
9
- };
10
-
11
- /**
12
- * Creates a new formatter.
13
- *
14
- * @param format Function used to format the value.
15
- * @param formatterOptions Additional options for the formatter.
16
- */
17
- var makeFormatter = function makeFormatter(format, formatterOptions) {
18
- var formatter = function formatter(props) {
19
- var _format, _props$suggestions;
20
-
21
- return (_format = format(props.children, (_props$suggestions = props.suggestions) !== null && _props$suggestions !== void 0 ? _props$suggestions : [], props)) !== null && _format !== void 0 ? _format : null;
22
- };
23
-
24
- formatter.displayName = formatterOptions === null || formatterOptions === void 0 ? void 0 : formatterOptions.displayName;
25
-
26
- formatter.format = function (value) {
27
- var suggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
28
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
29
- return format(value, ensureFormatSuggestionIntegrity(suggestions), dataContext);
30
- };
31
-
32
- formatter.formatAsPrimitive = function (value) {
33
- var suggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
34
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
35
- return format(value, ensureFormatAsPrimitiveSuggestionIntegrity(suggestions), dataContext);
36
- };
37
-
38
- formatter.wrap = function (nextFormat, nextFormatterOptions) {
39
- var nextFormatter = makeFormatter(function (value, suggestions, dataContext) {
40
- var delegate = function delegate(delegatedValue, delegatedSuggestions, delegatedDataContext) {
41
- return formatter.format(delegatedValue, delegatedSuggestions !== null && delegatedSuggestions !== void 0 ? delegatedSuggestions : suggestions, delegatedDataContext !== null && delegatedDataContext !== void 0 ? delegatedDataContext : dataContext);
42
- };
43
-
44
- return nextFormat(delegate, value, suggestions, dataContext);
45
- }, nextFormatterOptions !== null && nextFormatterOptions !== void 0 ? nextFormatterOptions : formatterOptions);
46
- nextFormatter.innerFormatter = formatter;
47
- return nextFormatter;
48
- };
49
-
50
- return formatter;
51
- };
52
-
53
- export { makeFormatter };
@@ -1,96 +0,0 @@
1
- (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Afformative = {}));
5
- })(this, (function (exports) { 'use strict';
6
-
7
- function _arrayLikeToArray(arr, len) {
8
- if (len == null || len > arr.length) len = arr.length;
9
-
10
- for (var i = 0, arr2 = new Array(len); i < len; i++) {
11
- arr2[i] = arr[i];
12
- }
13
-
14
- return arr2;
15
- }
16
-
17
- function _arrayWithoutHoles(arr) {
18
- if (Array.isArray(arr)) return _arrayLikeToArray(arr);
19
- }
20
-
21
- function _iterableToArray(iter) {
22
- if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
23
- }
24
-
25
- function _unsupportedIterableToArray(o, minLen) {
26
- if (!o) return;
27
- if (typeof o === "string") return _arrayLikeToArray(o, minLen);
28
- var n = Object.prototype.toString.call(o).slice(8, -1);
29
- if (n === "Object" && o.constructor) n = o.constructor.name;
30
- if (n === "Map" || n === "Set") return Array.from(o);
31
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
32
- }
33
-
34
- function _nonIterableSpread() {
35
- throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
36
- }
37
-
38
- function _toConsumableArray(arr) {
39
- return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
40
- }
41
-
42
- var ensureFormatSuggestionIntegrity = function ensureFormatSuggestionIntegrity(suggestions) {
43
- return suggestions.includes("primitive") || !suggestions.includes("comparable") ? suggestions : ["primitive"].concat(_toConsumableArray(suggestions));
44
- };
45
-
46
- var ensureFormatAsPrimitiveSuggestionIntegrity = function ensureFormatAsPrimitiveSuggestionIntegrity(suggestions) {
47
- return suggestions.includes("primitive") ? suggestions : ["primitive"].concat(_toConsumableArray(suggestions));
48
- };
49
-
50
- /**
51
- * Creates a new formatter.
52
- *
53
- * @param format Function used to format the value.
54
- * @param formatterOptions Additional options for the formatter.
55
- */
56
- var makeFormatter = function makeFormatter(format, formatterOptions) {
57
- var formatter = function formatter(props) {
58
- var _format, _props$suggestions;
59
-
60
- return (_format = format(props.children, (_props$suggestions = props.suggestions) !== null && _props$suggestions !== void 0 ? _props$suggestions : [], props)) !== null && _format !== void 0 ? _format : null;
61
- };
62
-
63
- formatter.displayName = formatterOptions === null || formatterOptions === void 0 ? void 0 : formatterOptions.displayName;
64
-
65
- formatter.format = function (value) {
66
- var suggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
67
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
68
- return format(value, ensureFormatSuggestionIntegrity(suggestions), dataContext);
69
- };
70
-
71
- formatter.formatAsPrimitive = function (value) {
72
- var suggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
73
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
74
- return format(value, ensureFormatAsPrimitiveSuggestionIntegrity(suggestions), dataContext);
75
- };
76
-
77
- formatter.wrap = function (nextFormat, nextFormatterOptions) {
78
- var nextFormatter = makeFormatter(function (value, suggestions, dataContext) {
79
- var delegate = function delegate(delegatedValue, delegatedSuggestions, delegatedDataContext) {
80
- return formatter.format(delegatedValue, delegatedSuggestions !== null && delegatedSuggestions !== void 0 ? delegatedSuggestions : suggestions, delegatedDataContext !== null && delegatedDataContext !== void 0 ? delegatedDataContext : dataContext);
81
- };
82
-
83
- return nextFormat(delegate, value, suggestions, dataContext);
84
- }, nextFormatterOptions !== null && nextFormatterOptions !== void 0 ? nextFormatterOptions : formatterOptions);
85
- nextFormatter.innerFormatter = formatter;
86
- return nextFormatter;
87
- };
88
-
89
- return formatter;
90
- };
91
-
92
- exports.makeFormatter = makeFormatter;
93
-
94
- Object.defineProperty(exports, '__esModule', { value: true });
95
-
96
- }));
@@ -1 +0,0 @@
1
- !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).Afformative={})}(this,(function(t){"use strict";function n(t,n){(null==n||n>t.length)&&(n=t.length);for(var r=0,e=Array(n);n>r;r++)e[r]=t[r];return e}function r(t){return function(t){if(Array.isArray(t))return n(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,r){if(t){if("string"==typeof t)return n(t,r);var e=Object.prototype.toString.call(t).slice(8,-1);return"Object"===e&&t.constructor&&(e=t.constructor.name),"Map"===e||"Set"===e?Array.from(t):"Arguments"===e||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e)?n(t,r):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}var e=function(t){return t.includes("primitive")||!t.includes("comparable")?t:["primitive"].concat(r(t))},i=function(t){return t.includes("primitive")?t:["primitive"].concat(r(t))};t.makeFormatter=function t(n,r){var o=function(t){var r,e;return null!==(r=n(t.children,null!==(e=t.suggestions)&&void 0!==e?e:[],t))&&void 0!==r?r:null};return o.displayName=null==r?void 0:r.displayName,o.format=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n(t,e(r),i)},o.formatAsPrimitive=function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],e=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return n(t,i(r),e)},o.wrap=function(n,e){var i=t((function(t,r,e){return n((function(t,n,i){return o.format(t,null!=n?n:r,null!=i?i:e)}),t,r,e)}),null!=e?e:r);return i.innerFormatter=o,i},o},Object.defineProperty(t,"__esModule",{value:!0})}));
@@ -1,2 +0,0 @@
1
- export * from "./makeFormatter";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA"}
@@ -1,97 +0,0 @@
1
- declare type PrimitiveSuggestion = "primitive" | "comparable";
2
- declare type Suggestion = PrimitiveSuggestion | "abbreviated" | "as-icon"
3
- /**
4
- * @deprecated Since v0.6.3. Use `"as-icon"` or `"with-icon"` instead.
5
- */
6
- | "icon" | "verbose" | "with-icon";
7
- declare type DataContext = Record<string, any>;
8
- interface FormatterOptions {
9
- /** Formatter name, useful for debugging or advanced pattern matching. */
10
- displayName?: string;
11
- }
12
- interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
13
- (
14
- /** Value to format. */
15
- value: TInput,
16
- /** Suggestions passed by the consumer of a formatter. */
17
- suggestions: Suggestion[],
18
- /** Additional data context to be used by the formatter. */
19
- dataContext: Partial<TDataContext>): TOutput | TPrimitiveOutput;
20
- }
21
- interface FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
22
- (
23
- /** Value to format. */
24
- value: TInput,
25
- /** Suggestions the formatter should take note of. */
26
- suggestions?: Suggestion[],
27
- /** Additional data context the formatter might find useful. */
28
- dataContext?: Partial<TDataContext>): TOutput | TPrimitiveOutput;
29
- }
30
- interface FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext extends DataContext> {
31
- (
32
- /** Value to format. */
33
- value: TInput,
34
- /** Suggestions the formatter should take note of in addition to `primitive`. */
35
- suggestions?: Suggestion[],
36
- /** Additional data context the formatter might find useful. */
37
- dataContext?: Partial<TDataContext>): TPrimitiveOutput;
38
- }
39
- interface FormatChainDefinition<TInnerInput, TInnerOutput, TInnerPrimitiveInput, TInnerDataContext extends DataContext, TOuterInput, TOuterOutput, TOuterPrimitiveOutput, TOuterDataContext extends DataContext> {
40
- (
41
- /**
42
- * The `formatter.format` method which can be used to delegate the formatting
43
- * to the wrapped formatter. Delegation is simplified so if no suggestions or contextual
44
- * props are passed, the original ones are used instead.
45
- */
46
- delegate: FormatMethod<TInnerInput, TInnerOutput, TInnerPrimitiveInput, TInnerDataContext>,
47
- /** Value to format. */
48
- value: TOuterInput,
49
- /** Suggestions the formatter should take note of. */
50
- suggestions: Suggestion[],
51
- /** Additional data context the formatter might find useful. */
52
- dataContext: Partial<TOuterDataContext>): TOuterOutput | TOuterPrimitiveOutput;
53
- }
54
- declare type FormatterProps<TInput, TDataContext extends DataContext> = {
55
- /** Value to format. */
56
- children: TInput;
57
- /** Suggestions the formatter should take note of. */
58
- suggestions?: Suggestion[];
59
- } & Partial<TDataContext>;
60
- export interface Formatter<TInput, TOutput, TPrimitiveOutput = TOutput, TDataContext extends DataContext = DataContext> {
61
- /** Formatter name, useful for debugging or advanced pattern matching. */
62
- displayName?: string;
63
- /** Formats a value. */
64
- format: FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext>;
65
- /** Formats a value with the `primitive` suggestion. */
66
- formatAsPrimitive: FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext>;
67
- /** The callee of the `wrap` method used to produce this formatter. */
68
- innerFormatter?: Formatter<any, any, any, any>;
69
- /**
70
- * Creates a new formatter from an existing one. Allows overriding of formatter behaviour
71
- * for certain values.
72
- */
73
- wrap: <TNextInput = TInput, TNextOutput = TOutput, TNextPrimitiveOutput = TPrimitiveOutput, TNextDataContext extends TDataContext = TDataContext>(
74
- /**
75
- * Function used to format the value. Has the same signature as the one passed
76
- * to `makeFormatter`, except a `delegate` function is passed in the first position.
77
- * This function can be used to delegate formatting to the original (inner) formatter.
78
- */
79
- nextFormat: FormatChainDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext, TNextInput, TNextOutput, TNextPrimitiveOutput, TNextDataContext>,
80
- /** New formatter options, replacing the original ones. */
81
- nextFormatterOptions?: FormatterOptions) => Formatter<TNextInput, TNextOutput, TNextPrimitiveOutput, TNextDataContext>;
82
- /**
83
- * Backwards-compatible way to use the formatter as a React component.
84
- *
85
- * @deprecated Since v0.6.0. Prefer using the `format` method instead.
86
- */
87
- (props: FormatterProps<TInput, TDataContext>): TOutput | TPrimitiveOutput | null;
88
- }
89
- /**
90
- * Creates a new formatter.
91
- *
92
- * @param format Function used to format the value.
93
- * @param formatterOptions Additional options for the formatter.
94
- */
95
- export declare const makeFormatter: <TInput, TOutput, TPrimitiveOutput = TOutput, TDataContext extends DataContext = DataContext>(format: FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext>, formatterOptions?: FormatterOptions | undefined) => Formatter<TInput, TOutput, TPrimitiveOutput, TDataContext>;
96
- export {};
97
- //# sourceMappingURL=makeFormatter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"makeFormatter.d.ts","sourceRoot":"","sources":["../../src/makeFormatter.ts"],"names":[],"mappings":"AAAA,aAAK,mBAAmB,GAAG,WAAW,GAAG,YAAY,CAAA;AAErD,aAAK,UAAU,GACX,mBAAmB,GACnB,aAAa,GACb,SAAS;AACX;;GAEG;GACD,MAAM,GACN,SAAS,GACT,WAAW,CAAA;AAUf,aAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAEtC,UAAU,gBAAgB;IACxB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,SAAS,WAAW;IAC5F;IACE,uBAAuB;IACvB,KAAK,EAAE,MAAM;IACb,yDAAyD;IACzD,WAAW,EAAE,UAAU,EAAE;IACzB,2DAA2D;IAC3D,WAAW,EAAE,OAAO,CAAC,YAAY,CAAC,GACjC,OAAO,GAAG,gBAAgB,CAAA;CAC9B;AAED,UAAU,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,SAAS,WAAW;IACxF;IACE,uBAAuB;IACvB,KAAK,EAAE,MAAM;IACb,qDAAqD;IACrD,WAAW,CAAC,EAAE,UAAU,EAAE;IAC1B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAClC,OAAO,GAAG,gBAAgB,CAAA;CAC9B;AAED,UAAU,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,EAAE,YAAY,SAAS,WAAW;IAC1F;IACE,uBAAuB;IACvB,KAAK,EAAE,MAAM;IACb,gFAAgF;IAChF,WAAW,CAAC,EAAE,UAAU,EAAE;IAC1B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAClC,gBAAgB,CAAA;CACpB;AAED,UAAU,qBAAqB,CAC7B,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,SAAS,WAAW,EACrC,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,SAAS,WAAW;IAErC;IACE;;;;OAIG;IACH,QAAQ,EAAE,YAAY,CAAC,WAAW,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,CAAC;IAC1F,uBAAuB;IACvB,KAAK,EAAE,WAAW;IAClB,qDAAqD;IACrD,WAAW,EAAE,UAAU,EAAE;IACzB,+DAA+D;IAC/D,WAAW,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACtC,YAAY,GAAG,qBAAqB,CAAA;CACxC;AAED,aAAK,cAAc,CAAC,MAAM,EAAE,YAAY,SAAS,WAAW,IAAI;IAC9D,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAC3B,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;AAEzB,MAAM,WAAW,SAAS,CACxB,MAAM,EACN,OAAO,EACP,gBAAgB,GAAG,OAAO,EAC1B,YAAY,SAAS,WAAW,GAAG,WAAW;IAE9C,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uBAAuB;IACvB,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAA;IACrE,uDAAuD;IACvD,iBAAiB,EAAE,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAA;IAClF,sEAAsE;IACtE,cAAc,CAAC,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IAC9C;;;OAGG;IACH,IAAI,EAAE,CACJ,UAAU,GAAG,MAAM,EACnB,WAAW,GAAG,OAAO,EACrB,oBAAoB,GAAG,gBAAgB,EACvC,gBAAgB,SAAS,YAAY,GAAG,YAAY;IAEpD;;;;OAIG;IACH,UAAU,EAAE,qBAAqB,CAC/B,MAAM,EACN,OAAO,EACP,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,oBAAoB,EACpB,gBAAgB,CACjB;IACD,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,gBAAgB,KACpC,SAAS,CAAC,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE,gBAAgB,CAAC,CAAA;IAC/E;;;;OAIG;IACH,CAAC,KAAK,EAAE,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAAA;CACjF;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,yRAoEzB,CAAA"}
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./makeFormatter"
@@ -1,218 +0,0 @@
1
- import { makeFormatter } from "./makeFormatter"
2
-
3
- const toUpperCase = (string: string) => string.toUpperCase()
4
-
5
- const upperCaseFormatter = makeFormatter<string, string>(toUpperCase)
6
-
7
- interface ValueRecord {
8
- value: string
9
- }
10
-
11
- const valueRecordUpperCaseFormatter = makeFormatter<ValueRecord, ValueRecord, string>(
12
- ({ value }, suggestions) => {
13
- if (suggestions.includes("primitive")) {
14
- return toUpperCase(value)
15
- }
16
-
17
- return { value: toUpperCase(value) }
18
- },
19
- )
20
-
21
- describe("makeFormatter", () => {
22
- const format = jest.fn()
23
-
24
- afterEach(() => {
25
- jest.clearAllMocks()
26
- })
27
-
28
- describe("displayName", () => {
29
- it("accepts a `displayName` option", () => {
30
- const displayName = "upperCaseFormatter"
31
- const formatter = makeFormatter<string, string>(toUpperCase, { displayName })
32
- expect(formatter.displayName).toBe(displayName)
33
- })
34
- })
35
-
36
- describe("format", () => {
37
- it("handles trivial formatting", () => {
38
- expect(upperCaseFormatter.format("foo")).toBe("FOO")
39
- })
40
-
41
- it("passes through non-empty suggestions", () => {
42
- const formatter = makeFormatter<string, string>(format)
43
- formatter.format("foo", ["abbreviated"])
44
- expect(format.mock.calls[0][1]).toEqual(["abbreviated"])
45
- })
46
-
47
- it("passes through empty suggestions", () => {
48
- const formatter = makeFormatter<string, string>(format)
49
- formatter.format("foo", [])
50
- expect(format.mock.calls[0][1]).toEqual([])
51
- })
52
-
53
- it("defaults suggestions to an empty array", () => {
54
- const formatter = makeFormatter<string, string>(format)
55
- formatter.format("foo")
56
- expect(format.mock.calls[0][1]).toEqual([])
57
- })
58
-
59
- it("passes through the `primitive` suggestion", () => {
60
- const formatter = makeFormatter<string, string>(format)
61
- formatter.format("foo", ["primitive"])
62
- expect(format.mock.calls[0][1]).toEqual(["primitive"])
63
- })
64
-
65
- it("passes the `primitive` suggestion alongside `comparable` when missing", () => {
66
- const formatter = makeFormatter<string, string>(format)
67
- formatter.format("foo", ["comparable"])
68
- expect(format.mock.calls[0][1]).toEqual(["primitive", "comparable"])
69
- })
70
-
71
- it("does not pass the `primitive` suggestion alongside other suggestions when missing", () => {
72
- const formatter = makeFormatter<string, string>(format)
73
- formatter.format("foo", ["abbreviated", "verbose"])
74
- expect(format.mock.calls[0][1]).toEqual(["abbreviated", "verbose"])
75
- })
76
-
77
- it("supports data context", () => {
78
- const formatter = makeFormatter<string, string>(format)
79
- formatter.format("foo", [], { foo: "bar" })
80
- expect(format.mock.calls[0][2]).toEqual({ foo: "bar" })
81
- })
82
-
83
- it("defaults data context to an empty object", () => {
84
- const formatter = makeFormatter<string, string>(format)
85
- formatter.format("foo")
86
- expect(format.mock.calls[0][2]).toEqual({})
87
- })
88
- })
89
-
90
- describe("formatAsPrimitive", () => {
91
- it("handles trivial formatting", () => {
92
- expect(upperCaseFormatter.formatAsPrimitive("foo")).toBe("FOO")
93
- })
94
-
95
- it("passes the `primitive` suggestion when no suggestions are passed", () => {
96
- const formatter = makeFormatter<string, string>(format)
97
- formatter.formatAsPrimitive("foo")
98
- expect(format.mock.calls[0][1]).toEqual(["primitive"])
99
- })
100
-
101
- it("passes the `primitive` suggestion when empty suggestions are passed", () => {
102
- const formatter = makeFormatter<string, string>(format)
103
- formatter.formatAsPrimitive("foo", [])
104
- expect(format.mock.calls[0][1]).toEqual(["primitive"])
105
- })
106
-
107
- it("passes the `primitive` suggestion when a different suggestion is passed", () => {
108
- const formatter = makeFormatter<string, string>(format)
109
- formatter.formatAsPrimitive("foo", ["abbreviated"])
110
- expect(format.mock.calls[0][1]).toEqual(["primitive", "abbreviated"])
111
- })
112
-
113
- it("passes through the `primitive` suggestion", () => {
114
- const formatter = makeFormatter<string, string>(format)
115
- formatter.formatAsPrimitive("foo", ["primitive"])
116
- expect(format.mock.calls[0][1]).toEqual(["primitive"])
117
- })
118
-
119
- it("passes the `primitive` suggestion alongside `comparable`", () => {
120
- const formatter = makeFormatter<string, string>(format)
121
- formatter.formatAsPrimitive("foo", ["comparable"])
122
- expect(format.mock.calls[0][1]).toEqual(["primitive", "comparable"])
123
- })
124
-
125
- it("supports data context", () => {
126
- const formatter = makeFormatter<string, string>(format)
127
- formatter.formatAsPrimitive("foo", [], { foo: "bar" })
128
- expect(format.mock.calls[0][2]).toEqual({ foo: "bar" })
129
- })
130
-
131
- it("defaults data context to an empty object", () => {
132
- const formatter = makeFormatter<string, string>(format)
133
- formatter.formatAsPrimitive("foo")
134
- expect(format.mock.calls[0][2]).toEqual({})
135
- })
136
- })
137
-
138
- describe("wrap", () => {
139
- it("handles trivial wrapping", () => {
140
- const formatter = makeFormatter<string, string>(format).wrap(() => "bar")
141
- expect(formatter.format("foo")).toBe("bar")
142
- })
143
-
144
- it("handles single-value mapping", () => {
145
- const formatter = makeFormatter<string, string>(format).wrap((delegate, value) =>
146
- value === "ping" ? "pong" : delegate(value),
147
- )
148
-
149
- expect(formatter.format("ping")).toBe("pong")
150
- })
151
-
152
- it("handles delegation to the original formatter", () => {
153
- const formatter = upperCaseFormatter.wrap((delegate, value) =>
154
- value === "ping" ? "pong" : delegate(value),
155
- )
156
-
157
- expect(formatter.format("foo")).toBe("FOO")
158
- })
159
-
160
- it("sets the `innerFormatter` property", () => {
161
- const wrappedFormatter = upperCaseFormatter.wrap((delegate, value) => delegate(value))
162
- expect(wrappedFormatter.innerFormatter).toBe(upperCaseFormatter)
163
- })
164
-
165
- it("passes the `primitive` suggestion to `delegate` when using `formatAsPrimitive`", () => {
166
- const formatter = valueRecordUpperCaseFormatter.wrap((delegate, value) => delegate(value))
167
- expect(formatter.formatAsPrimitive({ value: "foo" })).toBe("FOO")
168
- })
169
-
170
- it("passes suggestions through to `delegate` when not overriden", () => {
171
- const formatter = valueRecordUpperCaseFormatter.wrap((delegate, value) => delegate(value))
172
- expect(formatter.format({ value: "foo" }, ["primitive"])).toBe("FOO")
173
- })
174
-
175
- it("supports overriding of suggestions for `delegate`", () => {
176
- const formatter = valueRecordUpperCaseFormatter.wrap((delegate, value) => delegate(value, []))
177
- expect(formatter.format({ value: "foo" }, ["primitive"])).toEqual({ value: "FOO" })
178
- })
179
-
180
- it("supports modifying the input structure", () => {
181
- const formatter = upperCaseFormatter.wrap<ValueRecord>((delegate, { value }) =>
182
- delegate(value),
183
- )
184
-
185
- expect(formatter.format({ value: "foo" })).toBe("FOO")
186
- })
187
-
188
- it("supports modifying the output structure", () => {
189
- const formatter = upperCaseFormatter.wrap<ValueRecord, ValueRecord>(
190
- (delegate, { value }) => ({ value: delegate(value) }),
191
- )
192
-
193
- expect(formatter.format({ value: "foo" })).toEqual({ value: "FOO" })
194
- })
195
-
196
- it("supports modifying the data context structure", () => {
197
- interface ListContext {
198
- row: string
199
- }
200
-
201
- const listFormatter = upperCaseFormatter.wrap<string, string, string, ListContext>(
202
- (delegate, value, suggestions, { row }) => `${row}: ${delegate(value)}`,
203
- )
204
-
205
- interface GridContext extends ListContext {
206
- column: string
207
- }
208
-
209
- expect(listFormatter.format("foo", [], { row: "A" })).toBe("A: FOO")
210
-
211
- const gridFormatter = listFormatter.wrap<string, string, string, GridContext>(
212
- (delegate, value, suggestions, { column }) => delegate(value).replace(":", `${column}:`),
213
- )
214
-
215
- expect(gridFormatter.format("foo", [], { row: "B", column: "1" })).toBe("B1: FOO")
216
- })
217
- })
218
- })
@@ -1,219 +0,0 @@
1
- type PrimitiveSuggestion = "primitive" | "comparable"
2
-
3
- type Suggestion =
4
- | PrimitiveSuggestion
5
- | "abbreviated"
6
- | "as-icon"
7
- /**
8
- * @deprecated Since v0.6.3. Use `"as-icon"` or `"with-icon"` instead.
9
- */
10
- | "icon"
11
- | "verbose"
12
- | "with-icon"
13
-
14
- const ensureFormatSuggestionIntegrity = (suggestions: Suggestion[]): Suggestion[] =>
15
- suggestions.includes("primitive") || !suggestions.includes("comparable")
16
- ? suggestions
17
- : ["primitive", ...suggestions]
18
-
19
- const ensureFormatAsPrimitiveSuggestionIntegrity = (suggestions: Suggestion[]): Suggestion[] =>
20
- suggestions.includes("primitive") ? suggestions : ["primitive", ...suggestions]
21
-
22
- type DataContext = Record<string, any>
23
-
24
- interface FormatterOptions {
25
- /** Formatter name, useful for debugging or advanced pattern matching. */
26
- displayName?: string
27
- }
28
-
29
- interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
30
- (
31
- /** Value to format. */
32
- value: TInput,
33
- /** Suggestions passed by the consumer of a formatter. */
34
- suggestions: Suggestion[],
35
- /** Additional data context to be used by the formatter. */
36
- dataContext: Partial<TDataContext>,
37
- ): TOutput | TPrimitiveOutput
38
- }
39
-
40
- interface FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
41
- (
42
- /** Value to format. */
43
- value: TInput,
44
- /** Suggestions the formatter should take note of. */
45
- suggestions?: Suggestion[],
46
- /** Additional data context the formatter might find useful. */
47
- dataContext?: Partial<TDataContext>,
48
- ): TOutput | TPrimitiveOutput
49
- }
50
-
51
- interface FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext extends DataContext> {
52
- (
53
- /** Value to format. */
54
- value: TInput,
55
- /** Suggestions the formatter should take note of in addition to `primitive`. */
56
- suggestions?: Suggestion[],
57
- /** Additional data context the formatter might find useful. */
58
- dataContext?: Partial<TDataContext>,
59
- ): TPrimitiveOutput
60
- }
61
-
62
- interface FormatChainDefinition<
63
- TInnerInput,
64
- TInnerOutput,
65
- TInnerPrimitiveInput,
66
- TInnerDataContext extends DataContext,
67
- TOuterInput,
68
- TOuterOutput,
69
- TOuterPrimitiveOutput,
70
- TOuterDataContext extends DataContext
71
- > {
72
- (
73
- /**
74
- * The `formatter.format` method which can be used to delegate the formatting
75
- * to the wrapped formatter. Delegation is simplified so if no suggestions or contextual
76
- * props are passed, the original ones are used instead.
77
- */
78
- delegate: FormatMethod<TInnerInput, TInnerOutput, TInnerPrimitiveInput, TInnerDataContext>,
79
- /** Value to format. */
80
- value: TOuterInput,
81
- /** Suggestions the formatter should take note of. */
82
- suggestions: Suggestion[],
83
- /** Additional data context the formatter might find useful. */
84
- dataContext: Partial<TOuterDataContext>,
85
- ): TOuterOutput | TOuterPrimitiveOutput
86
- }
87
-
88
- type FormatterProps<TInput, TDataContext extends DataContext> = {
89
- /** Value to format. */
90
- children: TInput
91
- /** Suggestions the formatter should take note of. */
92
- suggestions?: Suggestion[]
93
- } & Partial<TDataContext>
94
-
95
- export interface Formatter<
96
- TInput,
97
- TOutput,
98
- TPrimitiveOutput = TOutput,
99
- TDataContext extends DataContext = DataContext
100
- > {
101
- /** Formatter name, useful for debugging or advanced pattern matching. */
102
- displayName?: string
103
- /** Formats a value. */
104
- format: FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext>
105
- /** Formats a value with the `primitive` suggestion. */
106
- formatAsPrimitive: FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext>
107
- /** The callee of the `wrap` method used to produce this formatter. */
108
- innerFormatter?: Formatter<any, any, any, any>
109
- /**
110
- * Creates a new formatter from an existing one. Allows overriding of formatter behaviour
111
- * for certain values.
112
- */
113
- wrap: <
114
- TNextInput = TInput,
115
- TNextOutput = TOutput,
116
- TNextPrimitiveOutput = TPrimitiveOutput,
117
- TNextDataContext extends TDataContext = TDataContext
118
- >(
119
- /**
120
- * Function used to format the value. Has the same signature as the one passed
121
- * to `makeFormatter`, except a `delegate` function is passed in the first position.
122
- * This function can be used to delegate formatting to the original (inner) formatter.
123
- */
124
- nextFormat: FormatChainDefinition<
125
- TInput,
126
- TOutput,
127
- TPrimitiveOutput,
128
- TDataContext,
129
- TNextInput,
130
- TNextOutput,
131
- TNextPrimitiveOutput,
132
- TNextDataContext
133
- >,
134
- /** New formatter options, replacing the original ones. */
135
- nextFormatterOptions?: FormatterOptions,
136
- ) => Formatter<TNextInput, TNextOutput, TNextPrimitiveOutput, TNextDataContext>
137
- /**
138
- * Backwards-compatible way to use the formatter as a React component.
139
- *
140
- * @deprecated Since v0.6.0. Prefer using the `format` method instead.
141
- */
142
- (props: FormatterProps<TInput, TDataContext>): TOutput | TPrimitiveOutput | null
143
- }
144
-
145
- /**
146
- * Creates a new formatter.
147
- *
148
- * @param format Function used to format the value.
149
- * @param formatterOptions Additional options for the formatter.
150
- */
151
- export const makeFormatter = <
152
- TInput,
153
- TOutput,
154
- TPrimitiveOutput = TOutput,
155
- TDataContext extends DataContext = DataContext
156
- >(
157
- format: FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext>,
158
- formatterOptions?: FormatterOptions,
159
- ): Formatter<TInput, TOutput, TPrimitiveOutput, TDataContext> => {
160
- const formatter: Formatter<TInput, TOutput, TPrimitiveOutput, TDataContext> = props =>
161
- format(props.children, props.suggestions ?? [], props) ?? null
162
-
163
- formatter.displayName = formatterOptions?.displayName
164
-
165
- formatter.format = (value, suggestions = [], dataContext = {}) =>
166
- format(value, ensureFormatSuggestionIntegrity(suggestions), dataContext)
167
-
168
- formatter.formatAsPrimitive = (value, suggestions = [], dataContext = {}) =>
169
- format(
170
- value,
171
- ensureFormatAsPrimitiveSuggestionIntegrity(suggestions),
172
- dataContext,
173
- ) as TPrimitiveOutput
174
-
175
- formatter.wrap = <
176
- TNextInput,
177
- TNextOutput,
178
- TNextPrimitiveOutput,
179
- TNextDataContext extends TDataContext
180
- >(
181
- nextFormat: FormatChainDefinition<
182
- TInput,
183
- TOutput,
184
- TPrimitiveOutput,
185
- TDataContext,
186
- TNextInput,
187
- TNextOutput,
188
- TNextPrimitiveOutput,
189
- TNextDataContext
190
- >,
191
- nextFormatterOptions?: FormatterOptions,
192
- ) => {
193
- const nextFormatter = makeFormatter<
194
- TNextInput,
195
- TNextOutput,
196
- TNextPrimitiveOutput,
197
- TNextDataContext
198
- >((value, suggestions, dataContext) => {
199
- const delegate: FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext> = (
200
- delegatedValue,
201
- delegatedSuggestions,
202
- delegatedDataContext,
203
- ) =>
204
- formatter.format(
205
- delegatedValue,
206
- delegatedSuggestions ?? suggestions,
207
- delegatedDataContext ?? dataContext,
208
- )
209
-
210
- return nextFormat(delegate, value, suggestions, dataContext)
211
- }, nextFormatterOptions ?? formatterOptions)
212
-
213
- nextFormatter.innerFormatter = formatter
214
-
215
- return nextFormatter
216
- }
217
-
218
- return formatter
219
- }
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.build.json",
3
- "compilerOptions": {
4
- "declarationDir": "./dist/types"
5
- }
6
- }