@isi-ui7/bos7-shared 0.2.4 → 0.2.6

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.
@@ -3,6 +3,8 @@ import { CrudDeleteSchema, CrudListSchema } from './crud-types';
3
3
  import { CrudForm, FormMode } from './form-types';
4
4
  import { Ui7FormDensity } from './style-contract';
5
5
 
6
+ export { buildValueSections, buildXRulesValidator, } from './form-value-schema';
7
+ export type { JSONSchema, XUi, XUiRoot, XUiNumeric, XUiOption, XUiWidget, XRule, XRuleOp, ValueValidatorFn, } from './form-value-schema';
6
8
  export declare function SharedCrudListPage({ schema, tableKey, onAdd, onPopupClick }: {
7
9
  schema: CrudListSchema;
8
10
  tableKey?: number;
@@ -12,4 +12,4 @@ export declare function applyCrudFieldOverrides<TField extends {
12
12
  readonly?: boolean;
13
13
  required?: boolean;
14
14
  helperText?: unknown;
15
- }>(fields: TField[], hooks?: CrudActionOverrideHooks<Record<string, unknown>> | CrudActionOverrideHooks<any>): TField[];
15
+ }, TData extends Record<string, unknown> = Record<string, unknown>>(fields: TField[], hooks?: CrudActionOverrideHooks<TData>): TField[];
@@ -63,8 +63,6 @@ export type CrudListSchema = {
63
63
  showSort?: boolean;
64
64
  /** Enable search bar. Defaults to true. */
65
65
  showSearch?: boolean;
66
- /** Endpoint for LookupInput (defaults to apiPath if omitted). In Pattern B both DataTable and LookupInput share the same /query endpoint. */
67
- lookupApiPath?: string;
68
66
  /**
69
67
  * Level 2: schema-driven confirmation modals for custom popup actions.
70
68
  * Keyed by popup menu item id. Actions not listed here fall through to onCustomAction (Level 1).
@@ -33,3 +33,15 @@ export declare const INDONESIA_PHONE_RULE: PhoneRule;
33
33
  export declare const GLOBAL_PHONE_RULE: PhoneRule;
34
34
  /** Standard currency precision for Indonesian banking (IDR 2 dec, USD 4 dec). */
35
35
  export declare const INDONESIA_CURRENCY_CONFIG: CurrencyPrecisionConfig;
36
+ /**
37
+ * Whole-rupiah currency config — used for ledger-style fields like plafond,
38
+ * subsidi, agent limit, where the convention in Indonesian retail banking is
39
+ * to round to whole IDR (no sen). USD/USD-denominated fields still keep 2
40
+ * decimals (standard cents). Default precision is 0 so any other currency
41
+ * defaults to whole units in these contexts.
42
+ *
43
+ * Use this for "agent plafond / saldo / subsidi" style fields. For amounts
44
+ * that genuinely need sub-rupiah precision (e.g. percentage-derived fees,
45
+ * accrued interest), prefer `INDONESIA_CURRENCY_CONFIG` instead.
46
+ */
47
+ export declare const INDONESIA_RUPIAH_WHOLE_CONFIG: CurrencyPrecisionConfig;
@@ -1,4 +1,4 @@
1
- import { Ui7FormDensity } from './style-contract';
1
+ import { Ui7FormDensity, Ui7FormWidth } from './style-contract';
2
2
  import { FormMode, FormSection } from './form-types';
3
3
 
4
4
  export type SchemaFormRendererProps<TData extends Record<string, unknown>> = {
@@ -14,5 +14,7 @@ export type SchemaFormRendererProps<TData extends Record<string, unknown>> = {
14
14
  scopeClassName?: string;
15
15
  /** Form layout density. Defaults to "compact". */
16
16
  density?: Ui7FormDensity;
17
+ /** Desktop form-panel width. Defaults to "two-thirds". */
18
+ width?: Ui7FormWidth;
17
19
  };
18
- export declare function SchemaFormRenderer<TData extends Record<string, unknown>>({ mode, value, sections, errors, disabled, onChange, className, showSectionHeader, scopeClassName, density, }: SchemaFormRendererProps<TData>): import("react/jsx-runtime").JSX.Element;
20
+ export declare function SchemaFormRenderer<TData extends Record<string, unknown>>({ mode, value, sections, errors, disabled, onChange, className, showSectionHeader, scopeClassName, density, width, }: SchemaFormRendererProps<TData>): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { ReactNode } from 'react';
2
- import { Ui7FormDensity } from './style-contract';
2
+ import { Ui7FormDensity, Ui7FormWidth } from './style-contract';
3
3
  import { T_LookupTblStruct } from '@isi-ui7/lookup-input';
4
+ import { EditableColumnDef } from '@isi-ui7/editable-table';
4
5
 
5
6
  export type FormMode = "create" | "edit" | "view";
6
7
  export type ApiPath = string | ((params: Record<string, string>) => string);
@@ -18,7 +19,7 @@ export type FieldValidation<TData extends Record<string, unknown>> = {
18
19
  /** Array of validators — compose Rules.* or custom functions. Runs after built-in rules. */
19
20
  validators?: FieldValidatorFn<TData>[];
20
21
  };
21
- export type FormFieldType = "text" | "textarea" | "number" | "date" | "select" | "lookup" | "checkbox" | "toggle";
22
+ export type FormFieldType = "text" | "textarea" | "number" | "date" | "select" | "lookup" | "checkbox" | "toggle" | "detail-rows";
22
23
  export type FormFieldOption = {
23
24
  value: string;
24
25
  label: string;
@@ -33,6 +34,17 @@ export type FormFieldLookupConfig<TData extends Record<string, unknown>> = {
33
34
  dataSource?: "api" | "direct";
34
35
  /** Return a patch to merge into the form when a lookup row is selected. */
35
36
  onDataPatch?: (row: Record<string, unknown>, data: TData) => Partial<TData>;
37
+ /**
38
+ * Form field keys whose values combine into the initial display text on
39
+ * Edit load. Example: `["kode_cabang","branch_name"]` renders
40
+ * "001 KANTOR CABANG BANDUNG" instead of just "001". Typical pairing
41
+ * with an onDataPatch that stashes the picked row's name into a
42
+ * companion field. If any referenced field is empty, the lookup falls
43
+ * back to the field's own primary-key value.
44
+ */
45
+ initialDisplayFields?: (keyof TData)[];
46
+ /** Separator joining initialDisplayFields. Default: " " */
47
+ initialDisplaySeparator?: string;
36
48
  };
37
49
  export type FormFieldNumericConfig<TData extends Record<string, unknown>> = {
38
50
  kind: "currency";
@@ -50,11 +62,51 @@ export type FormFieldNumericConfig<TData extends Record<string, unknown>> = {
50
62
  maxDigits: number;
51
63
  allowedPrefixes?: string[];
52
64
  };
65
+ /**
66
+ * Toggle field state mapping for non-boolean stored values.
67
+ *
68
+ * Example for a "Y" / "N" string column:
69
+ * { type: "toggle", toggle: { valueOn: "Y", valueOff: "N" } }
70
+ *
71
+ * Toggled = (current value === valueOn). Flipping the toggle writes
72
+ * either `valueOn` or `valueOff` into the form data. Defaults to
73
+ * boolean true / false when omitted.
74
+ */
75
+ export type FormFieldToggleConfig = {
76
+ valueOn?: unknown;
77
+ valueOff?: unknown;
78
+ };
79
+ /**
80
+ * Detail-rows config — embeds an inline `<EditableTable>` inside the form for
81
+ * master-detail entry. The field value at `key` must be an array of plain
82
+ * objects (e.g. `details: BucketRow[]`). The renderer reads / writes that
83
+ * array atomically via `onChange({ ...data, [key]: newRows })`.
84
+ *
85
+ * `columns`, `newRowFactory`, `validateRow`, `maxRows` mirror the props of
86
+ * `<EditableTable>` from `@isi-ui7/editable-table`.
87
+ */
88
+ export type DetailRowsConfig = {
89
+ columns: EditableColumnDef[];
90
+ newRowFactory: () => Record<string, unknown>;
91
+ validateRow?: (row: Record<string, unknown>, index: number) => Record<string, string>;
92
+ maxRows?: number;
93
+ };
53
94
  export type FormField<TData extends Record<string, unknown>> = {
54
95
  key: keyof TData;
55
96
  label: ReactNode;
56
97
  type: FormFieldType;
57
98
  span?: number;
99
+ /**
100
+ * Force this field to start a new row even when the current row still
101
+ * has space for its `span`. Useful to break a logical group onto its
102
+ * own line (e.g. a wide picker that should sit alone, or a sub-group
103
+ * header pattern within one section).
104
+ *
105
+ * Implemented via `gridColumnStart: 1` — the CSS grid skips the
106
+ * remaining columns of the previous row and places this field at the
107
+ * left edge of a new row.
108
+ */
109
+ breakBefore?: boolean;
58
110
  /** Static or dynamic readonly. Function receives current mode + form data. */
59
111
  readonly?: boolean | ((mode: FormMode, data: TData) => boolean);
60
112
  /** Static or dynamic visibility. Hidden fields are skipped in validation. */
@@ -69,6 +121,8 @@ export type FormField<TData extends Record<string, unknown>> = {
69
121
  options?: FormFieldOption[];
70
122
  lookup?: FormFieldLookupConfig<TData>;
71
123
  numeric?: FormFieldNumericConfig<TData>;
124
+ toggle?: FormFieldToggleConfig;
125
+ detailRows?: DetailRowsConfig;
72
126
  format?: (value: TData[keyof TData], data: TData) => ReactNode;
73
127
  validation?: FieldValidation<TData>;
74
128
  };
@@ -119,6 +173,11 @@ export type CrudForm<TData extends Record<string, unknown>> = {
119
173
  };
120
174
  /** Form layout density. Defaults to "compact". */
121
175
  density?: Ui7FormDensity;
176
+ /**
177
+ * Desktop form-panel width — "half" | "two-thirds" | "full".
178
+ * Default "two-thirds". Tablet + mobile (<1056px) always stretch full.
179
+ */
180
+ width?: Ui7FormWidth;
122
181
  emptyData: TData;
123
182
  createApiPath: ApiPath;
124
183
  updateApiPath?: ApiPath;
@@ -0,0 +1,118 @@
1
+ import { FormMode, FormSection } from './form-types';
2
+
3
+ export type T = (key: string, fallback?: string) => string;
4
+ export type XUiNumeric = {
5
+ kind: "currency" | "percent" | "integer" | "phone";
6
+ /** currency: name of the sibling field holding the currency code. */
7
+ currencyField?: string;
8
+ precisionByCurrency?: Record<string, number>;
9
+ defaultPrecision?: number;
10
+ /** percent. */
11
+ maxFractionDigits?: number;
12
+ /** phone. */
13
+ minDigits?: number;
14
+ maxDigits?: number;
15
+ allowedPrefixes?: string[];
16
+ };
17
+ export type XUiOption = {
18
+ value: string;
19
+ labelKey?: string;
20
+ label?: string;
21
+ };
22
+ export type XUiWidget = "text" | "textarea" | "number" | "select" | "date" | "toggle" | "lookup" | "detail-rows";
23
+ /** Per-property `x-ui` hint object (also array-level for `detail-rows`). */
24
+ export type XUi = {
25
+ widget?: XUiWidget;
26
+ labelKey?: string;
27
+ label?: string;
28
+ helpKey?: string;
29
+ help?: string;
30
+ /** 1–12 grid width. Default 6. */
31
+ span?: number;
32
+ /** Sort order within an inferred section. */
33
+ order?: number;
34
+ /** Modes in which the field renders read-only, e.g. ["edit"]. */
35
+ readonlyOn?: FormMode[];
36
+ placeholder?: string;
37
+ numeric?: XUiNumeric;
38
+ /** `select` options (overrides enum labels). */
39
+ options?: XUiOption[];
40
+ /** Named dynamic-options source — deferred in Wave C (resolved by FE). */
41
+ optionsRef?: string;
42
+ lookup?: {
43
+ api: string;
44
+ valueField?: string;
45
+ displayFields?: string[];
46
+ };
47
+ toggle?: {
48
+ valueOn?: unknown;
49
+ valueOff?: unknown;
50
+ };
51
+ /** array `detail-rows`: max number of rows. */
52
+ maxRows?: number;
53
+ };
54
+ /** Root-level `x-ui` (layout grouping + density). */
55
+ export type XUiRoot = XUi & {
56
+ layout?: {
57
+ sections?: Array<{
58
+ titleKey?: string;
59
+ title?: string;
60
+ fields: string[];
61
+ }>;
62
+ };
63
+ density?: string;
64
+ };
65
+ export type JSONSchema = {
66
+ type?: string;
67
+ properties?: Record<string, JSONSchema>;
68
+ required?: string[];
69
+ enum?: unknown[];
70
+ minimum?: number;
71
+ maximum?: number;
72
+ minLength?: number;
73
+ maxLength?: number;
74
+ pattern?: string;
75
+ format?: string;
76
+ default?: unknown;
77
+ items?: JSONSchema;
78
+ "x-ui"?: XUiRoot;
79
+ "x-rules"?: XRule[];
80
+ };
81
+ export type XRuleOp = "lte" | "gte" | "lt" | "gt" | "eq" | "required-if";
82
+ export type XRule = {
83
+ op: XRuleOp;
84
+ left: string;
85
+ /** Other field to compare against (lte/gte/lt/gt/eq, or the condition field of required-if). */
86
+ right?: string;
87
+ /** Literal to compare against (eq) or the condition value (required-if). */
88
+ value?: unknown;
89
+ message?: string;
90
+ /** i18n key for the message (preferred over `message`). */
91
+ messageKey?: string;
92
+ };
93
+ type ValueData = Record<string, unknown>;
94
+ export type ValueValidatorFn = (data: ValueData) => Partial<Record<string, string>>;
95
+ /**
96
+ * Build a form-level validator from `x-rules`. Comparison ops skip when either
97
+ * operand is empty/non-numeric (per-field `required` handles emptiness), so the
98
+ * cross-field check only fires on otherwise-complete input. Returns errors keyed
99
+ * by the offending field so the page blocks submit before `startWorkflow`.
100
+ */
101
+ export declare function buildXRulesValidator(rules: XRule[] | undefined, t: T): ValueValidatorFn;
102
+ /**
103
+ * Build the "value" form section(s) + a cross-field validator from a policy7
104
+ * `value_schema`. Pure; no fetch.
105
+ *
106
+ * @example
107
+ * const { sections, validate } = buildValueSections(valueSchema, t);
108
+ * const form: CrudForm<Data> = {
109
+ * ...,
110
+ * layout: { type: "single-page", sections: [scopeSection(t), ...sections] },
111
+ * validate: (data) => ({ ...scopeValidate(data), ...validate(data) }),
112
+ * };
113
+ */
114
+ export declare function buildValueSections(valueSchema: JSONSchema, t: T): {
115
+ sections: FormSection<ValueData>[];
116
+ validate: ValueValidatorFn;
117
+ };
118
+ export {};
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export * from './notifications-bff';
15
15
  export * from './crud-types';
16
16
  export * from './crud-hooks';
17
17
  export * from './form-types';
18
+ export * from './form-value-schema';
18
19
  export * from './i18n';
19
20
  export { proxyBackendPost, proxyQueryRoute } from './data-table/proxy';
20
21
  export type { ProxyBackendPostOptions, ProxyQueryRouteOptions } from './data-table/proxy';
@@ -1,4 +1,4 @@
1
- import { _ as Xa } from "./jspdf.es.min-CpRUhh3E.js";
1
+ import { _ as Xa } from "./jspdf.es.min-Dzm5j8wC.js";
2
2
  var Ke = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {};
3
3
  function Do(a) {
4
4
  return a && a.__esModule && Object.prototype.hasOwnProperty.call(a, "default") ? a.default : a;