@genesislcap/foundation-forms 15.50.2 → 15.50.3-holiday-picker.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/dist/custom-elements.json +416 -0
- package/dist/dts/index.d.ts +2 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/DatePickerControlRenderer.d.ts +40 -0
- package/dist/dts/jsonforms/renderers/DatePickerControlRenderer.d.ts.map +1 -0
- package/dist/dts/jsonforms/renderers/RenderersRanks.d.ts +2 -0
- package/dist/dts/jsonforms/renderers/RenderersRanks.d.ts.map +1 -1
- package/dist/dts/jsonforms/renderers/ValueDateControlRenderer.d.ts +57 -0
- package/dist/dts/jsonforms/renderers/ValueDateControlRenderer.d.ts.map +1 -0
- package/dist/dts/jsonforms/renderers/date-value-format.d.ts +44 -0
- package/dist/dts/jsonforms/renderers/date-value-format.d.ts.map +1 -0
- package/dist/dts/jsonforms/renderers/renderer-data.d.ts +17 -0
- package/dist/dts/jsonforms/renderers/renderer-data.d.ts.map +1 -0
- package/dist/dts/types.d.ts +211 -1
- package/dist/dts/types.d.ts.map +1 -1
- package/dist/dts/utils/validation.d.ts +32 -0
- package/dist/dts/utils/validation.d.ts.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/jsonforms/renderers/DatePickerControlRenderer.js +131 -0
- package/dist/esm/jsonforms/renderers/RenderersRanks.js +2 -0
- package/dist/esm/jsonforms/renderers/ValueDateControlRenderer.js +275 -0
- package/dist/esm/jsonforms/renderers/date-value-format.js +70 -0
- package/dist/esm/jsonforms/renderers/renderer-data.js +36 -0
- package/dist/esm/utils/validation.js +39 -0
- package/dist/foundation-forms.api.json +479 -2
- package/dist/foundation-forms.d.ts +307 -1
- package/package.json +13 -13
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { mapStateToControlProps, rankWith, Resolve, toDataPath } from '@jsonforms/core';
|
|
2
|
+
import { html } from '@microsoft/fast-element';
|
|
3
|
+
import { optionIs } from '../testers/optionIs';
|
|
4
|
+
import { controlWrapperTemplate } from './ControlWrapperRenderer';
|
|
5
|
+
import { toDateControlValue, toIsoDateValue } from './date-value-format';
|
|
6
|
+
import { rendererOptionsOf, rootDataOf, siblingStringOf } from './renderer-data';
|
|
7
|
+
import { VALUE_DATE_CONTROL_RANK } from './RenderersRanks';
|
|
8
|
+
const SOFR = {
|
|
9
|
+
spotLag: 'T+2',
|
|
10
|
+
dayRollConvention: 'modifiedFollowing',
|
|
11
|
+
currency: 'USD',
|
|
12
|
+
};
|
|
13
|
+
const SONIA = {
|
|
14
|
+
spotLag: 'T+0',
|
|
15
|
+
dayRollConvention: 'modifiedFollowing',
|
|
16
|
+
currency: 'GBP',
|
|
17
|
+
};
|
|
18
|
+
const ESTR = {
|
|
19
|
+
spotLag: 'T+1',
|
|
20
|
+
dayRollConvention: 'modifiedFollowing',
|
|
21
|
+
currency: 'EUR',
|
|
22
|
+
};
|
|
23
|
+
const TONA = {
|
|
24
|
+
spotLag: 'T+2',
|
|
25
|
+
dayRollConvention: 'modifiedFollowing',
|
|
26
|
+
currency: 'JPY',
|
|
27
|
+
};
|
|
28
|
+
const t2 = (currency) => ({
|
|
29
|
+
spotLag: 'T+2',
|
|
30
|
+
dayRollConvention: 'modifiedFollowing',
|
|
31
|
+
currency,
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Default settlement convention per overnight benchmark index, applied when the control's
|
|
35
|
+
* `options.indexField` sibling carries one of these codes: a short name (`SOFR`) or its FpML
|
|
36
|
+
* `floatingRateIndexScheme` code (`USD-SOFR`, checked against scheme 3-14).
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_INDEX_CONVENTIONS = {
|
|
40
|
+
SOFR,
|
|
41
|
+
SONIA,
|
|
42
|
+
ESTR,
|
|
43
|
+
TONA,
|
|
44
|
+
'USD-SOFR': SOFR,
|
|
45
|
+
'USD-SOFR ICE Term': SOFR,
|
|
46
|
+
'GBP-SONIA': SONIA,
|
|
47
|
+
'EUR-EuroSTR': ESTR,
|
|
48
|
+
'JPY-TONA': TONA,
|
|
49
|
+
'CHF-SARON': t2('CHF'),
|
|
50
|
+
'DKK-DESTR': t2('DKK'),
|
|
51
|
+
'NOK-NOWA': t2('NOK'),
|
|
52
|
+
'SEK-SWESTR': t2('SEK'),
|
|
53
|
+
'PLN-WIRON': t2('PLN'),
|
|
54
|
+
'SGD-SORA': t2('SGD'),
|
|
55
|
+
'TRY-TLREF': t2('TRY'),
|
|
56
|
+
};
|
|
57
|
+
// `<foundation-value-date>`'s own defaults.
|
|
58
|
+
const DEFAULT_SPOT_LAG = 'HOST_T+2';
|
|
59
|
+
const DEFAULT_DAY_ROLL_CONVENTION = 'modifiedFollowing';
|
|
60
|
+
function optionsOf(x) {
|
|
61
|
+
return rendererOptionsOf(x);
|
|
62
|
+
}
|
|
63
|
+
function baseCurrencyOf(x) {
|
|
64
|
+
var _a, _b;
|
|
65
|
+
const options = optionsOf(x);
|
|
66
|
+
return (_b = siblingStringOf(x, (_a = options.baseCurrencyField) !== null && _a !== void 0 ? _a : 'BASE_CURRENCY')) !== null && _b !== void 0 ? _b : options.baseCurrency;
|
|
67
|
+
}
|
|
68
|
+
function quoteCurrencyOf(x) {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
const options = optionsOf(x);
|
|
71
|
+
return ((_b = siblingStringOf(x, (_a = options.quoteCurrencyField) !== null && _a !== void 0 ? _a : 'QUOTE_CURRENCY')) !== null && _b !== void 0 ? _b : options.quoteCurrency);
|
|
72
|
+
}
|
|
73
|
+
function indexConventionOf(x) {
|
|
74
|
+
var _a;
|
|
75
|
+
const index = siblingStringOf(x, (_a = optionsOf(x).indexField) !== null && _a !== void 0 ? _a : 'INDEX');
|
|
76
|
+
return index ? DEFAULT_INDEX_CONVENTIONS[index] : undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The conventions this control locks: a recognised index's, else the fixed
|
|
80
|
+
* `spotLag`/`dayRollConvention` options; `undefined` leaves them to the user.
|
|
81
|
+
*/
|
|
82
|
+
function lockedConventionOf(x) {
|
|
83
|
+
const index = indexConventionOf(x);
|
|
84
|
+
if (index) {
|
|
85
|
+
return index;
|
|
86
|
+
}
|
|
87
|
+
const { spotLag, dayRollConvention } = optionsOf(x);
|
|
88
|
+
return spotLag || dayRollConvention
|
|
89
|
+
? {
|
|
90
|
+
spotLag: spotLag !== null && spotLag !== void 0 ? spotLag : DEFAULT_SPOT_LAG,
|
|
91
|
+
dayRollConvention: dayRollConvention !== null && dayRollConvention !== void 0 ? dayRollConvention : DEFAULT_DAY_ROLL_CONVENTION,
|
|
92
|
+
}
|
|
93
|
+
: undefined;
|
|
94
|
+
}
|
|
95
|
+
/** The widest spot lag `<foundation-value-date>` offers, in business days. */
|
|
96
|
+
const MAX_SPOT_LAG_DAYS = 2;
|
|
97
|
+
/** A spot lag in business days (`T+N` is `N`); `HOST_T+2` counts two. */
|
|
98
|
+
export function spotLagDaysOf(spotLag) {
|
|
99
|
+
return spotLag === 'HOST_T+2' ? MAX_SPOT_LAG_DAYS : Number(spotLag.slice('T+'.length));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The spot lag for a stored day count, capped at `T+2`; `undefined` for an empty or invalid value.
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
export function spotLagOfDays(days) {
|
|
106
|
+
const n = typeof days === 'string' && days.trim() ? Number(days) : days;
|
|
107
|
+
if (typeof n !== 'number' || !Number.isInteger(n) || n < 0) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return `T+${Math.min(n, MAX_SPOT_LAG_DAYS)}`;
|
|
111
|
+
}
|
|
112
|
+
/** The `spotLagDaysScope` field's data path, or `undefined` without one. */
|
|
113
|
+
function spotLagDaysPathOf(x) {
|
|
114
|
+
const scope = optionsOf(x).spotLagDaysScope;
|
|
115
|
+
return scope ? toDataPath(scope) : undefined;
|
|
116
|
+
}
|
|
117
|
+
/** The raw value of the `spotLagDaysScope` field. */
|
|
118
|
+
function storedSpotLagDaysOf(x) {
|
|
119
|
+
const path = spotLagDaysPathOf(x);
|
|
120
|
+
return path ? Resolve.data(rootDataOf(x), path) : undefined;
|
|
121
|
+
}
|
|
122
|
+
/** The conventions the user last picked, echoed back on re-render. */
|
|
123
|
+
const userConventionsState = new WeakMap();
|
|
124
|
+
/**
|
|
125
|
+
* Locked conventions win; otherwise the user's own choice; otherwise the `spotLagDaysScope`
|
|
126
|
+
* field's lag; otherwise the widget's defaults.
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
export function conventionsOf(x) {
|
|
130
|
+
var _a, _b, _c;
|
|
131
|
+
return ((_b = (_a = lockedConventionOf(x)) !== null && _a !== void 0 ? _a : userConventionsState.get(x)) !== null && _b !== void 0 ? _b : {
|
|
132
|
+
spotLag: (_c = spotLagOfDays(storedSpotLagDaysOf(x))) !== null && _c !== void 0 ? _c : DEFAULT_SPOT_LAG,
|
|
133
|
+
dayRollConvention: DEFAULT_DAY_ROLL_CONVENTION,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Keeps the user's choice, and writes a changed spot lag to the `spotLagDaysScope` field. An
|
|
138
|
+
* unchanged lag leaves the field alone, so a stored offset above 2 survives a day-roll edit.
|
|
139
|
+
* @internal
|
|
140
|
+
*/
|
|
141
|
+
export function handleConventionsChanged(x, event) {
|
|
142
|
+
const detail = event.detail;
|
|
143
|
+
userConventionsState.set(x, detail);
|
|
144
|
+
const path = spotLagDaysPathOf(x);
|
|
145
|
+
if (path && spotLagOfDays(storedSpotLagDaysOf(x)) !== detail.spotLag) {
|
|
146
|
+
x.control.handleChange(path, spotLagDaysOf(detail.spotLag));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** The `tradeDateField` sibling's value as ISO `YYYY-MM-DD`; `undefined` counts from today. */
|
|
150
|
+
function tradeDateOf(x) {
|
|
151
|
+
const field = optionsOf(x).tradeDateField;
|
|
152
|
+
return field ? toIsoDateValue(rootDataOf(x)[field]) : undefined;
|
|
153
|
+
}
|
|
154
|
+
/** The element's last `targetInput` (e.g. a typed tenor), echoed back on re-render. */
|
|
155
|
+
const targetInputState = new WeakMap();
|
|
156
|
+
/**
|
|
157
|
+
* The element's last `targetInput`, else a first-render seed: `SPOT` with `tradeDateField` (so it
|
|
158
|
+
* follows the trade date), otherwise the stored value as ISO `YYYY-MM-DD` so it round-trips.
|
|
159
|
+
*/
|
|
160
|
+
function targetInputOf(x) {
|
|
161
|
+
const cached = targetInputState.get(x);
|
|
162
|
+
if (cached !== undefined) {
|
|
163
|
+
return cached;
|
|
164
|
+
}
|
|
165
|
+
const seed = optionsOf(x).tradeDateField ? 'SPOT' : toIsoDateValue(x.control.data) || 'SPOT';
|
|
166
|
+
targetInputState.set(x, seed);
|
|
167
|
+
return seed;
|
|
168
|
+
}
|
|
169
|
+
/** The base, quote and index currencies that drive `holidayCurrencies`, in that order. */
|
|
170
|
+
function drivingCurrenciesOf(x) {
|
|
171
|
+
var _a;
|
|
172
|
+
return [baseCurrencyOf(x), quoteCurrencyOf(x), (_a = indexConventionOf(x)) === null || _a === void 0 ? void 0 : _a.currency];
|
|
173
|
+
}
|
|
174
|
+
/** Identifies the driving currencies, so a cached selection is dropped when they change. */
|
|
175
|
+
function currencySignatureOf(currencies) {
|
|
176
|
+
return currencies.map((code) => code !== null && code !== void 0 ? code : '').join('|');
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* `holidayCurrencies` derived from the pair/index, or the user's own selection, keyed by the
|
|
180
|
+
* signature it belongs to; a pair/index change invalidates it.
|
|
181
|
+
*/
|
|
182
|
+
const holidayCurrenciesState = new WeakMap();
|
|
183
|
+
function holidayCurrenciesOf(x) {
|
|
184
|
+
const driving = drivingCurrenciesOf(x);
|
|
185
|
+
const signature = currencySignatureOf(driving);
|
|
186
|
+
const cached = holidayCurrenciesState.get(x);
|
|
187
|
+
if (cached && cached.signature === signature) {
|
|
188
|
+
return cached.currencies;
|
|
189
|
+
}
|
|
190
|
+
const currencies = Array.from(new Set(driving.filter((code) => !!code)));
|
|
191
|
+
holidayCurrenciesState.set(x, { signature, currencies });
|
|
192
|
+
return currencies;
|
|
193
|
+
}
|
|
194
|
+
/** @internal */
|
|
195
|
+
export function handleValueDateChanged(x, event) {
|
|
196
|
+
var _a;
|
|
197
|
+
const detail = event.detail;
|
|
198
|
+
const targetInput = (_a = event.target) === null || _a === void 0 ? void 0 : _a.targetInput;
|
|
199
|
+
if (targetInput !== undefined) {
|
|
200
|
+
targetInputState.set(x, targetInput);
|
|
201
|
+
}
|
|
202
|
+
x.onBlur();
|
|
203
|
+
const { valueFormat = 'iso', unadjustedDateScope } = optionsOf(x);
|
|
204
|
+
x.control.handleChange(x.control.path,
|
|
205
|
+
// `undefined` (the target no longer resolves) clears the control.
|
|
206
|
+
toDateControlValue(detail.deliveryDateValue, valueFormat));
|
|
207
|
+
if (unadjustedDateScope) {
|
|
208
|
+
x.control.handleChange(toDataPath(unadjustedDateScope), toDateControlValue(detail.targetDateValue, valueFormat));
|
|
209
|
+
}
|
|
210
|
+
const spotLagDaysPath = spotLagDaysPathOf(x);
|
|
211
|
+
if (spotLagDaysPath && spotLagOfDays(storedSpotLagDaysOf(x)) === undefined) {
|
|
212
|
+
x.control.handleChange(spotLagDaysPath, spotLagDaysOf(detail.spotLag));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Captures the user's holiday-calendar selection for `holidayCurrenciesOf` to echo back. */
|
|
216
|
+
function handleHolidayCurrenciesChanged(x, event) {
|
|
217
|
+
const currencies = event.detail;
|
|
218
|
+
holidayCurrenciesState.set(x, {
|
|
219
|
+
signature: currencySignatureOf(drivingCurrenciesOf(x)),
|
|
220
|
+
currencies,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Renders a `<foundation-value-date>` bound to sibling fields (currency pair, index, trade date)
|
|
225
|
+
* via `options`, and writes the resolved delivery date back as this control's value - plus,
|
|
226
|
+
* optionally, the unadjusted date (`unadjustedDateScope`) and the spot lag in days
|
|
227
|
+
* (`spotLagDaysScope`). A read-only control shows the date with its conventions locked.
|
|
228
|
+
*
|
|
229
|
+
* Every binding re-pushes on each form-wide re-render, so values the user can change in the
|
|
230
|
+
* widget are cached per renderer and echoed back instead of being reset.
|
|
231
|
+
* @public
|
|
232
|
+
*/
|
|
233
|
+
export const ValueDateControlRendererTemplate = (prefix = 'rapid') => html `
|
|
234
|
+
<template>
|
|
235
|
+
${controlWrapperTemplate({
|
|
236
|
+
prefix,
|
|
237
|
+
innerTemplate: html `
|
|
238
|
+
<${prefix}-value-date
|
|
239
|
+
id=${(x) => x.control.path}
|
|
240
|
+
style="${(x) => (x.control.enabled ? '' : 'pointer-events: none; opacity: 0.6;')}"
|
|
241
|
+
aria-disabled="${(x) => (x.control.enabled ? 'false' : 'true')}"
|
|
242
|
+
data-test-id=${(x) => x.control.path}
|
|
243
|
+
base-currency="${(x) => baseCurrencyOf(x)}"
|
|
244
|
+
quote-currency="${(x) => quoteCurrencyOf(x)}"
|
|
245
|
+
?holiday-calendars=${(x) => !!optionsOf(x).holidayCalendars}
|
|
246
|
+
:holidayCurrencies=${(x) => holidayCurrenciesOf(x)}
|
|
247
|
+
:targetInput=${(x) => targetInputOf(x)}
|
|
248
|
+
trade-date="${(x) => tradeDateOf(x)}"
|
|
249
|
+
:spotLag=${(x) => conventionsOf(x).spotLag}
|
|
250
|
+
:dayRollConvention=${(x) => conventionsOf(x).dayRollConvention}
|
|
251
|
+
?lock-conventions=${(x) => !!lockedConventionOf(x) || !x.control.enabled}
|
|
252
|
+
@value-date-changed=${(x, c) => handleValueDateChanged(x, c.event)}
|
|
253
|
+
@holiday-currencies-changed=${(x, c) => handleHolidayCurrenciesChanged(x, c.event)}
|
|
254
|
+
@conventions-changed=${(x, c) => handleConventionsChanged(x, c.event)}
|
|
255
|
+
></${prefix}-value-date>
|
|
256
|
+
`,
|
|
257
|
+
})}
|
|
258
|
+
</template>
|
|
259
|
+
`;
|
|
260
|
+
/**
|
|
261
|
+
* Renderer entry for a value-date control. Opt in per form via `additionalRenderers`:
|
|
262
|
+
* ```ts
|
|
263
|
+
* import { ValueDateControlRendererEntry } from '@genesislcap/foundation-forms';
|
|
264
|
+
* form.additionalRenderers = [ValueDateControlRendererEntry];
|
|
265
|
+
* ```
|
|
266
|
+
* and mark the field with `options: { valueDate: true, baseCurrencyField: 'BASE_CCY', ... }`.
|
|
267
|
+
* @public
|
|
268
|
+
*/
|
|
269
|
+
export const ValueDateControlRendererEntry = {
|
|
270
|
+
renderer: html `
|
|
271
|
+
${(x) => ValueDateControlRendererTemplate(x.prefix)}
|
|
272
|
+
`,
|
|
273
|
+
tester: rankWith(VALUE_DATE_CONTROL_RANK, optionIs('valueDate')),
|
|
274
|
+
mapper: mapStateToControlProps,
|
|
275
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
2
|
+
/** The date picker's internal `value` wire format, `MM-DD-YYYY` (`DATE_PICKER_VALUE_FORMAT`). */
|
|
3
|
+
const PICKER_WIRE_VALUE = /^(\d{2})-(\d{2})-(\d{4})$/;
|
|
4
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
5
|
+
/**
|
|
6
|
+
* Parses a stored date control value — an ISO `YYYY-MM-DD` string or UTC-midnight epoch
|
|
7
|
+
* milliseconds — into its calendar parts. Returns `undefined` for anything else.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export function parseDateValue(value) {
|
|
11
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
12
|
+
const date = new Date(value);
|
|
13
|
+
return {
|
|
14
|
+
year: date.getUTCFullYear(),
|
|
15
|
+
month: date.getUTCMonth() + 1,
|
|
16
|
+
day: date.getUTCDate(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const match = typeof value === 'string' ? ISO_DATE.exec(value) : null;
|
|
20
|
+
return match
|
|
21
|
+
? { year: Number(match[1]), month: Number(match[2]), day: Number(match[3]) }
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Serialises calendar parts as an ISO `YYYY-MM-DD` string or UTC-midnight epoch milliseconds.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export function formatDateValue(parts, valueFormat) {
|
|
29
|
+
return valueFormat === 'epoch'
|
|
30
|
+
? Date.UTC(parts.year, parts.month - 1, parts.day)
|
|
31
|
+
: `${parts.year}-${pad(parts.month)}-${pad(parts.day)}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Converts a date control value (ISO `YYYY-MM-DD` or UTC-midnight epoch milliseconds) into
|
|
35
|
+
* `valueFormat`, or `undefined` when it isn't a date (which clears the control).
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function toDateControlValue(value, valueFormat) {
|
|
39
|
+
const parts = parseDateValue(value);
|
|
40
|
+
return parts ? formatDateValue(parts, valueFormat) : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Normalises a date control value to ISO `YYYY-MM-DD`, or `undefined` when it isn't a date.
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export function toIsoDateValue(value) {
|
|
47
|
+
return toDateControlValue(value, 'iso');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Converts a stored date control value (either storage format) into the picker's `MM-DD-YYYY`
|
|
51
|
+
* wire value, or `''` when it isn't a date.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export function toPickerWireValue(value) {
|
|
55
|
+
const parts = parseDateValue(value);
|
|
56
|
+
return parts ? `${pad(parts.month)}-${pad(parts.day)}-${parts.year}` : '';
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Converts the picker's `value-changed` detail into a storage format; the inverse of
|
|
60
|
+
* `toPickerWireValue`.
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export function fromPickerWireValue(value, valueFormat) {
|
|
64
|
+
const match = value ? PICKER_WIRE_VALUE.exec(value) : null;
|
|
65
|
+
if (!match) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
const [, month, day, year] = match;
|
|
69
|
+
return formatDateValue({ year: Number(year), month: Number(month), day: Number(day) }, valueFormat);
|
|
70
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** A control's `uischema.options`, typed for its renderer. */
|
|
2
|
+
export function rendererOptionsOf(x) {
|
|
3
|
+
var _a, _b;
|
|
4
|
+
return ((_b = (_a = x.control.uischema) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : {});
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The whole form's data, not just this control's, so a control nested anywhere (including in
|
|
8
|
+
* array rows) can read top-level sibling fields such as a trade currency or start date.
|
|
9
|
+
*/
|
|
10
|
+
export function rootDataOf(x) {
|
|
11
|
+
var _a, _b, _c;
|
|
12
|
+
return (_c = (_b = (_a = x.jsonforms) === null || _a === void 0 ? void 0 : _a.core) === null || _b === void 0 ? void 0 : _b.data) !== null && _c !== void 0 ? _c : {};
|
|
13
|
+
}
|
|
14
|
+
/** A top-level sibling field's value when it's a non-empty string, else `undefined`. */
|
|
15
|
+
export function siblingStringOf(x, field) {
|
|
16
|
+
if (!field) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const value = rootDataOf(x)[field];
|
|
20
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Calendar codes from a fixed list plus a top-level sibling field, which may hold an array or a
|
|
24
|
+
* string separated by `;` (as Genesis calendar fields store them), commas or spaces. Returned
|
|
25
|
+
* space-separated, deduped, for a date-picker code attribute; `undefined` when there are none.
|
|
26
|
+
*/
|
|
27
|
+
export function calendarCodesOf(x, fixed, field) {
|
|
28
|
+
const value = field ? rootDataOf(x)[field] : undefined;
|
|
29
|
+
const fromField = Array.isArray(value)
|
|
30
|
+
? value.filter((code) => typeof code === 'string')
|
|
31
|
+
: typeof value === 'string'
|
|
32
|
+
? value.split(/[\s,;]+/)
|
|
33
|
+
: [];
|
|
34
|
+
const codes = Array.from(new Set([...(fixed !== null && fixed !== void 0 ? fixed : []), ...fromField].map((code) => code.trim()).filter(Boolean)));
|
|
35
|
+
return codes.length ? codes.join(' ') : undefined;
|
|
36
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toIsoDateValue } from '../jsonforms/renderers/date-value-format';
|
|
1
2
|
/**
|
|
2
3
|
* Ensures the value of the current field (the one the validator is on) matches the one of the target field.
|
|
3
4
|
* @param target - any other field
|
|
@@ -23,6 +24,44 @@ export const mustMatch = (target) => {
|
|
|
23
24
|
];
|
|
24
25
|
};
|
|
25
26
|
};
|
|
27
|
+
const dateOrderValidator = (target, violates, message) => (data, path) => {
|
|
28
|
+
if (!(path === null || path === void 0 ? void 0 : path.length) || !(target === null || target === void 0 ? void 0 : target.length) || !data)
|
|
29
|
+
return [];
|
|
30
|
+
// ISO `YYYY-MM-DD` strings compare chronologically; either field may hold ISO or epoch.
|
|
31
|
+
const value = toIsoDateValue(data === null || data === void 0 ? void 0 : data[path]);
|
|
32
|
+
const targetValue = toIsoDateValue(data === null || data === void 0 ? void 0 : data[target]);
|
|
33
|
+
if (!value || !targetValue || !violates(value, targetValue))
|
|
34
|
+
return [];
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
instancePath: `/${path}`,
|
|
38
|
+
message,
|
|
39
|
+
schemaPath: '',
|
|
40
|
+
keyword: '',
|
|
41
|
+
params: {},
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Ensures the date in the current field (the one the validator is on) is on or after the date in
|
|
47
|
+
* the target field, e.g. an end date that can't be before its start date. Either field may hold
|
|
48
|
+
* an ISO `YYYY-MM-DD` string or epoch milliseconds; no error while either is empty.
|
|
49
|
+
* @param target - the other date field, e.g. `'START_DATE'`
|
|
50
|
+
* @param message - the error shown on the current field; defaults to `Must not be before <target>`
|
|
51
|
+
* @returns a validator function that will be run by the smart form during the client-side validation stage
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export const mustBeOnOrAfter = (target, message = `Must not be before ${target}`) => dateOrderValidator(target, (value, targetValue) => value < targetValue, message);
|
|
55
|
+
/**
|
|
56
|
+
* Ensures the date in the current field (the one the validator is on) is on or before the date
|
|
57
|
+
* in the target field, e.g. a start date that can't be after its end date. Either field may hold
|
|
58
|
+
* an ISO `YYYY-MM-DD` string or epoch milliseconds; no error while either is empty.
|
|
59
|
+
* @param target - the other date field, e.g. `'END_DATE'`
|
|
60
|
+
* @param message - the error shown on the current field; defaults to `Must not be after <target>`
|
|
61
|
+
* @returns a validator function that will be run by the smart form during the client-side validation stage
|
|
62
|
+
* @public
|
|
63
|
+
*/
|
|
64
|
+
export const mustBeOnOrBefore = (target, message = `Must not be after ${target}`) => dateOrderValidator(target, (value, targetValue) => value > targetValue, message);
|
|
26
65
|
const describeError = (prop, parentProps) => {
|
|
27
66
|
const pSchema = parentProps[prop] || {};
|
|
28
67
|
const constraints = ['required'];
|