afformative 0.6.2 → 0.7.0-beta.1

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.formatAsString(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 comes 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, usageSuggestions) => {
90
- if (usageSuggestions.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,72 +140,49 @@ 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.
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.
125
144
 
126
- ```js
127
- const rowTrendFormatter = makeFormatter((value, suggestions, { row, cellIndex }) => {
128
- if (cellIndex === 0) {
129
- return <span>{value}</span>
130
- }
145
+ ```tsx
146
+ import { createFormatter } from "afformative"
147
+ import { ReactNode } from "react"
131
148
 
132
- const previousValue = row[cellIndex - 1]
149
+ import { TableFormatterUsageContext } from "./Table"
133
150
 
134
- return <span style={{ color: value >= previousValue ? "green" : "red" }}>{value}</span>
151
+ const rowTrendFormatter = createFormatter<number, ReactNode, TableFormatterUsageContext>({
152
+ format: (value, { row, cellIndex }) => {
153
+ if (cellIndex === 0) {
154
+ return <span>{value}</span>
155
+ }
156
+
157
+ const previousValue = row[cellIndex - 1]
158
+
159
+ return <span style={{ color: value >= previousValue ? "green" : "red" }}>{value}</span>
160
+ },
135
161
  })
136
162
  ```
137
163
 
138
- Of course, this formatter only makes sense for our table component, nowhere else.
164
+ This formatter only makes sense in the context of our table component.
139
165
 
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.
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.
141
167
 
142
- ### Accessing React Context Reliably
168
+ ## Accessing React Context
143
169
 
144
- Use hooks.
170
+ Create formatters inside custom hooks to access React context values such as translations or application state.
145
171
 
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))
172
+ ```tsx
173
+ const useEnumFormatter = (enumType: string): Formatter<string, ReactNode> => {
174
+ const enumTranslationKeys = useSelector(selectEnumTranslationKeys(enumType))
151
175
  const intl = useIntl()
152
176
 
153
177
  return useMemo(
154
178
  () =>
155
- makeFormatter(value =>
156
- intl.formatMessage({
157
- defaultMessage: value,
158
- id: enumTranslationKeys[value],
159
- }),
179
+ createFormatter<string, ReactNode>(
180
+ value => <FormattedMessage defaultMessage={value} id={enumTranslationKeys[value]} />,
181
+ value => intl.formatMessage({ defaultMessage: value, id: enumTranslationKeys[value] }),
160
182
  ),
161
183
  [intl, enumTranslationKeys],
162
184
  )
163
185
  }
164
-
165
- const someEnumFormatter = useEnumFormatter("someEnum")
166
- ```
167
-
168
- ### Consuming Formatters
169
-
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.
171
-
172
- All formatters also expose the `wrap` method. You can use this method to alter the behaviour of the formatter for some specific values.
173
-
174
- ```js
175
- const useSnowflakeAwareFormatter = formatter => {
176
- const intl = useIntl()
177
-
178
- return useMemo(
179
- () =>
180
- formatter.wrap((delegate, value) => {
181
- if (isSnowflake(value)) {
182
- return intl.formatMessage(messages.snowflake)
183
- }
184
-
185
- return delegate(value)
186
- }),
187
- [formatter, intl],
188
- )
189
- }
190
186
  ```
191
187
 
192
188
  ## Changelog
@@ -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.2",
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.1",
9
4
  "description": "A standardized way to format values in your React components.",
10
5
  "keywords": [
11
6
  "react",
@@ -24,15 +19,43 @@
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
+ ".": "./dist/index.mjs",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "dist"
29
34
  ],
30
- "dependencies": {
31
- "@babel/runtime": "^7.12.1"
35
+ "scripts": {
36
+ "build": "tsdown",
37
+ "prepublishOnly": "pnpm run build",
38
+ "release": "bumpp",
39
+ "test": "pnpm test:prettier && pnpm run test:typescript && pnpm run test:eslint && pnpm run test:vitest",
40
+ "test:eslint": "eslint src",
41
+ "test:prettier": "prettier --check src",
42
+ "test:typescript": "tsc --noEmit",
43
+ "test:vitest": "vitest run"
44
+ },
45
+ "devDependencies": {
46
+ "@eslint/js": "^10.0.1",
47
+ "@types/node": "^25.6.2",
48
+ "@typescript/native-preview": "^7.0.0-dev.20260527.2",
49
+ "bumpp": "^11.1.0",
50
+ "eslint": "^10.4.1",
51
+ "prettier": "^3.8.3",
52
+ "tsdown": "^0.22.1",
53
+ "typescript": "^6.0.3",
54
+ "typescript-eslint": "^8.60.0",
55
+ "vitest": "^4.1.7"
32
56
  },
57
+ "packageManager": "pnpm@11.5.0+sha512.dbfcc4f81cf48597afd4bc391ffdf12c11f1a9fb83a395bfa6b0a2d9cc2fd8ffebafdb1ccbd529632153f793904c2615b7f09fe1a345473fd1c35845172a8eb1",
33
58
  "publishConfig": {
34
59
  "access": "public"
35
- },
36
- "sideEffects": false,
37
- "gitHead": "3e3b893eee2b69d29019b3683b9d1d719192a2b5"
60
+ }
38
61
  }
@@ -1,73 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray');
6
- var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
7
-
8
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
-
10
- var _toConsumableArray__default = /*#__PURE__*/_interopDefaultLegacy(_toConsumableArray);
11
- var _objectWithoutProperties__default = /*#__PURE__*/_interopDefaultLegacy(_objectWithoutProperties);
12
-
13
- // TODO: Update `FormatDefinition` type based on this snippet. Sadly, TypeScript cannot correctly
14
- // verify the return type of the format definition based on the suggestions, printing errors such
15
- // as `Type 'string' is not assignable to type '"primitive" extends TSuggestion ? string : string'`
16
- // which is, of course, nonsense. Same applies to `FormatChainDefinition`.
17
- //
18
- // interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
19
- // <TSuggestion extends Suggestion>(
20
- // value: TInput,
21
- // usageSuggestions: TSuggestion[],
22
- // dataContext: Partial<TDataContext>,
23
- // ): PrimitiveSuggestion extends TSuggestion ? TPrimitiveOutput : TOutput
24
- // }
25
-
26
- /**
27
- * Creates a new formatter.
28
- *
29
- * @param format Function used to format the value.
30
- * @param formatterOptions Additional options for the formatter.
31
- */
32
- var makeFormatter = function makeFormatter(format, formatterOptions) {
33
- var formatter = function formatter(_ref) {
34
- var _format;
35
-
36
- var children = _ref.children,
37
- _ref$suggestions = _ref.suggestions,
38
- suggestions = _ref$suggestions === void 0 ? [] : _ref$suggestions,
39
- dataContext = _objectWithoutProperties__default['default'](_ref, ["children", "suggestions"]);
40
-
41
- return (_format = format(children, suggestions, dataContext)) !== null && _format !== void 0 ? _format : null;
42
- };
43
-
44
- formatter.displayName = formatterOptions === null || formatterOptions === void 0 ? void 0 : formatterOptions.displayName;
45
-
46
- formatter.format = function (value) {
47
- var usageSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
48
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
49
- return format(value, usageSuggestions, dataContext);
50
- };
51
-
52
- formatter.formatAsPrimitive = function (value) {
53
- var usageSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
54
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
55
- return format(value, ["primitive"].concat(_toConsumableArray__default['default'](usageSuggestions)), dataContext);
56
- };
57
-
58
- formatter.wrap = function (nextFormat, nextFormatterOptions) {
59
- var nextFormatter = makeFormatter(function (value, usageSuggestions, dataContext) {
60
- var delegate = function delegate(delegatedValue, delegatedUsageSuggestions, delegatedDataContext) {
61
- return formatter.format(delegatedValue, delegatedUsageSuggestions !== null && delegatedUsageSuggestions !== void 0 ? delegatedUsageSuggestions : usageSuggestions, delegatedDataContext !== null && delegatedDataContext !== void 0 ? delegatedDataContext : dataContext);
62
- };
63
-
64
- return nextFormat(delegate, value, usageSuggestions, dataContext);
65
- }, nextFormatterOptions !== null && nextFormatterOptions !== void 0 ? nextFormatterOptions : formatterOptions);
66
- nextFormatter.innerFormatter = formatter;
67
- return nextFormatter;
68
- };
69
-
70
- return formatter;
71
- };
72
-
73
- exports.makeFormatter = makeFormatter;
@@ -1,64 +0,0 @@
1
- import _toConsumableArray from '@babel/runtime/helpers/esm/toConsumableArray';
2
- import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties';
3
-
4
- // TODO: Update `FormatDefinition` type based on this snippet. Sadly, TypeScript cannot correctly
5
- // verify the return type of the format definition based on the suggestions, printing errors such
6
- // as `Type 'string' is not assignable to type '"primitive" extends TSuggestion ? string : string'`
7
- // which is, of course, nonsense. Same applies to `FormatChainDefinition`.
8
- //
9
- // interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
10
- // <TSuggestion extends Suggestion>(
11
- // value: TInput,
12
- // usageSuggestions: TSuggestion[],
13
- // dataContext: Partial<TDataContext>,
14
- // ): PrimitiveSuggestion extends TSuggestion ? TPrimitiveOutput : TOutput
15
- // }
16
-
17
- /**
18
- * Creates a new formatter.
19
- *
20
- * @param format Function used to format the value.
21
- * @param formatterOptions Additional options for the formatter.
22
- */
23
- var makeFormatter = function makeFormatter(format, formatterOptions) {
24
- var formatter = function formatter(_ref) {
25
- var _format;
26
-
27
- var children = _ref.children,
28
- _ref$suggestions = _ref.suggestions,
29
- suggestions = _ref$suggestions === void 0 ? [] : _ref$suggestions,
30
- dataContext = _objectWithoutProperties(_ref, ["children", "suggestions"]);
31
-
32
- return (_format = format(children, suggestions, dataContext)) !== null && _format !== void 0 ? _format : null;
33
- };
34
-
35
- formatter.displayName = formatterOptions === null || formatterOptions === void 0 ? void 0 : formatterOptions.displayName;
36
-
37
- formatter.format = function (value) {
38
- var usageSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
39
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
40
- return format(value, usageSuggestions, dataContext);
41
- };
42
-
43
- formatter.formatAsPrimitive = function (value) {
44
- var usageSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
45
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
46
- return format(value, ["primitive"].concat(_toConsumableArray(usageSuggestions)), dataContext);
47
- };
48
-
49
- formatter.wrap = function (nextFormat, nextFormatterOptions) {
50
- var nextFormatter = makeFormatter(function (value, usageSuggestions, dataContext) {
51
- var delegate = function delegate(delegatedValue, delegatedUsageSuggestions, delegatedDataContext) {
52
- return formatter.format(delegatedValue, delegatedUsageSuggestions !== null && delegatedUsageSuggestions !== void 0 ? delegatedUsageSuggestions : usageSuggestions, delegatedDataContext !== null && delegatedDataContext !== void 0 ? delegatedDataContext : dataContext);
53
- };
54
-
55
- return nextFormat(delegate, value, usageSuggestions, dataContext);
56
- }, nextFormatterOptions !== null && nextFormatterOptions !== void 0 ? nextFormatterOptions : formatterOptions);
57
- nextFormatter.innerFormatter = formatter;
58
- return nextFormatter;
59
- };
60
-
61
- return formatter;
62
- };
63
-
64
- export { makeFormatter };
@@ -1,140 +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
- function _objectWithoutPropertiesLoose(source, excluded) {
43
- if (source == null) return {};
44
- var target = {};
45
- var sourceKeys = Object.keys(source);
46
- var key, i;
47
-
48
- for (i = 0; i < sourceKeys.length; i++) {
49
- key = sourceKeys[i];
50
- if (excluded.indexOf(key) >= 0) continue;
51
- target[key] = source[key];
52
- }
53
-
54
- return target;
55
- }
56
-
57
- function _objectWithoutProperties(source, excluded) {
58
- if (source == null) return {};
59
- var target = _objectWithoutPropertiesLoose(source, excluded);
60
- var key, i;
61
-
62
- if (Object.getOwnPropertySymbols) {
63
- var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
64
-
65
- for (i = 0; i < sourceSymbolKeys.length; i++) {
66
- key = sourceSymbolKeys[i];
67
- if (excluded.indexOf(key) >= 0) continue;
68
- if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
69
- target[key] = source[key];
70
- }
71
- }
72
-
73
- return target;
74
- }
75
-
76
- // TODO: Update `FormatDefinition` type based on this snippet. Sadly, TypeScript cannot correctly
77
- // verify the return type of the format definition based on the suggestions, printing errors such
78
- // as `Type 'string' is not assignable to type '"primitive" extends TSuggestion ? string : string'`
79
- // which is, of course, nonsense. Same applies to `FormatChainDefinition`.
80
- //
81
- // interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
82
- // <TSuggestion extends Suggestion>(
83
- // value: TInput,
84
- // usageSuggestions: TSuggestion[],
85
- // dataContext: Partial<TDataContext>,
86
- // ): PrimitiveSuggestion extends TSuggestion ? TPrimitiveOutput : TOutput
87
- // }
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
- var makeFormatter = function makeFormatter(format, formatterOptions) {
96
- var formatter = function formatter(_ref) {
97
- var _format;
98
-
99
- var children = _ref.children,
100
- _ref$suggestions = _ref.suggestions,
101
- suggestions = _ref$suggestions === void 0 ? [] : _ref$suggestions,
102
- dataContext = _objectWithoutProperties(_ref, ["children", "suggestions"]);
103
-
104
- return (_format = format(children, suggestions, dataContext)) !== null && _format !== void 0 ? _format : null;
105
- };
106
-
107
- formatter.displayName = formatterOptions === null || formatterOptions === void 0 ? void 0 : formatterOptions.displayName;
108
-
109
- formatter.format = function (value) {
110
- var usageSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
111
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
112
- return format(value, usageSuggestions, dataContext);
113
- };
114
-
115
- formatter.formatAsPrimitive = function (value) {
116
- var usageSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
117
- var dataContext = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
118
- return format(value, ["primitive"].concat(_toConsumableArray(usageSuggestions)), dataContext);
119
- };
120
-
121
- formatter.wrap = function (nextFormat, nextFormatterOptions) {
122
- var nextFormatter = makeFormatter(function (value, usageSuggestions, dataContext) {
123
- var delegate = function delegate(delegatedValue, delegatedUsageSuggestions, delegatedDataContext) {
124
- return formatter.format(delegatedValue, delegatedUsageSuggestions !== null && delegatedUsageSuggestions !== void 0 ? delegatedUsageSuggestions : usageSuggestions, delegatedDataContext !== null && delegatedDataContext !== void 0 ? delegatedDataContext : dataContext);
125
- };
126
-
127
- return nextFormat(delegate, value, usageSuggestions, dataContext);
128
- }, nextFormatterOptions !== null && nextFormatterOptions !== void 0 ? nextFormatterOptions : formatterOptions);
129
- nextFormatter.innerFormatter = formatter;
130
- return nextFormatter;
131
- };
132
-
133
- return formatter;
134
- };
135
-
136
- exports.makeFormatter = makeFormatter;
137
-
138
- Object.defineProperty(exports, '__esModule', { value: true });
139
-
140
- })));
@@ -1 +0,0 @@
1
- !function(t,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((t="undefined"!=typeof globalThis?globalThis:t||self).Afformative={})}(this,(function(t){"use strict";function r(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=Array(r);r>e;e++)n[e]=t[e];return n}function e(t){return function(t){if(Array.isArray(t))return r(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return r(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(t,e):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.")}()}function n(t,r){if(null==t)return{};var e,n,o=function(t,r){if(null==t)return{};var e,n,o={},i=Object.keys(t);for(n=0;i.length>n;n++)0>r.indexOf(e=i[n])&&(o[e]=t[e]);return o}(t,r);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;i.length>n;n++)0>r.indexOf(e=i[n])&&Object.prototype.propertyIsEnumerable.call(t,e)&&(o[e]=t[e])}return o}t.makeFormatter=function t(r,o){var i=function(t){var e,o=t.children,i=t.suggestions,u=void 0===i?[]:i,l=n(t,["children","suggestions"]);return null!==(e=r(o,u,l))&&void 0!==e?e:null};return i.displayName=null==o?void 0:o.displayName,i.format=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r(t,e,n)},i.formatAsPrimitive=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r(t,["primitive"].concat(e(n)),o)},i.wrap=function(r,e){var n=t((function(t,e,n){return r((function(t,r,o){return i.format(t,null!=r?r:e,null!=o?o:n)}),t,e,n)}),null!=e?e:o);return n.innerFormatter=i,n},i},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,94 +0,0 @@
1
- declare type PrimitiveSuggestion = "primitive";
2
- declare type SemanticSuggestion = "abbreviated" | "icon" | "verbose";
3
- declare type Suggestion = PrimitiveSuggestion | SemanticSuggestion;
4
- interface FormatterOptions {
5
- /** Formatter name, useful for debugging or advanced pattern matching. */
6
- displayName?: string;
7
- }
8
- declare type DataContext = Record<string, any>;
9
- interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
10
- (
11
- /** Value to format. */
12
- value: TInput,
13
- /** Suggestions passed by the consumer of a formatter. */
14
- usageSuggestions: Suggestion[],
15
- /** Additional data context to be used by the formatter. */
16
- dataContext: Partial<TDataContext>): TOutput | TPrimitiveOutput;
17
- }
18
- interface FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
19
- <TSuggestion extends Suggestion>(
20
- /** Value to format. */
21
- value: TInput,
22
- /** Suggestions the formatter should take note of. */
23
- usageSuggestions?: TSuggestion[],
24
- /** Additional data context the formatter might find useful. */
25
- dataContext?: Partial<TDataContext>): PrimitiveSuggestion extends TSuggestion ? TPrimitiveOutput : TOutput;
26
- }
27
- interface FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext extends DataContext> {
28
- (
29
- /** Value to format. */
30
- value: TInput,
31
- /** Suggestions the formatter should take note of in addition to `primitive`. */
32
- usageSuggestions?: Suggestion[],
33
- /** Additional data context the formatter might find useful. */
34
- dataContext?: Partial<TDataContext>): TPrimitiveOutput;
35
- }
36
- interface FormatChainDefinition<TInnerInput, TInnerOutput, TInnerPrimitiveInput, TInnerDataContext extends DataContext, TOuterInput, TOuterOutput, TOuterPrimitiveOutput, TOuterDataContext extends DataContext> {
37
- (
38
- /**
39
- * The `formatter.format` method which can be used to delegate the formatting
40
- * to the wrapped formatter. Delegation is simplified so if no suggestions or contextual
41
- * props are passed, the original ones are used instead.
42
- */
43
- delegate: FormatMethod<TInnerInput, TInnerOutput, TInnerPrimitiveInput, TInnerDataContext>,
44
- /** Value to format. */
45
- value: TOuterInput,
46
- /** Suggestions the formatter should take note of. */
47
- usageSuggestions: Suggestion[],
48
- /** Additional data context the formatter might find useful. */
49
- dataContext: Partial<TOuterDataContext>): TOuterOutput | TOuterPrimitiveOutput;
50
- }
51
- declare type FormatterProps<TInput, TDataContext extends DataContext> = {
52
- /** Value to format. */
53
- children: TInput;
54
- /** Suggestions the formatter should take note of. */
55
- suggestions?: Suggestion[];
56
- } & Partial<TDataContext>;
57
- export interface Formatter<TInput, TOutput, TPrimitiveOutput = TOutput, TDataContext extends DataContext = DataContext> {
58
- /** Formatter name, useful for debugging or advanced pattern matching. */
59
- displayName?: string;
60
- /** Formats a value. */
61
- format: FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext>;
62
- /** Formats a value with the `primitive` suggestion. */
63
- formatAsPrimitive: FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext>;
64
- /** The callee of the `.wrap` method used to produce this formatter. */
65
- innerFormatter?: Formatter<any, any, any, any>;
66
- /**
67
- * Creates a new formatter from an existing one. Allows overriding of formatter behaviour
68
- * for certain values.
69
- */
70
- wrap: <TNextInput = TInput, TNextOutput = TOutput, TNextPrimitiveOutput = TPrimitiveOutput, TNextDataContext extends DataContext = TDataContext>(
71
- /**
72
- * Function used to format the value. Has the same signature as the one passed
73
- * to `makeFormatter`, except a `delegate` function is passed in the first position.
74
- * This function can be used to delegate formatting to the original (inner) formatter.
75
- */
76
- nextFormat: FormatChainDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext, TNextInput, TNextOutput, TNextPrimitiveOutput, TNextDataContext>,
77
- /** New formatter options, replacing the original ones. */
78
- nextFormatterOptions?: FormatterOptions) => Formatter<TNextInput, TNextOutput, TNextPrimitiveOutput, TNextDataContext>;
79
- /**
80
- * Backwards-compatible way to use the formatter as a React component.
81
- *
82
- * @deprecated Since v0.6.0. Prefer using the `Formatter.format` method instead.
83
- */
84
- (props: FormatterProps<TInput, TDataContext>): TOutput | TPrimitiveOutput | null;
85
- }
86
- /**
87
- * Creates a new formatter.
88
- *
89
- * @param format Function used to format the value.
90
- * @param formatterOptions Additional options for the formatter.
91
- */
92
- 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>;
93
- export {};
94
- //# sourceMappingURL=makeFormatter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"makeFormatter.d.ts","sourceRoot":"","sources":["../../src/makeFormatter.ts"],"names":[],"mappings":"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"}
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./makeFormatter"
@@ -1,159 +0,0 @@
1
- import { makeFormatter } from "./makeFormatter"
2
-
3
- const toUpperCase = (string: string) => string.toUpperCase()
4
-
5
- describe("makeFormatter", () => {
6
- it("handles trivial formatting", () => {
7
- const formatter = makeFormatter<string, string>(toUpperCase)
8
- expect(formatter.format("foo")).toBe("FOO")
9
- })
10
-
11
- it("accepts a `name` option", () => {
12
- const formatter = makeFormatter<string, string>(toUpperCase, { displayName: "UpperFormatter" })
13
- expect(formatter.displayName).toBe("UpperFormatter")
14
- })
15
-
16
- it("passes `suggestions` to `format`", () => {
17
- const formatter = makeFormatter<string, string>((value, suggestions) => {
18
- if (suggestions.includes("abbreviated")) {
19
- return value[0]
20
- }
21
-
22
- return value
23
- })
24
-
25
- expect(formatter.format("foo", ["abbreviated"])).toBe("f")
26
- expect(formatter.format("foo", [])).toBe("foo")
27
- expect(formatter.format("foo")).toBe("foo")
28
- })
29
-
30
- it("handles formatting with the `primitive` suggestion", () => {
31
- type Structure = { value: string }
32
-
33
- const formatter = makeFormatter<Structure, Structure, string>((value, suggestions) => {
34
- if (suggestions.includes("primitive")) {
35
- return value.value
36
- }
37
-
38
- return value
39
- })
40
-
41
- expect(formatter.format({ value: "foo" }, ["primitive"])).toBe("foo")
42
- expect(formatter.formatAsPrimitive({ value: "foo" })).toBe("foo")
43
- expect(formatter.formatAsPrimitive({ value: "foo" }, ["primitive"])).toBe("foo")
44
- })
45
-
46
- it("supports simple behavior wrapping", () => {
47
- const formatter = makeFormatter<string, string>(toUpperCase)
48
-
49
- const wrappedFormatter = formatter.wrap((delegate, value) =>
50
- value === "foo" ? "override" : delegate(value),
51
- )
52
-
53
- expect(wrappedFormatter.format("foo")).toBe("override")
54
- expect(wrappedFormatter.format("bar")).toBe("BAR")
55
- })
56
-
57
- it("supports simple behavior wrapping with suggestions", () => {
58
- const formatter = makeFormatter<string, string>(toUpperCase)
59
-
60
- const wrappedFormatter = formatter.wrap((delegate, value, suggestions) =>
61
- value === "foo" && suggestions.includes("abbreviated") ? "f" : delegate(value),
62
- )
63
-
64
- expect(wrappedFormatter.format("foo")).toBe("FOO")
65
- expect(wrappedFormatter.format("foo", ["abbreviated"])).toBe("f")
66
- expect(wrappedFormatter.format("bar")).toBe("BAR")
67
- })
68
-
69
- it("supports simple behavior wrapping with the `primitive` suggestion", () => {
70
- type Structure = { value: string }
71
-
72
- const formatter = makeFormatter<string, string>(toUpperCase)
73
-
74
- const wrappedFormatter = formatter.wrap<Structure, Structure, string>(
75
- (delegate, value, suggestions) => (suggestions.includes("primitive") ? value.value : value),
76
- )
77
-
78
- expect(wrappedFormatter.format({ value: "foo" }, ["primitive"])).toBe("foo")
79
- expect(wrappedFormatter.formatAsPrimitive({ value: "foo" })).toBe("foo")
80
- expect(wrappedFormatter.formatAsPrimitive({ value: "foo" }, ["primitive"])).toBe("foo")
81
- })
82
-
83
- it("supports simple behavior wrapping with data context", () => {
84
- const formatter = makeFormatter<string, string>(toUpperCase)
85
-
86
- interface WrappedDataContext {
87
- forcedValue?: string
88
- }
89
-
90
- const wrappedFormatter = formatter.wrap(
91
- (delegate, value, suggestions, { forcedValue }: WrappedDataContext) =>
92
- forcedValue ?? delegate(value),
93
- )
94
-
95
- expect(wrappedFormatter.format("foo")).toBe("FOO")
96
- expect(wrappedFormatter.format("foo", undefined, { forcedValue: "override" })).toBe("override")
97
- })
98
-
99
- it("passes the original suggestions when they are not passed manually to `format` when wrapping", () => {
100
- const formatter = makeFormatter<string, string>((value, suggestions) => {
101
- if (suggestions.includes("abbreviated")) {
102
- return value[0]
103
- }
104
-
105
- return value
106
- })
107
-
108
- const wrappedFormatter = formatter.wrap((delegate, value) => delegate(value))
109
- expect(wrappedFormatter.format("foo", ["abbreviated"])).toBe("f")
110
- })
111
-
112
- it("sets the `innerFormatter` static property when wrapping", () => {
113
- const formatter = makeFormatter<string, string>(toUpperCase)
114
- const wrappedFormatter = formatter.wrap((delegate, value) => delegate(value))
115
- expect(wrappedFormatter.innerFormatter).toBe(formatter)
116
- })
117
-
118
- it("handles complex wrapping with structural changes", () => {
119
- interface BasicDataContext {
120
- index?: number
121
- }
122
-
123
- const basicFormatter = makeFormatter(
124
- (value: string, suggestions, dataContext: BasicDataContext): string | undefined =>
125
- value[dataContext.index ?? 0],
126
- )
127
-
128
- expect(basicFormatter.format("test", [], { index: 2 })).toBe("s")
129
-
130
- interface ComplexStructure {
131
- index: number
132
- value: string
133
- }
134
-
135
- interface ComplexDataContext {
136
- getIndex?: (structure: ComplexStructure) => number
137
- }
138
-
139
- const complexFormatter = basicFormatter.wrap(
140
- (delegate, value: ComplexStructure, suggestions, dataContext: ComplexDataContext) =>
141
- delegate(value.value, suggestions, { index: dataContext.getIndex?.(value) }),
142
- )
143
-
144
- expect(complexFormatter.format({ index: 3, value: "procrastination" })).toBe("p")
145
-
146
- expect(
147
- complexFormatter.format({ index: 3, value: "procrastination" }, [], {
148
- getIndex: ({ index }) => index,
149
- }),
150
- ).toBe("c")
151
-
152
- const boundComplexFormatter = complexFormatter.wrap(
153
- (delegate, value, suggestions, dataContext) =>
154
- delegate(value, suggestions, { ...dataContext, getIndex: ({ index }) => index }),
155
- )
156
-
157
- expect(boundComplexFormatter.format({ index: 2, value: "swag" })).toBe("a")
158
- })
159
- })
@@ -1,214 +0,0 @@
1
- type PrimitiveSuggestion = "primitive"
2
- type SemanticSuggestion = "abbreviated" | "icon" | "verbose"
3
- type Suggestion = PrimitiveSuggestion | SemanticSuggestion
4
-
5
- interface FormatterOptions {
6
- /** Formatter name, useful for debugging or advanced pattern matching. */
7
- displayName?: string
8
- }
9
-
10
- type DataContext = Record<string, any>
11
-
12
- // TODO: Update `FormatDefinition` type based on this snippet. Sadly, TypeScript cannot correctly
13
- // verify the return type of the format definition based on the suggestions, printing errors such
14
- // as `Type 'string' is not assignable to type '"primitive" extends TSuggestion ? string : string'`
15
- // which is, of course, nonsense. Same applies to `FormatChainDefinition`.
16
- //
17
- // interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
18
- // <TSuggestion extends Suggestion>(
19
- // value: TInput,
20
- // usageSuggestions: TSuggestion[],
21
- // dataContext: Partial<TDataContext>,
22
- // ): PrimitiveSuggestion extends TSuggestion ? TPrimitiveOutput : TOutput
23
- // }
24
-
25
- interface FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
26
- (
27
- /** Value to format. */
28
- value: TInput,
29
- /** Suggestions passed by the consumer of a formatter. */
30
- usageSuggestions: Suggestion[],
31
- /** Additional data context to be used by the formatter. */
32
- dataContext: Partial<TDataContext>,
33
- ): TOutput | TPrimitiveOutput
34
- }
35
-
36
- interface FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext extends DataContext> {
37
- <TSuggestion extends Suggestion>(
38
- /** Value to format. */
39
- value: TInput,
40
- /** Suggestions the formatter should take note of. */
41
- usageSuggestions?: TSuggestion[],
42
- /** Additional data context the formatter might find useful. */
43
- dataContext?: Partial<TDataContext>,
44
- ): PrimitiveSuggestion extends TSuggestion ? TPrimitiveOutput : TOutput
45
- }
46
-
47
- interface FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext extends DataContext> {
48
- (
49
- /** Value to format. */
50
- value: TInput,
51
- /** Suggestions the formatter should take note of in addition to `primitive`. */
52
- usageSuggestions?: Suggestion[],
53
- /** Additional data context the formatter might find useful. */
54
- dataContext?: Partial<TDataContext>,
55
- ): TPrimitiveOutput
56
- }
57
-
58
- interface FormatChainDefinition<
59
- TInnerInput,
60
- TInnerOutput,
61
- TInnerPrimitiveInput,
62
- TInnerDataContext extends DataContext,
63
- TOuterInput,
64
- TOuterOutput,
65
- TOuterPrimitiveOutput,
66
- TOuterDataContext extends DataContext
67
- > {
68
- (
69
- /**
70
- * The `formatter.format` method which can be used to delegate the formatting
71
- * to the wrapped formatter. Delegation is simplified so if no suggestions or contextual
72
- * props are passed, the original ones are used instead.
73
- */
74
- delegate: FormatMethod<TInnerInput, TInnerOutput, TInnerPrimitiveInput, TInnerDataContext>,
75
- /** Value to format. */
76
- value: TOuterInput,
77
- /** Suggestions the formatter should take note of. */
78
- usageSuggestions: Suggestion[],
79
- /** Additional data context the formatter might find useful. */
80
- dataContext: Partial<TOuterDataContext>,
81
- ): TOuterOutput | TOuterPrimitiveOutput
82
- }
83
-
84
- type FormatterProps<TInput, TDataContext extends DataContext> = {
85
- /** Value to format. */
86
- children: TInput
87
- /** Suggestions the formatter should take note of. */
88
- suggestions?: Suggestion[]
89
- } & Partial<TDataContext>
90
-
91
- export interface Formatter<
92
- TInput,
93
- TOutput,
94
- TPrimitiveOutput = TOutput,
95
- TDataContext extends DataContext = DataContext
96
- > {
97
- /** Formatter name, useful for debugging or advanced pattern matching. */
98
- displayName?: string
99
- /** Formats a value. */
100
- format: FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext>
101
- /** Formats a value with the `primitive` suggestion. */
102
- formatAsPrimitive: FormatAsPrimitiveMethod<TInput, TPrimitiveOutput, TDataContext>
103
- /** The callee of the `.wrap` method used to produce this formatter. */
104
- innerFormatter?: Formatter<any, any, any, any>
105
- /**
106
- * Creates a new formatter from an existing one. Allows overriding of formatter behaviour
107
- * for certain values.
108
- */
109
- wrap: <
110
- TNextInput = TInput,
111
- TNextOutput = TOutput,
112
- TNextPrimitiveOutput = TPrimitiveOutput,
113
- TNextDataContext extends DataContext = TDataContext
114
- >(
115
- /**
116
- * Function used to format the value. Has the same signature as the one passed
117
- * to `makeFormatter`, except a `delegate` function is passed in the first position.
118
- * This function can be used to delegate formatting to the original (inner) formatter.
119
- */
120
- nextFormat: FormatChainDefinition<
121
- TInput,
122
- TOutput,
123
- TPrimitiveOutput,
124
- TDataContext,
125
- TNextInput,
126
- TNextOutput,
127
- TNextPrimitiveOutput,
128
- TNextDataContext
129
- >,
130
- /** New formatter options, replacing the original ones. */
131
- nextFormatterOptions?: FormatterOptions,
132
- ) => Formatter<TNextInput, TNextOutput, TNextPrimitiveOutput, TNextDataContext>
133
- /**
134
- * Backwards-compatible way to use the formatter as a React component.
135
- *
136
- * @deprecated Since v0.6.0. Prefer using the `Formatter.format` method instead.
137
- */
138
- (props: FormatterProps<TInput, TDataContext>): TOutput | TPrimitiveOutput | null
139
- }
140
-
141
- /**
142
- * Creates a new formatter.
143
- *
144
- * @param format Function used to format the value.
145
- * @param formatterOptions Additional options for the formatter.
146
- */
147
- export const makeFormatter = <
148
- TInput,
149
- TOutput,
150
- TPrimitiveOutput = TOutput,
151
- TDataContext extends DataContext = DataContext
152
- >(
153
- format: FormatDefinition<TInput, TOutput, TPrimitiveOutput, TDataContext>,
154
- formatterOptions?: FormatterOptions,
155
- ): Formatter<TInput, TOutput, TPrimitiveOutput, TDataContext> => {
156
- const formatter: Formatter<TInput, TOutput, TPrimitiveOutput, TDataContext> = ({
157
- children,
158
- suggestions = [],
159
- ...dataContext
160
- }) => format(children, suggestions, dataContext as any) ?? null
161
-
162
- formatter.displayName = formatterOptions?.displayName
163
-
164
- formatter.format = (value, usageSuggestions = [], dataContext = {}) =>
165
- format(value, usageSuggestions, dataContext) as any
166
-
167
- formatter.formatAsPrimitive = (value, usageSuggestions = [], dataContext = {}) =>
168
- format(value, ["primitive", ...usageSuggestions], dataContext) as any
169
-
170
- formatter.wrap = <
171
- TNextInput,
172
- TNextOutput,
173
- TNextPrimitiveOutput,
174
- TNextDataContext extends DataContext
175
- >(
176
- nextFormat: FormatChainDefinition<
177
- TInput,
178
- TOutput,
179
- TPrimitiveOutput,
180
- TDataContext,
181
- TNextInput,
182
- TNextOutput,
183
- TNextPrimitiveOutput,
184
- TNextDataContext
185
- >,
186
- nextFormatterOptions?: FormatterOptions,
187
- ) => {
188
- const nextFormatter: Formatter<
189
- TNextInput,
190
- TNextOutput,
191
- TNextPrimitiveOutput,
192
- TNextDataContext
193
- > = makeFormatter((value, usageSuggestions, dataContext) => {
194
- const delegate: FormatMethod<TInput, TOutput, TPrimitiveOutput, TDataContext> = (
195
- delegatedValue,
196
- delegatedUsageSuggestions,
197
- delegatedDataContext,
198
- ) =>
199
- formatter.format(
200
- delegatedValue,
201
- delegatedUsageSuggestions ?? usageSuggestions,
202
- (delegatedDataContext ?? dataContext) as any,
203
- ) as any
204
-
205
- return nextFormat(delegate, value, usageSuggestions, dataContext) as any
206
- }, nextFormatterOptions ?? formatterOptions)
207
-
208
- nextFormatter.innerFormatter = formatter
209
-
210
- return nextFormatter
211
- }
212
-
213
- return formatter
214
- }
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.build.json",
3
- "compilerOptions": {
4
- "declarationDir": "./dist/types"
5
- }
6
- }