@isi-ui7/bos7-shared 0.3.11 → 0.4.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/dist/crud-types.d.ts +50 -0
- package/dist/form-renderer-utils.d.ts +11 -0
- package/dist/{index-C_114Ysm.js → index-DqWPV29B.js} +71 -71
- package/dist/{index.es-B-MluT1E.js → index.es-9cvhYbjn.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/{jspdf.es.min-C5VEsLZd.js → jspdf.es.min-CCBzPHw4.js} +2 -2
- package/dist/{jspdf.plugin.autotable-DcntYaes.js → jspdf.plugin.autotable-DFaknRns.js} +19 -22
- package/dist/purify.es-Cm3utOpm.js +560 -0
- package/package.json +9 -9
- package/src/crud-components.tsx +81 -3
- package/src/crud-filter-fields.test.tsx +156 -0
- package/src/crud-types.ts +50 -0
- package/src/date-field-render.test.tsx +83 -0
- package/src/form-renderer-aria-required.test.tsx +101 -0
- package/src/form-renderer-utils.ts +19 -0
- package/src/form-renderer.tsx +28 -3
- package/src/vitest.setup.ts +14 -0
- package/dist/purify.es-CiEWEeUM.js +0 -605
package/src/form-renderer.tsx
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import type { Ui7FormDensity, Ui7FormWidth } from "./style-contract";
|
|
34
34
|
import { useBosSharedI18n } from "./i18n";
|
|
35
35
|
import type { FormField, FormMode, FormSection } from "./form-types";
|
|
36
|
-
import { toLocalISODate } from "./form-renderer-utils";
|
|
36
|
+
import { toLocalISODate, fromLocalISODate } from "./form-renderer-utils";
|
|
37
37
|
import { DetailModalField } from "./detail-modal-field";
|
|
38
38
|
|
|
39
39
|
export type SchemaFormRendererProps<TData extends Record<string, unknown>> = {
|
|
@@ -64,7 +64,14 @@ function asString(value: unknown): string {
|
|
|
64
64
|
return String(value);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
// Wraps a field label with an optional required asterisk for Carbon's `labelText` prop.
|
|
68
|
+
// The asterisk is `aria-hidden` on purpose — it's decoration for sighted users.
|
|
69
|
+
// The actual screen-reader signal is `aria-required`, forwarded separately onto
|
|
70
|
+
// each control below. Forwarded as `aria-required`, NOT native `required`: native
|
|
71
|
+
// `required` adds a second, browser-native validation system with its own
|
|
72
|
+
// message/tooltip on top of `runFieldValidation`'s in-app enforcement of
|
|
73
|
+
// `validation.required` — two error systems for one field. `aria-required` only
|
|
74
|
+
// announces, doesn't enforce, which is exactly the gap that was missing.
|
|
68
75
|
function mkLabel(label: ReactNode, required?: boolean) {
|
|
69
76
|
return (
|
|
70
77
|
<>
|
|
@@ -225,6 +232,7 @@ function NumericField({
|
|
|
225
232
|
invalid,
|
|
226
233
|
invalidText,
|
|
227
234
|
placeholder,
|
|
235
|
+
required,
|
|
228
236
|
onChange,
|
|
229
237
|
}: {
|
|
230
238
|
id: string;
|
|
@@ -246,6 +254,8 @@ function NumericField({
|
|
|
246
254
|
invalid?: boolean;
|
|
247
255
|
invalidText?: string;
|
|
248
256
|
placeholder?: string;
|
|
257
|
+
/** Forwarded as `aria-required` on the underlying `TextInput` — see `mkLabel` for why not native `required`. */
|
|
258
|
+
required?: boolean;
|
|
249
259
|
onChange: (v: number) => void;
|
|
250
260
|
}) {
|
|
251
261
|
const [raw, setRaw] = useState<string | null>(null);
|
|
@@ -378,6 +388,7 @@ function NumericField({
|
|
|
378
388
|
invalid={invalid}
|
|
379
389
|
invalidText={invalidText}
|
|
380
390
|
placeholder={placeholder}
|
|
391
|
+
aria-required={required ? true : undefined}
|
|
381
392
|
/>
|
|
382
393
|
);
|
|
383
394
|
|
|
@@ -836,7 +847,12 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
836
847
|
// DISIMPAN tetap ISO. Dua hal berbeda: yang satu untuk
|
|
837
848
|
// dibaca manusia, yang satu kontrak dgn backend.
|
|
838
849
|
dateFormat="d-m-Y"
|
|
839
|
-
value
|
|
850
|
+
// `value` HARUS `Date`, bukan string ISO — string ISO
|
|
851
|
+
// di sini di-parse ULANG flatpickr seolah format
|
|
852
|
+
// tampilan (d-m-Y), menghasilkan tanggal lain sama
|
|
853
|
+
// sekali pada setiap re-render (laporan DG-00,
|
|
854
|
+
// agent7 2026-08-31, kena SEMUA field tanggal).
|
|
855
|
+
value={fromLocalISODate(asString(rawValue))}
|
|
840
856
|
onChange={([date]) =>
|
|
841
857
|
setValue(field.key, toLocalISODate(date) as TData[keyof TData])
|
|
842
858
|
}
|
|
@@ -849,6 +865,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
849
865
|
disabled={disabled}
|
|
850
866
|
invalid={Boolean(invalidText)}
|
|
851
867
|
invalidText={invalidText}
|
|
868
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
852
869
|
/>
|
|
853
870
|
</DatePicker>
|
|
854
871
|
{field.helperText ? (
|
|
@@ -872,6 +889,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
872
889
|
disabled={disabled}
|
|
873
890
|
invalid={Boolean(invalidText)}
|
|
874
891
|
invalidText={invalidText}
|
|
892
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
875
893
|
>
|
|
876
894
|
{(field.options ?? []).map((opt) => (
|
|
877
895
|
<SelectItem key={String(opt.value)} value={opt.value} text={opt.label} />
|
|
@@ -944,6 +962,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
944
962
|
invalid={Boolean(effectiveInvalidText)}
|
|
945
963
|
invalidText={effectiveInvalidText}
|
|
946
964
|
placeholder={field.placeholder}
|
|
965
|
+
required={effectiveRequired}
|
|
947
966
|
onChange={(v) => setValue(field.key, v as TData[keyof TData])}
|
|
948
967
|
/>
|
|
949
968
|
</div>,
|
|
@@ -962,6 +981,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
962
981
|
setValue(field.key, Boolean(checked) as TData[keyof TData])
|
|
963
982
|
}
|
|
964
983
|
disabled={disabled}
|
|
984
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
965
985
|
/>
|
|
966
986
|
</div>,
|
|
967
987
|
];
|
|
@@ -999,6 +1019,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
999
1019
|
)
|
|
1000
1020
|
}
|
|
1001
1021
|
disabled={disabled}
|
|
1022
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
1002
1023
|
/>
|
|
1003
1024
|
</div>
|
|
1004
1025
|
</FieldShell>
|
|
@@ -1023,6 +1044,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
1023
1044
|
maxLength={field.maxLength}
|
|
1024
1045
|
invalid={Boolean(invalidText)}
|
|
1025
1046
|
invalidText={invalidText}
|
|
1047
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
1026
1048
|
/>
|
|
1027
1049
|
</div>,
|
|
1028
1050
|
];
|
|
@@ -1049,6 +1071,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
1049
1071
|
readOnly={effectiveReadonly}
|
|
1050
1072
|
invalid={Boolean(invalidText)}
|
|
1051
1073
|
invalidText={invalidText}
|
|
1074
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
1052
1075
|
/>
|
|
1053
1076
|
</div>,
|
|
1054
1077
|
];
|
|
@@ -1076,6 +1099,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
1076
1099
|
invalid={Boolean(invalidText)}
|
|
1077
1100
|
invalidText={invalidText}
|
|
1078
1101
|
placeholder={field.placeholder}
|
|
1102
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
1079
1103
|
/>
|
|
1080
1104
|
</div>,
|
|
1081
1105
|
];
|
|
@@ -1099,6 +1123,7 @@ export function SchemaFormRenderer<TData extends Record<string, unknown>>({
|
|
|
1099
1123
|
invalid={Boolean(invalidText)}
|
|
1100
1124
|
invalidText={invalidText}
|
|
1101
1125
|
placeholder={field.placeholder}
|
|
1126
|
+
aria-required={effectiveRequired ? true : undefined}
|
|
1102
1127
|
/>
|
|
1103
1128
|
</div>,
|
|
1104
1129
|
];
|
package/src/vitest.setup.ts
CHANGED
|
@@ -20,3 +20,17 @@ if (typeof Element !== "undefined" && !Element.prototype.scrollIntoView) {
|
|
|
20
20
|
/* no-op: jsdom nol punya viewport untuk digulir */
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
// Sama alasannya, komponen beda: jsdom nol mengimplementasikan `ResizeObserver`.
|
|
25
|
+
// Carbon `TextArea` memanggilnya (auto-resize) saat mount — tanpa tambalan ini,
|
|
26
|
+
// SETIAP uji yang merender `TextArea` (langsung atau lewat `SchemaFormRenderer`)
|
|
27
|
+
// melempar `ReferenceError` di layout-effect, di luar rantai promise uji — kelas
|
|
28
|
+
// kegagalan yang sama: ringkasan `it()` bisa tetap hijau sementara exit code
|
|
29
|
+
// menolak. No-op sengaja: yang diuji adalah apa yang terender, bukan resize.
|
|
30
|
+
if (typeof globalThis.ResizeObserver === "undefined") {
|
|
31
|
+
globalThis.ResizeObserver = class ResizeObserver {
|
|
32
|
+
observe() {}
|
|
33
|
+
unobserve() {}
|
|
34
|
+
disconnect() {}
|
|
35
|
+
} as unknown as typeof ResizeObserver;
|
|
36
|
+
}
|