ding-react-admin 2.2.1 → 2.3.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ReferenceField.d.ts","sourceRoot":"","sources":["../../../../src/crud/fields/ReferenceField.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,UAAU,EACV,cAAc,EACf,MAAM,UAAU,CAAC;AAOlB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAC/C,cAAc,GAAG;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,wEAAwE;IACxE,aAAa,CAAC,EAAE,CACd,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KACnB,IAAI,CAAC;IACV,4EAA4E;IAC5E,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,mBAAmB,CAAC;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAgIJ,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,IAAI,EACJ,KAAK,EACL,SAAS,EACT,OAAO,EACP,WAAoB,EACpB,WAAkB,EAClB,QAAQ,EACR,KAAK,EACL,MAAM,EACN,UAAU,EACV,QAAQ,EAAE,YAAY,EACtB,SAAS,EACT,UAAU,EACV,aAAa,EACb,IAAW,EACX,YAAY,EACZ,aAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,gBAAuB,EACvB,qBAAqB,EACrB,aAAa,GACd,EAAE,mBAAmB,2CA4CrB"}
1
+ {"version":3,"file":"ReferenceField.d.ts","sourceRoot":"","sources":["../../../../src/crud/fields/ReferenceField.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,UAAU,EACV,cAAc,EACf,MAAM,UAAU,CAAC;AAWlB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAC/C,cAAc,GAAG;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,wEAAwE;IACxE,aAAa,CAAC,EAAE,CACd,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,YAAY,GAAG,SAAS,EAChC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KACnB,IAAI,CAAC;IACV,4EAA4E;IAC5E,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,mBAAmB,CAAC;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAsIJ,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,IAAI,EACJ,KAAK,EACL,SAAS,EACT,OAAO,EACP,WAAoB,EACpB,WAAkB,EAClB,QAAQ,EACR,KAAK,EACL,MAAM,EACN,UAAU,EACV,QAAQ,EAAE,YAAY,EACtB,SAAS,EACT,UAAU,EACV,aAAa,EACb,IAAW,EACX,YAAY,EACZ,aAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,gBAAuB,EACvB,qBAAqB,EACrB,aAAa,GACd,EAAE,mBAAmB,2CA4CrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReferenceManyField.d.ts","sourceRoot":"","sources":["../../../../src/crud/fields/ReferenceManyField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAO5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,uBAAuB,GAAG,eAAe,GACnD,cAAc,GAAG;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,oBAAoB,CAAC,EAAE,mBAAmB,CAAC;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAkHJ,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAI,EACJ,KAAK,EACL,SAAS,EACT,OAAO,EACP,WAAoB,EACpB,WAAkB,EAClB,QAAQ,EACR,KAAK,EACL,MAAM,EACN,UAAiB,EACjB,SAAS,EACT,QAAQ,EAAE,YAAY,EACtB,IAAW,EACX,YAAY,EACZ,aAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,gBAAuB,EACvB,qBAAqB,EACrB,aAAa,GACd,EAAE,uBAAuB,2CAyCzB"}
1
+ {"version":3,"file":"ReferenceManyField.d.ts","sourceRoot":"","sources":["../../../../src/crud/fields/ReferenceManyField.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAW5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,uBAAuB,GAAG,eAAe,GACnD,cAAc,GAAG;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,oBAAoB,CAAC,EAAE,mBAAmB,CAAC;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAuHJ,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAI,EACJ,KAAK,EACL,SAAS,EACT,OAAO,EACP,WAAoB,EACpB,WAAkB,EAClB,QAAQ,EACR,KAAK,EACL,MAAM,EACN,UAAiB,EACjB,SAAS,EACT,QAAQ,EAAE,YAAY,EACtB,IAAW,EACX,YAAY,EACZ,aAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,gBAAuB,EACvB,qBAAqB,EACrB,aAAa,GACd,EAAE,uBAAuB,2CAyCzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReferenceFilter.d.ts","sourceRoot":"","sources":["../../../../src/crud/filters/ReferenceFilter.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM9E,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG;IAChD,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA0BF,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,GACT,EAAE,iBAAiB,QA2BnB;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAChD,cAAc,GAAG;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AA6DJ,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,KAAK,EACL,SAAS,EACT,OAAO,EACP,WAAoB,EACpB,WAAkB,EAClB,QAAQ,EACR,MAAM,EACN,IAAW,EACX,aAAoB,EACpB,qBAAqB,EACrB,aAAa,GACd,EAAE,oBAAoB,QAgDtB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,2CAE9D"}
1
+ {"version":3,"file":"ReferenceFilter.d.ts","sourceRoot":"","sources":["../../../../src/crud/filters/ReferenceFilter.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAU9E,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG;IAChD,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA0BF,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,KAAK,EACL,OAAO,EACP,QAAQ,GACT,EAAE,iBAAiB,QA2BnB;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAChD,cAAc,GAAG;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAwEJ,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,KAAK,EACL,SAAS,EACT,OAAO,EACP,WAAoB,EACpB,WAAkB,EAClB,QAAQ,EACR,MAAM,EACN,IAAW,EACX,aAAoB,EACpB,qBAAqB,EACrB,aAAa,GACd,EAAE,oBAAoB,QAgDtB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,2CAE9D"}
@@ -7,4 +7,11 @@ export declare function collectSelectedRecords(selectedValues: unknown | unknown
7
7
  export declare function resolveOptionLabel(record: Record<string, unknown>, optionLabel: string | ((record: Record<string, unknown>) => string)): string;
8
8
  export declare function recordsToOptions(records: Record<string, unknown>[], optionLabel: string | ((record: Record<string, unknown>) => string), optionValue: string): ChoiceOption[];
9
9
  export declare function mergeOptions(existing: ChoiceOption[], incoming: ChoiceOption[]): ChoiceOption[];
10
+ /** Selected ids that do not yet have a matching option (label still unresolved). */
11
+ export declare function unresolvedSelectedIds(selectedIds: unknown[], knownOptions: ChoiceOption[]): unknown[];
12
+ /**
13
+ * Keep options for currently selected ids when replacing the dropdown list
14
+ * (e.g. closing a lazy select) so selected labels do not flash back to raw ids.
15
+ */
16
+ export declare function keepSelectedOptions(previous: ChoiceOption[], selectedIds: unknown[], embeddedOptions?: ChoiceOption[]): ChoiceOption[];
10
17
  //# sourceMappingURL=choiceSelectionUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"choiceSelectionUtils.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/choiceSelectionUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GAClB,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOlC;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,MAAM,GAAG,SAAS,CAS7B;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,OAAO,EACf,WAAW,EAAE,MAAM,GAClB,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAKrB;AAED,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,OAAO,GAAG,OAAO,EAAE,GAAG,SAAS,EAC/C,WAAW,EAAE,MAAM,GAClB,OAAO,EAAE,CAUX;AAED,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,OAAO,GAAG,OAAO,EAAE,GAAG,SAAS,EAC/C,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,SAAS,EAChF,WAAW,EAAE,MAAM,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CA4B3B;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,GAClE,MAAM,CAGR;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClC,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,EACnE,WAAW,EAAE,MAAM,GAClB,YAAY,EAAE,CAMhB;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EAAE,EACxB,QAAQ,EAAE,YAAY,EAAE,GACvB,YAAY,EAAE,CAKhB"}
1
+ {"version":3,"file":"choiceSelectionUtils.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/choiceSelectionUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GAClB,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOlC;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,MAAM,GAAG,SAAS,CAS7B;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,OAAO,EACf,WAAW,EAAE,MAAM,GAClB,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAKrB;AAED,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,OAAO,GAAG,OAAO,EAAE,GAAG,SAAS,EAC/C,WAAW,EAAE,MAAM,GAClB,OAAO,EAAE,CAUX;AAED,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,OAAO,GAAG,OAAO,EAAE,GAAG,SAAS,EAC/C,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,SAAS,EAChF,WAAW,EAAE,MAAM,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CA4B3B;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,GAClE,MAAM,CAGR;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClC,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,EACnE,WAAW,EAAE,MAAM,GAClB,YAAY,EAAE,CAMhB;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EAAE,EACxB,QAAQ,EAAE,YAAY,EAAE,GACvB,YAAY,EAAE,CAKhB;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,OAAO,EAAE,EACtB,YAAY,EAAE,YAAY,EAAE,GAC3B,OAAO,EAAE,CAIX;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,EAAE,EACxB,WAAW,EAAE,OAAO,EAAE,EACtB,eAAe,GAAE,YAAY,EAAO,GACnC,YAAY,EAAE,CAKhB"}
@@ -0,0 +1,6 @@
1
+ import { SelectProps } from 'antd';
2
+ /** Select loading/disabled while selected labels resolve via getOne. */
3
+ export declare function referenceSelectSelectedProps(listLoading: boolean, selectedLoading: boolean, disabled?: boolean): Pick<SelectProps, "loading" | "disabled">;
4
+ /** Omit the raw id from the Select while the selected label is still loading. */
5
+ export declare function referenceSelectDisplayValue<T>(selectedLoading: boolean, value: T, empty: T): T;
6
+ //# sourceMappingURL=referenceSelectSelectedProps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"referenceSelectSelectedProps.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/referenceSelectSelectedProps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAExC,wEAAwE;AACxE,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,OAAO,EACpB,eAAe,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,OAAO,GACjB,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,UAAU,CAAC,CAK3C;AAED,iFAAiF;AACjF,wBAAgB,2BAA2B,CAAC,CAAC,EAC3C,eAAe,EAAE,OAAO,EACxB,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,CAAC,GACP,CAAC,CAEH"}
@@ -6,8 +6,8 @@ export type ToFormDataOptions = {
6
6
  skipExistingUploadUrls?: boolean;
7
7
  };
8
8
  /**
9
- * Flatten a save payload into `FormData` using bracket notation for nested fields
10
- * and arrays (e.g. `lines[0][label]`, `address[city]`).
9
+ * Flatten a save payload into `FormData` using Django REST Framework HTML form
10
+ * keys for nested fields and arrays (e.g. `lines[0]label`, `address.city`).
11
11
  */
12
12
  export declare function toFormData(data: Record<string, unknown>, options?: ToFormDataOptions): FormData;
13
13
  //# sourceMappingURL=toFormData.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"toFormData.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/toFormData.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAoDF;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,QAAQ,CAWV"}
1
+ {"version":3,"file":"toFormData.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/toFormData.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAiEF;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,QAAQ,CAWV"}
@@ -28,6 +28,7 @@ export type UseChoicesOptions = {
28
28
  export declare function useChoices(loader: ChoicesLoader | undefined, reference: string | undefined, optionLabel?: string | ((record: Record<string, unknown>) => string), optionValue?: string, search?: string, hookOptions?: UseChoicesOptions): {
29
29
  options: ChoiceOption[];
30
30
  loading: boolean;
31
+ selectedLoading: boolean;
31
32
  labelForValue: (value: unknown) => string;
32
33
  labelsForValues: (values: unknown[]) => string;
33
34
  optionForValue: (value: unknown) => ChoiceOption | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"useChoices.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/useChoices.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAY5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,iGAAiG;IACjG,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IACrC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtE;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAyEF,wBAAgB,UAAU,CACxB,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,GAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAU,EAC5E,WAAW,SAAO,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,GAAE,iBAAsB;;;2BAqLzB,OAAO,KAAG,MAAM;8BAQf,OAAO,EAAE,KAAG,MAAM;4BAQnB,OAAO,KAAG,YAAY,GAAG,SAAS;;EAa7C"}
1
+ {"version":3,"file":"useChoices.d.ts","sourceRoot":"","sources":["../../../../src/crud/utils/useChoices.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAc5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,iGAAiG;IACjG,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IACrC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtE;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAyEF,wBAAgB,UAAU,CACxB,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,GAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAU,EAC5E,WAAW,SAAO,EAClB,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,GAAE,iBAAsB;;;;2BA6OzB,OAAO,KAAG,MAAM;8BAQf,OAAO,EAAE,KAAG,MAAM;4BAQnB,OAAO,KAAG,YAAY,GAAG,SAAS;;EAc7C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ding-react-admin",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Composable admin shell (Ant Design + React Router): quick-start AdminApp or build your own layout.",
@@ -11,6 +11,10 @@ import type {
11
11
  import { valueAsId, resolveOptionLabel } from "../utils/choiceSelectionUtils";
12
12
  import { referenceSelectDropdownProps } from "../utils/referenceSelectDropdownProps";
13
13
  import { referenceSelectNotFoundContent } from "../utils/referenceSelectNotFoundContent";
14
+ import {
15
+ referenceSelectDisplayValue,
16
+ referenceSelectSelectedProps,
17
+ } from "../utils/referenceSelectSelectedProps";
14
18
  import { useChoices } from "../utils/useChoices";
15
19
  import { FieldWrapper } from "./FieldWrapper";
16
20
  import { ReferenceInputActions } from "./ReferenceInputActions";
@@ -88,19 +92,25 @@ function ReferenceFieldSelect({
88
92
  const active = dropdownOpen || Boolean(searchText);
89
93
  const selectValue = valueAsId(value, optionValue);
90
94
 
91
- const { options, loading, optionForValue, reload } = useChoices(
92
- choices,
93
- reference,
94
- optionLabel,
95
- optionValue,
96
- search ? searchText : undefined,
97
- {
98
- lazy,
99
- active,
100
- selectedValues: value,
101
- selectedRecords,
102
- fetchSelected,
103
- },
95
+ const { options, loading, selectedLoading, optionForValue, reload } =
96
+ useChoices(
97
+ choices,
98
+ reference,
99
+ optionLabel,
100
+ optionValue,
101
+ search ? searchText : undefined,
102
+ {
103
+ lazy,
104
+ active,
105
+ selectedValues: value,
106
+ selectedRecords,
107
+ fetchSelected,
108
+ },
109
+ );
110
+ const selectState = referenceSelectSelectedProps(
111
+ loading,
112
+ selectedLoading,
113
+ disabled,
104
114
  );
105
115
 
106
116
  const selectOptions = useMemo(
@@ -130,14 +140,14 @@ function ReferenceFieldSelect({
130
140
  const select = (
131
141
  <Select
132
142
  {...referenceSelectDropdownProps({ popupMatchSelectWidth, popupMinWidth })}
133
- value={selectValue}
143
+ value={referenceSelectDisplayValue(selectedLoading, selectValue, undefined)}
134
144
  onChange={(next) => {
135
145
  onChange(next);
136
146
  onValueChange?.(next, optionForValue(next), { name: fieldName });
137
147
  }}
138
148
  options={selectOptions}
139
- loading={loading}
140
- notFoundContent={referenceSelectNotFoundContent(loading)}
149
+ loading={selectState.loading}
150
+ notFoundContent={referenceSelectNotFoundContent(selectState.loading)}
141
151
  showSearch={search}
142
152
  filterOption={search ? false : undefined}
143
153
  onSearch={search ? setSearchText : undefined}
@@ -146,7 +156,7 @@ function ReferenceFieldSelect({
146
156
  if (!open) setSearchText(undefined);
147
157
  }}
148
158
  allowClear={allowClear}
149
- disabled={disabled}
159
+ disabled={selectState.disabled}
150
160
  optionFilterProp="label"
151
161
  style={{ width: "100%", minWidth: 160, ...inputStyle }}
152
162
  />
@@ -165,7 +175,7 @@ function ReferenceFieldSelect({
165
175
  referenceDefaultValues={referenceDefaultValues}
166
176
  referenceModalWidth={referenceModalWidth}
167
177
  selectedId={selectValue}
168
- disabled={disabled}
178
+ disabled={selectState.disabled}
169
179
  onCreated={applyRecord}
170
180
  onUpdated={() => void reload()}
171
181
  />
@@ -5,6 +5,10 @@ import type { BaseSourceProps, FieldRules, ReferenceProps } from "../types";
5
5
  import { valuesAsIds } from "../utils/choiceSelectionUtils";
6
6
  import { referenceSelectDropdownProps } from "../utils/referenceSelectDropdownProps";
7
7
  import { referenceSelectNotFoundContent } from "../utils/referenceSelectNotFoundContent";
8
+ import {
9
+ referenceSelectDisplayValue,
10
+ referenceSelectSelectedProps,
11
+ } from "../utils/referenceSelectSelectedProps";
8
12
  import { useChoices } from "../utils/useChoices";
9
13
  import { FieldWrapper } from "./FieldWrapper";
10
14
  import { ReferenceInputActions } from "./ReferenceInputActions";
@@ -68,7 +72,7 @@ function ReferenceManyFieldSelect({
68
72
  const active = dropdownOpen || Boolean(searchText);
69
73
  const selectValue = valuesAsIds(value, optionValue);
70
74
 
71
- const { options, loading, reload } = useChoices(
75
+ const { options, loading, selectedLoading, reload } = useChoices(
72
76
  choices,
73
77
  reference,
74
78
  optionLabel,
@@ -82,6 +86,11 @@ function ReferenceManyFieldSelect({
82
86
  fetchSelected,
83
87
  },
84
88
  );
89
+ const selectState = referenceSelectSelectedProps(
90
+ loading,
91
+ selectedLoading,
92
+ disabled,
93
+ );
85
94
 
86
95
  const selectOptions = useMemo(
87
96
  () =>
@@ -96,11 +105,11 @@ function ReferenceManyFieldSelect({
96
105
  <Select
97
106
  {...referenceSelectDropdownProps({ popupMatchSelectWidth, popupMinWidth })}
98
107
  mode="multiple"
99
- value={selectValue}
108
+ value={referenceSelectDisplayValue(selectedLoading, selectValue, [])}
100
109
  onChange={onChange}
101
110
  options={selectOptions}
102
- loading={loading}
103
- notFoundContent={referenceSelectNotFoundContent(loading)}
111
+ loading={selectState.loading}
112
+ notFoundContent={referenceSelectNotFoundContent(selectState.loading)}
104
113
  showSearch={search}
105
114
  filterOption={search ? false : undefined}
106
115
  onSearch={search ? setSearchText : undefined}
@@ -109,7 +118,7 @@ function ReferenceManyFieldSelect({
109
118
  if (!open) setSearchText(undefined);
110
119
  }}
111
120
  allowClear={allowClear}
112
- disabled={disabled}
121
+ disabled={selectState.disabled}
113
122
  optionFilterProp="label"
114
123
  style={{ width: "100%" }}
115
124
  />
@@ -127,7 +136,7 @@ function ReferenceManyFieldSelect({
127
136
  referenceTitle={referenceTitle}
128
137
  referenceDefaultValues={referenceDefaultValues}
129
138
  referenceModalWidth={referenceModalWidth}
130
- disabled={disabled}
139
+ disabled={selectState.disabled}
131
140
  onCreated={(record) => {
132
141
  const id = record[optionValue];
133
142
  const current = Array.isArray(selectValue) ? selectValue : [];
@@ -5,6 +5,10 @@ import { useRegisterFilter } from "../context/FilterContext";
5
5
  import { useChoices } from "../utils/useChoices";
6
6
  import { referenceSelectDropdownProps } from "../utils/referenceSelectDropdownProps";
7
7
  import { referenceSelectNotFoundContent } from "../utils/referenceSelectNotFoundContent";
8
+ import {
9
+ referenceSelectDisplayValue,
10
+ referenceSelectSelectedProps,
11
+ } from "../utils/referenceSelectSelectedProps";
8
12
 
9
13
  export type SelectFilterProps = BaseSourceProps & {
10
14
  choices: ChoiceOption[];
@@ -98,7 +102,7 @@ function ReferenceFilterInput({
98
102
  const [dropdownOpen, setDropdownOpen] = useState(false);
99
103
  const active = dropdownOpen || Boolean(searchText);
100
104
 
101
- const { options, loading } = useChoices(
105
+ const { options, loading, selectedLoading } = useChoices(
102
106
  choices,
103
107
  reference,
104
108
  optionLabel,
@@ -106,6 +110,12 @@ function ReferenceFilterInput({
106
110
  search ? searchText : undefined,
107
111
  { lazy, active, selectedValues: value, fetchSelected },
108
112
  );
113
+ const selectState = referenceSelectSelectedProps(loading, selectedLoading);
114
+ const selectValue = value as
115
+ | string
116
+ | number
117
+ | (string | number)[]
118
+ | undefined;
109
119
 
110
120
  return (
111
121
  <Select
@@ -113,14 +123,19 @@ function ReferenceFilterInput({
113
123
  allowClear
114
124
  mode={multiple ? "multiple" : undefined}
115
125
  placeholder={label ?? source}
116
- value={value as string | number | (string | number)[] | undefined}
126
+ value={referenceSelectDisplayValue(
127
+ selectedLoading,
128
+ selectValue,
129
+ (multiple ? [] : undefined) as typeof selectValue,
130
+ )}
117
131
  onChange={onChange}
118
132
  options={options.map((o) => ({
119
133
  label: o.label,
120
134
  value: o.value as string | number,
121
135
  }))}
122
- loading={loading}
123
- notFoundContent={referenceSelectNotFoundContent(loading)}
136
+ loading={selectState.loading}
137
+ disabled={selectState.disabled}
138
+ notFoundContent={referenceSelectNotFoundContent(selectState.loading)}
124
139
  showSearch={search}
125
140
  filterOption={search ? false : undefined}
126
141
  onSearch={search ? setSearchText : undefined}
@@ -2,8 +2,10 @@ import { describe, expect, it } from "vitest";
2
2
  import {
3
3
  collectSelectedRecords,
4
4
  isRecordObject,
5
+ keepSelectedOptions,
5
6
  normalizeSelectedIds,
6
7
  recordsToOptions,
8
+ unresolvedSelectedIds,
7
9
  valueAsId,
8
10
  valuesAsIds,
9
11
  } from "./choiceSelectionUtils";
@@ -42,4 +44,25 @@ describe("choiceSelectionUtils", () => {
42
44
  },
43
45
  ]);
44
46
  });
47
+
48
+ it("finds selected ids that do not yet have options", () => {
49
+ const options = [{ label: "One", value: "1" }];
50
+ expect(unresolvedSelectedIds(["1", "2"], [])).toEqual(["1", "2"]);
51
+ expect(unresolvedSelectedIds(["1", "2"], options)).toEqual(["2"]);
52
+ expect(unresolvedSelectedIds(["1"], options)).toEqual([]);
53
+ expect(unresolvedSelectedIds([], options)).toEqual([]);
54
+ });
55
+
56
+ it("keeps selected options when replacing a lazy dropdown list", () => {
57
+ const previous = [
58
+ { label: "One", value: "1" },
59
+ { label: "Two", value: "2" },
60
+ ];
61
+ const embedded = [{ label: "Embedded", value: "3" }];
62
+ expect(keepSelectedOptions(previous, ["2"], embedded)).toEqual([
63
+ { label: "Embedded", value: "3" },
64
+ { label: "Two", value: "2" },
65
+ ]);
66
+ expect(keepSelectedOptions(previous, [], [])).toEqual([]);
67
+ });
45
68
  });
@@ -114,3 +114,28 @@ export function mergeOptions(
114
114
  for (const option of incoming) byValue.set(option.value, option);
115
115
  return Array.from(byValue.values());
116
116
  }
117
+
118
+ /** Selected ids that do not yet have a matching option (label still unresolved). */
119
+ export function unresolvedSelectedIds(
120
+ selectedIds: unknown[],
121
+ knownOptions: ChoiceOption[],
122
+ ): unknown[] {
123
+ return selectedIds.filter(
124
+ (value) => !knownOptions.some((option) => option.value === value),
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Keep options for currently selected ids when replacing the dropdown list
130
+ * (e.g. closing a lazy select) so selected labels do not flash back to raw ids.
131
+ */
132
+ export function keepSelectedOptions(
133
+ previous: ChoiceOption[],
134
+ selectedIds: unknown[],
135
+ embeddedOptions: ChoiceOption[] = [],
136
+ ): ChoiceOption[] {
137
+ const keep = previous.filter((option) =>
138
+ selectedIds.some((value) => option.value === value),
139
+ );
140
+ return mergeOptions(embeddedOptions, keep);
141
+ }
@@ -0,0 +1,22 @@
1
+ import type { SelectProps } from "antd";
2
+
3
+ /** Select loading/disabled while selected labels resolve via getOne. */
4
+ export function referenceSelectSelectedProps(
5
+ listLoading: boolean,
6
+ selectedLoading: boolean,
7
+ disabled?: boolean,
8
+ ): Pick<SelectProps, "loading" | "disabled"> {
9
+ return {
10
+ loading: listLoading || selectedLoading,
11
+ disabled: Boolean(disabled || selectedLoading),
12
+ };
13
+ }
14
+
15
+ /** Omit the raw id from the Select while the selected label is still loading. */
16
+ export function referenceSelectDisplayValue<T>(
17
+ selectedLoading: boolean,
18
+ value: T,
19
+ empty: T,
20
+ ): T {
21
+ return selectedLoading ? empty : value;
22
+ }
@@ -8,6 +8,14 @@ export type ToFormDataOptions = {
8
8
  skipExistingUploadUrls?: boolean;
9
9
  };
10
10
 
11
+ /** DRF `parse_html_list` expects `lines[0]label`; nested dicts use `address.city`. */
12
+ function nestedObjectKey(parentKey: string, childKey: string): string {
13
+ if (/\[[0-9]+\]$/.test(parentKey)) {
14
+ return `${parentKey}${childKey}`;
15
+ }
16
+ return `${parentKey}.${childKey}`;
17
+ }
18
+
11
19
  function appendFormDataValue(
12
20
  formData: FormData,
13
21
  key: string,
@@ -50,7 +58,12 @@ function appendFormDataValue(
50
58
  for (const [childKey, childValue] of Object.entries(
51
59
  value as Record<string, unknown>,
52
60
  )) {
53
- appendFormDataValue(formData, `${key}[${childKey}]`, childValue, options);
61
+ appendFormDataValue(
62
+ formData,
63
+ nestedObjectKey(key, childKey),
64
+ childValue,
65
+ options,
66
+ );
54
67
  }
55
68
  return;
56
69
  }
@@ -59,8 +72,8 @@ function appendFormDataValue(
59
72
  }
60
73
 
61
74
  /**
62
- * Flatten a save payload into `FormData` using bracket notation for nested fields
63
- * and arrays (e.g. `lines[0][label]`, `address[city]`).
75
+ * Flatten a save payload into `FormData` using Django REST Framework HTML form
76
+ * keys for nested fields and arrays (e.g. `lines[0]label`, `address.city`).
64
77
  */
65
78
  export function toFormData(
66
79
  data: Record<string, unknown>,
@@ -1,13 +1,15 @@
1
- import { useCallback, useEffect, useMemo, useState } from "react";
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { useDataProvider } from "../../context/DataProvider";
3
3
  import type { Identifier } from "../../data/dataProviderTypes";
4
4
  import type { ChoiceOption, ChoicesLoader } from "../types";
5
5
  import {
6
6
  collectSelectedRecords,
7
+ keepSelectedOptions,
7
8
  mergeOptions,
8
9
  normalizeSelectedIds,
9
10
  recordsToOptions,
10
11
  resolveOptionLabel,
12
+ unresolvedSelectedIds,
11
13
  } from "./choiceSelectionUtils";
12
14
 
13
15
  const choicesCache = new Map<string, ChoiceOption[]>();
@@ -174,6 +176,19 @@ export function useChoices(
174
176
  return !choicesCache.has(cacheKey);
175
177
  });
176
178
 
179
+ const [selectedLoading, setSelectedLoading] = useState(() =>
180
+ Boolean(
181
+ fetchSelected &&
182
+ reference &&
183
+ unresolvedSelectedIds(normalizedSelected, embeddedOptions).length,
184
+ ),
185
+ );
186
+
187
+ const optionsRef = useRef(options);
188
+ optionsRef.current = options;
189
+ const normalizedSelectedRef = useRef(normalizedSelected);
190
+ normalizedSelectedRef.current = normalizedSelected;
191
+
177
192
  useEffect(() => {
178
193
  if (!embeddedOptions.length) return;
179
194
  setOptions((prev) => mergeOptions(prev, embeddedOptions));
@@ -192,7 +207,16 @@ export function useChoices(
192
207
  const key = loaderKey(effectiveLoader, search);
193
208
  const cached = choicesCache.get(key);
194
209
  if (cached && !search) {
195
- setOptions(mergeOptions(embeddedOptions, cached));
210
+ setOptions((prev) =>
211
+ mergeOptions(
212
+ keepSelectedOptions(
213
+ prev,
214
+ normalizedSelectedRef.current,
215
+ embeddedOptions,
216
+ ),
217
+ cached,
218
+ ),
219
+ );
196
220
  setLoading(false);
197
221
  return;
198
222
  }
@@ -200,7 +224,13 @@ export function useChoices(
200
224
 
201
225
  setLoading(true);
202
226
  if (lazy) {
203
- setOptions(embeddedOptions);
227
+ setOptions((prev) =>
228
+ keepSelectedOptions(
229
+ prev,
230
+ normalizedSelectedRef.current,
231
+ embeddedOptions,
232
+ ),
233
+ );
204
234
  }
205
235
 
206
236
  try {
@@ -212,12 +242,27 @@ export function useChoices(
212
242
  search,
213
243
  useCache,
214
244
  );
215
- setOptions(mergeOptions(embeddedOptions, result));
245
+ setOptions((prev) =>
246
+ mergeOptions(
247
+ keepSelectedOptions(
248
+ prev,
249
+ normalizedSelectedRef.current,
250
+ embeddedOptions,
251
+ ),
252
+ result,
253
+ ),
254
+ );
216
255
  } catch {
217
256
  if (!normalizedSelected.length && !embeddedOptions.length) {
218
257
  setOptions([]);
219
258
  } else if (lazy) {
220
- setOptions(embeddedOptions);
259
+ setOptions((prev) =>
260
+ keepSelectedOptions(
261
+ prev,
262
+ normalizedSelectedRef.current,
263
+ embeddedOptions,
264
+ ),
265
+ );
221
266
  }
222
267
  } finally {
223
268
  setLoading(false);
@@ -241,20 +286,30 @@ export function useChoices(
241
286
 
242
287
  useEffect(() => {
243
288
  if (lazy && !active && !search) {
244
- setOptions(embeddedOptions);
289
+ setOptions((prev) =>
290
+ keepSelectedOptions(prev, normalizedSelected, embeddedOptions),
291
+ );
245
292
  setLoading(false);
246
293
  }
247
- }, [lazy, active, search, embeddedOptions]);
294
+ }, [lazy, active, search, embeddedOptions, normalizedSelected]);
248
295
 
249
296
  useEffect(() => {
250
- if (!fetchSelected || !reference || !normalizedSelected.length) return;
297
+ if (!fetchSelected || !reference || !normalizedSelected.length) {
298
+ setSelectedLoading(false);
299
+ return;
300
+ }
251
301
 
252
- const idsToFetch = normalizedSelected.filter(
253
- (value) => !embeddedOptions.some((option) => option.value === value),
302
+ const idsToFetch = unresolvedSelectedIds(
303
+ normalizedSelected,
304
+ mergeOptions(embeddedOptions, optionsRef.current),
254
305
  );
255
- if (!idsToFetch.length) return;
306
+ if (!idsToFetch.length) {
307
+ setSelectedLoading(false);
308
+ return;
309
+ }
256
310
 
257
311
  let cancelled = false;
312
+ setSelectedLoading(true);
258
313
 
259
314
  void (async () => {
260
315
  const resolved: ChoiceOption[] = [];
@@ -274,14 +329,17 @@ export function useChoices(
274
329
  });
275
330
  }
276
331
  }
277
- if (cancelled || !resolved.length) return;
278
-
279
- setOptions((prev) => {
280
- const missing = resolved.filter(
281
- (option) => !prev.some((existing) => existing.value === option.value),
282
- );
283
- return missing.length ? mergeOptions(prev, missing) : prev;
284
- });
332
+ if (cancelled) return;
333
+
334
+ if (resolved.length) {
335
+ setOptions((prev) => {
336
+ const missing = resolved.filter(
337
+ (option) => !prev.some((existing) => existing.value === option.value),
338
+ );
339
+ return missing.length ? mergeOptions(prev, missing) : prev;
340
+ });
341
+ }
342
+ setSelectedLoading(false);
285
343
  })();
286
344
 
287
345
  return () => {
@@ -322,6 +380,7 @@ export function useChoices(
322
380
  return {
323
381
  options,
324
382
  loading,
383
+ selectedLoading,
325
384
  labelForValue,
326
385
  labelsForValues,
327
386
  optionForValue,
@@ -102,15 +102,24 @@ describe("toFormData", () => {
102
102
  expect(fd.get("title")).toBe("Hello");
103
103
  });
104
104
 
105
- it("encodes inline rows with bracket notation", () => {
105
+ it("encodes inline rows with Django REST Framework HTML keys", () => {
106
106
  const file = new File(["x"], "line.png", { type: "image/png" });
107
107
  const fd = toFormData({
108
108
  lines: [{ id: 10, label: "A", photo: file }],
109
109
  });
110
110
 
111
- expect(fd.get("lines[0][id]")).toBe("10");
112
- expect(fd.get("lines[0][label]")).toBe("A");
113
- expect(fd.get("lines[0][photo]")).toBe(file);
111
+ expect(fd.get("lines[0]id")).toBe("10");
112
+ expect(fd.get("lines[0]label")).toBe("A");
113
+ expect(fd.get("lines[0]photo")).toBe(file);
114
+ });
115
+
116
+ it("encodes nested objects with dotted keys", () => {
117
+ const fd = toFormData({
118
+ address: { city: "Lahore", zip: 54000 },
119
+ });
120
+
121
+ expect(fd.get("address.city")).toBe("Lahore");
122
+ expect(fd.get("address.zip")).toBe("54000");
114
123
  });
115
124
  });
116
125