@ioca/react 1.5.15 → 1.5.16

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.
Files changed (36) hide show
  1. package/README.md +5 -0
  2. package/lib/cjs/components/datagrid/datagrid.js +1 -1
  3. package/lib/cjs/components/datagrid/datagrid.js.map +1 -1
  4. package/lib/cjs/components/drawer/drawer.js +4 -1
  5. package/lib/cjs/components/drawer/drawer.js.map +1 -1
  6. package/lib/cjs/components/form/field.js +9 -5
  7. package/lib/cjs/components/form/field.js.map +1 -1
  8. package/lib/cjs/components/form/form.js +16 -8
  9. package/lib/cjs/components/form/form.js.map +1 -1
  10. package/lib/cjs/components/form/useConfig.js +1 -1
  11. package/lib/cjs/components/form/useConfig.js.map +1 -1
  12. package/lib/cjs/components/form/useForm.js +43 -58
  13. package/lib/cjs/components/form/useForm.js.map +1 -1
  14. package/lib/cjs/components/form/utils.js +33 -0
  15. package/lib/cjs/components/form/utils.js.map +1 -0
  16. package/lib/css/colors.css +1 -1
  17. package/lib/css/index.css +1 -1
  18. package/lib/css/index.css.map +1 -1
  19. package/lib/css/tokens.css +11 -10
  20. package/lib/es/components/datagrid/datagrid.js +1 -1
  21. package/lib/es/components/datagrid/datagrid.js.map +1 -1
  22. package/lib/es/components/drawer/drawer.js +4 -1
  23. package/lib/es/components/drawer/drawer.js.map +1 -1
  24. package/lib/es/components/form/field.js +9 -5
  25. package/lib/es/components/form/field.js.map +1 -1
  26. package/lib/es/components/form/form.js +17 -9
  27. package/lib/es/components/form/form.js.map +1 -1
  28. package/lib/es/components/form/useConfig.js +1 -1
  29. package/lib/es/components/form/useConfig.js.map +1 -1
  30. package/lib/es/components/form/useForm.js +43 -58
  31. package/lib/es/components/form/useForm.js.map +1 -1
  32. package/lib/es/components/form/utils.js +29 -0
  33. package/lib/es/components/form/utils.js.map +1 -0
  34. package/lib/index.js +100 -75
  35. package/lib/types/components/form/useForm.d.ts +3 -3
  36. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import classNames from 'classnames';
3
- import { debounce, uid, throttle } from 'radash';
3
+ import { debounce, uid, crush, throttle } from 'radash';
4
4
  import { useState, useRef, useEffect, useCallback, useMemo, Children, cloneElement, createElement, isValidElement, memo, Fragment as Fragment$1, useTransition, forwardRef, useLayoutEffect, useContext, createContext, useImperativeHandle } from 'react';
5
5
  import { SkipPreviousRound, CloseRound, MinusRound, PlusRound, InboxTwotone, UndoRound, RedoRound, FormatBoldRound, FormatItalicRound, FormatUnderlinedRound, StrikethroughSRound, ClearAllRound, PlayArrowRound, PauseRound, StopRound, VolumeDownRound, VolumeOffRound, FullscreenRound, FullscreenExitRound, FeedOutlined, AspectRatioRound, OpenInNewRound, FileDownloadOutlined, RotateRightRound, RotateLeftRound, KeyboardArrowLeftRound, KeyboardArrowRightRound, KeyboardDoubleArrowUpRound, SyncAltRound, VisibilityRound, VisibilityOffRound, MoreHorizRound, SearchRound, CheckRound, UnfoldMoreRound, CalendarMonthTwotone, AccessTimeRound, InfoOutlined, KeyboardArrowDownRound, MoveToInboxTwotone, OutboxTwotone, FilePresentOutlined, DriveFolderUploadOutlined } from '@ricons/material';
6
6
  import { createRoot } from 'react-dom/client';
@@ -1478,7 +1478,7 @@ function VirtualDatagrid(props) {
1478
1478
  }
1479
1479
 
1480
1480
  const Datagrid = (props) => {
1481
- const { data = [], columns = [], border, striped, header = true, resizable, cellPadding = ".5em", cellEllipsis, empty = jsx(Empty, {}), loading, height = "unset", style, className, rowKey, virtual, renderLoading = () => (jsx(Loading, { size: '1.5em', className: 'color-3', absolute: true })), onCellClick, onRowClick, onCellDoubleClick, onHeaderClick, onSort, onScroll, onResize, } = props;
1481
+ const { data = [], columns = [], border, striped, header = true, resizable, cellPadding = ".5em", cellEllipsis, empty = jsx(Empty, {}), loading, height = "unset", style, className, rowKey, virtual, renderLoading = () => jsx(Loading, { className: "color-3", absolute: true }), onCellClick, onRowClick, onCellDoubleClick, onHeaderClick, onSort, onScroll, onResize, } = props;
1482
1482
  const container = useRef(null);
1483
1483
  const wrapRef = useRef(null);
1484
1484
  const state = useReactive({
@@ -1749,9 +1749,12 @@ function Drawer(props) {
1749
1749
  });
1750
1750
  if (!state.show)
1751
1751
  return null;
1752
+ const container = typeof document === "undefined" ? null : document.body;
1753
+ if (!container)
1754
+ return null;
1752
1755
  return createPortal(jsx("div", { className: classNames("i-backdrop-drawer", className, {
1753
1756
  "i-active": state.active,
1754
- }), onClick: handleBackdropClick, ...restProps, children: jsxs("div", { className: classNames("i-drawer", `i-drawer-${position}`), onClick: (e) => e.stopPropagation(), children: [header && (jsxs("header", { className: 'i-drawer-header', children: [header, !hideCloseButton && (jsx(Helpericon, { className: 'i-drawer-close', onClick: handleHide }))] })), jsx("div", { className: 'i-drawer-content', children: children }), footer && jsx("div", { className: 'i-drawer-footer', children: footer })] }) }), document.body);
1757
+ }), onClick: handleBackdropClick, ...restProps, children: jsxs("div", { className: classNames("i-drawer", `i-drawer-${position}`), onClick: (e) => e.stopPropagation(), children: [header && (jsxs("header", { className: 'i-drawer-header', children: [header, !hideCloseButton && (jsx(Helpericon, { className: 'i-drawer-close', onClick: handleHide }))] })), jsx("div", { className: 'i-drawer-content', children: children }), footer && jsx("div", { className: 'i-drawer-footer', children: footer })] }) }), container);
1755
1758
  }
1756
1759
 
1757
1760
  const Item$4 = (props) => {
@@ -3010,10 +3013,10 @@ function Field(props) {
3010
3013
  useEffect(() => {
3011
3014
  if (!name)
3012
3015
  return;
3013
- PubSub.subscribe(`${id}:set:${name}`, (evt, v) => {
3016
+ PubSub.subscribe(`${id}:set:${name}`, (_evt, v) => {
3014
3017
  setFieldValue(v);
3015
3018
  });
3016
- PubSub.subscribe(`${id}:invalid:${name}`, (evt, v) => {
3019
+ PubSub.subscribe(`${id}:invalid:${name}`, (_evt, v) => {
3017
3020
  if (v?.value !== undefined)
3018
3021
  setFieldValue(v.value);
3019
3022
  if (v?.status)
@@ -3022,121 +3025,136 @@ function Field(props) {
3022
3025
  setFieldMessage(v.message);
3023
3026
  });
3024
3027
  Promise.resolve().then(() => {
3025
- form.set(name, form.cacheData[name] ?? undefined);
3028
+ if (name in form.cacheData) {
3029
+ form.set(name, form.cacheData[name]);
3030
+ }
3026
3031
  });
3027
3032
  return () => {
3028
3033
  PubSub.unsubscribe(`${id}:set:${name}`);
3029
3034
  PubSub.unsubscribe(`${id}:invalid:${name}`);
3030
- form.delete(name);
3035
+ if (name && !name.includes(".")) {
3036
+ form.data[name] = undefined;
3037
+ }
3031
3038
  };
3032
- }, [name, children]);
3039
+ }, [name]);
3033
3040
  if (!name)
3034
3041
  return children;
3035
3042
  return hijackChildren;
3036
3043
  }
3037
3044
 
3045
+ function getDeep(obj, path) {
3046
+ if (!path.includes("."))
3047
+ return obj[path];
3048
+ return path.split(".").reduce((acc, key) => (acc != null ? acc[key] : undefined), obj);
3049
+ }
3050
+ function setDeep(obj, path, value) {
3051
+ const parts = path.split(".");
3052
+ let current = obj;
3053
+ for (let i = 0; i < parts.length - 1; i++) {
3054
+ const key = parts[i];
3055
+ if (current[key] == null || typeof current[key] !== "object") {
3056
+ current[key] = {};
3057
+ }
3058
+ current = current[key];
3059
+ }
3060
+ current[parts[parts.length - 1]] = value;
3061
+ }
3062
+ function deleteDeep(obj, path) {
3063
+ const parts = path.split(".");
3064
+ const parent = parts
3065
+ .slice(0, -1)
3066
+ .reduce((acc, key) => (acc != null ? acc[key] : undefined), obj);
3067
+ if (parent != null) {
3068
+ delete parent[parts[parts.length - 1]];
3069
+ }
3070
+ }
3071
+
3038
3072
  class IFormInstance {
3039
3073
  id;
3040
3074
  data = {};
3041
3075
  cacheData = {};
3042
- rules = {};
3076
+ rules;
3043
3077
  constructor() {
3044
3078
  this.id = uid(8);
3045
3079
  this.data = {};
3046
3080
  }
3047
3081
  get(field) {
3048
- return field ? this.data[field] : this.data;
3082
+ return field ? getDeep(this.data, field) : this.data;
3049
3083
  }
3050
3084
  set(field, value) {
3051
3085
  const id = this.id;
3052
3086
  if (!this.data)
3053
3087
  return;
3054
3088
  if (typeof field === "string") {
3055
- this.data[field] = value;
3089
+ if (field.includes(".")) {
3090
+ const parts = field.split(".");
3091
+ for (let i = 1; i < parts.length; i++) {
3092
+ const ancestor = parts.slice(0, i).join(".");
3093
+ if (ancestor in this.data) {
3094
+ console.warn(`[ioca-form] Field "${field}" conflicts with "${ancestor}". ` +
3095
+ "Nested representation in form.get() may be inconsistent.");
3096
+ }
3097
+ }
3098
+ setDeep(this.data, field, value);
3099
+ }
3100
+ else {
3101
+ this.data[field] = value;
3102
+ }
3056
3103
  this.cacheData[field] = value;
3057
3104
  PubSub.publish(`${id}:set:${field}`, value);
3058
3105
  return;
3059
3106
  }
3060
- Object.keys(field).map((name) => {
3061
- this.data[name] = field[name];
3107
+ Object.keys(field).forEach((name) => {
3108
+ if (name.includes("."))
3109
+ setDeep(this.data, name, field[name]);
3110
+ else
3111
+ this.data[name] = field[name];
3062
3112
  this.cacheData[name] = field[name];
3063
3113
  PubSub.publish(`${id}:set:${name}`, field[name]);
3064
3114
  });
3065
3115
  }
3066
3116
  delete(field) {
3067
- delete this.data[field];
3117
+ delete this.cacheData[field];
3118
+ if (field.includes("."))
3119
+ deleteDeep(this.data, field);
3120
+ else
3121
+ delete this.data[field];
3068
3122
  }
3069
3123
  clear() {
3070
3124
  if (!this.data)
3071
3125
  return;
3126
+ const names = Object.keys(this.cacheData);
3072
3127
  this.cacheData = {};
3073
- Object.keys(this.data).map((name) => {
3128
+ names.forEach((name) => {
3129
+ if (name.includes("."))
3130
+ deleteDeep(this.data, name);
3131
+ else
3132
+ this.data[name] = undefined;
3074
3133
  PubSub.publish(`${this.id}:set:${name}`, undefined);
3075
- this.data[name] = undefined;
3076
3134
  });
3077
3135
  }
3078
3136
  async validate(field) {
3079
3137
  const { id, rules, data } = this;
3080
3138
  if (!rules)
3081
3139
  return data;
3082
- if (field) {
3083
- const o = rules[field];
3084
- const rule = {
3085
- validator: (v) => Array.isArray(v)
3086
- ? v.length > 0
3087
- : ![undefined, null, ""].includes(v),
3088
- message: undefined,
3089
- };
3090
- if (typeof o === "function") {
3091
- rule.validator = o;
3092
- }
3093
- else if (o === true) {
3094
- rule.validator = (v) => ![undefined, null, ""].includes(v);
3095
- rule.message = "required";
3096
- }
3097
- else {
3098
- Object.assign(rule, o);
3099
- }
3100
- const isValid = rule.validator?.(data[field], this);
3101
- if (typeof isValid === "string") {
3102
- rule.message = isValid;
3103
- }
3104
- if (isValid !== true) {
3105
- PubSub.publish(`${id}:invalid:${field}`, {
3106
- message: rule.message,
3107
- status: "error",
3108
- });
3109
- return false;
3110
- }
3111
- PubSub.publish(`${id}:invalid:${name}`, {
3112
- message: null,
3113
- status: "normal",
3114
- });
3115
- return true;
3116
- }
3140
+ const names = field ? [field] : Object.keys(this.cacheData);
3117
3141
  let isAllValid = true;
3118
- Object.keys(data).map((name) => {
3142
+ names.forEach((name) => {
3119
3143
  const o = rules[name];
3120
- if (o === undefined)
3144
+ if (!field && o === undefined)
3121
3145
  return;
3122
3146
  const rule = {
3123
- validator: (v) => (Array.isArray(v) ? v.length > 0 : !!v),
3124
- message: undefined,
3147
+ validator: (v) => Array.isArray(v) ? v.length > 0 : ![undefined, null, ""].includes(v),
3125
3148
  };
3126
- if (typeof o === "function") {
3149
+ if (typeof o === "function")
3127
3150
  rule.validator = o;
3128
- }
3129
- else if (o === true) {
3130
- rule.validator = (v) => ![undefined, null, ""].includes(v);
3151
+ else if (o === true)
3131
3152
  rule.message = "required";
3132
- }
3133
- else {
3153
+ else if (o)
3134
3154
  Object.assign(rule, o);
3135
- }
3136
- const isValid = rule.validator?.(data[name], this);
3137
- if (typeof isValid === "string") {
3155
+ const isValid = rule.validator?.(getDeep(data, name), this);
3156
+ if (typeof isValid === "string")
3138
3157
  rule.message = isValid;
3139
- }
3140
3158
  if (isValid !== true) {
3141
3159
  PubSub.publish(`${id}:invalid:${name}`, {
3142
3160
  message: rule.message,
@@ -3151,7 +3169,7 @@ class IFormInstance {
3151
3169
  });
3152
3170
  }
3153
3171
  });
3154
- return isAllValid ? Promise.resolve(data) : false;
3172
+ return field ? isAllValid : isAllValid ? data : false;
3155
3173
  }
3156
3174
  }
3157
3175
  function useForm(form) {
@@ -3172,7 +3190,7 @@ function useConfig(configs, formProps) {
3172
3190
  };
3173
3191
  const node = useMemo(() => {
3174
3192
  return (jsx(Form, { ...formProps, onChange: handleChange, form: form, children: configs.map((config) => {
3175
- const { name, label, required, component: El, componentProps = {}, colspan = 1, render, shouldUpdate, shouldRender, } = config;
3193
+ const { name, label, required, component: El, componentProps = {}, colspan = 1, render, shouldRender, } = config;
3176
3194
  const { className, style } = componentProps;
3177
3195
  if (shouldRender && !shouldRender(values, form)) {
3178
3196
  return jsx(Fragment$1, {}, name);
@@ -3207,20 +3225,27 @@ const Form = (props) => {
3207
3225
  }
3208
3226
  return columns;
3209
3227
  }, [columns]);
3228
+ const initialAppliedRef = useRef(false);
3210
3229
  useEffect(() => {
3211
- Object.assign(form, {
3212
- data: { ...initialValues },
3213
- rules,
3214
- });
3215
- }, [form]);
3230
+ if (!initialAppliedRef.current && initialValues) {
3231
+ const flat = crush(initialValues);
3232
+ Object.keys(flat).forEach((key) => {
3233
+ form.set(key, flat[key]);
3234
+ });
3235
+ initialAppliedRef.current = true;
3236
+ }
3237
+ if (rules) {
3238
+ form.rules = rules;
3239
+ }
3240
+ }, [initialValues, rules, form]);
3216
3241
  useEffect(() => {
3217
- PubSub.subscribe(`${form.id}:change`, (evt, v) => {
3242
+ const token = PubSub.subscribe(`${form.id}:change`, (_evt, v) => {
3218
3243
  onChange?.(v.name, v.value);
3219
3244
  });
3220
3245
  return () => {
3221
- PubSub.unsubscribe(`${form.id}:change`);
3246
+ PubSub.unsubscribe(token);
3222
3247
  };
3223
- }, []);
3248
+ }, [form.id, onChange]);
3224
3249
  return (jsx(Context, { value: form, children: jsx("form", { style: {
3225
3250
  ...style,
3226
3251
  width,
@@ -1,14 +1,14 @@
1
- import { IForm } from './type.js';
1
+ import { TValidator, TRule } from './type.js';
2
2
 
3
3
  declare class IFormInstance {
4
4
  readonly id?: string;
5
5
  data: Record<string, any>;
6
6
  cacheData: Record<string, any>;
7
- rules?: Pick<IForm, "rules">;
7
+ rules?: Record<string, boolean | TValidator | TRule>;
8
8
  constructor();
9
9
  get(field?: string): any;
10
10
  set(field: any, value?: any): void;
11
- delete(field: any): void;
11
+ delete(field: string): void;
12
12
  clear(): void;
13
13
  validate(field?: string): Promise<boolean | Record<string, any>>;
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ioca/react",
3
- "version": "1.5.15",
3
+ "version": "1.5.16",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite",