@openg2p/registry-widgets 1.1.3 → 1.1.4-dev.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.
Files changed (36) hide show
  1. package/dist/components/SectionRenderer/utils/executeSectionSave.d.ts.map +1 -1
  2. package/dist/components/SectionRenderer.d.ts +61 -0
  3. package/dist/components/SectionRenderer.d.ts.map +1 -0
  4. package/dist/hooks/useGeoHierarchy.d.ts +1 -0
  5. package/dist/hooks/useGeoHierarchy.d.ts.map +1 -1
  6. package/dist/hooks/useGeoWidgetCascade.d.ts +12 -0
  7. package/dist/hooks/useGeoWidgetCascade.d.ts.map +1 -0
  8. package/dist/hooks/useWidgetTranslation.d.ts +16 -0
  9. package/dist/hooks/useWidgetTranslation.d.ts.map +1 -0
  10. package/dist/i18n/config.d.ts +21 -0
  11. package/dist/i18n/config.d.ts.map +1 -0
  12. package/dist/index.d.ts +9 -1
  13. package/dist/index.esm.js +141 -25
  14. package/dist/index.esm.js.map +1 -1
  15. package/dist/index.js +141 -25
  16. package/dist/index.js.map +1 -1
  17. package/dist/types/index.d.ts +9 -1
  18. package/dist/types/index.d.ts.map +1 -1
  19. package/dist/utils/buildSectionChanges.d.ts +11 -0
  20. package/dist/utils/buildSectionChanges.d.ts.map +1 -0
  21. package/dist/utils/geoHierarchy.d.ts +13 -0
  22. package/dist/utils/geoHierarchy.d.ts.map +1 -1
  23. package/dist/utils/schemaNamespace.d.ts +12 -0
  24. package/dist/utils/schemaNamespace.d.ts.map +1 -0
  25. package/dist/widgets/ArrayWidget.d.ts +29 -0
  26. package/dist/widgets/ArrayWidget.d.ts.map +1 -0
  27. package/dist/widgets/CurrencyInputWidget.d.ts +24 -0
  28. package/dist/widgets/CurrencyInputWidget.d.ts.map +1 -0
  29. package/dist/widgets/DocsWidget.d.ts.map +1 -1
  30. package/dist/widgets/GeoHierarchyWidget.d.ts.map +1 -1
  31. package/dist/widgets/HeaderSectionWidget.d.ts.map +1 -1
  32. package/dist/widgets/IterableAccordionWidget.d.ts +30 -0
  33. package/dist/widgets/IterableAccordionWidget.d.ts.map +1 -0
  34. package/dist/widgets/SimpleTableWidget.d.ts +32 -0
  35. package/dist/widgets/SimpleTableWidget.d.ts.map +1 -0
  36. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2990,6 +2990,15 @@ const extractProfileImage = (records) => {
2990
2990
  profileImage = value;
2991
2991
  copy[key] = '';
2992
2992
  }
2993
+ else if (isSerializedFile(value)) {
2994
+ try {
2995
+ profileImage = deserializeFile(value);
2996
+ copy[key] = '';
2997
+ }
2998
+ catch (err) {
2999
+ console.error('Failed to deserialize profile image:', err);
3000
+ }
3001
+ }
2993
3002
  }
2994
3003
  return copy;
2995
3004
  });
@@ -8697,10 +8706,6 @@ const DEFAULT_LABELS = {
8697
8706
  lastApprovedBy: 'Last Approved by',
8698
8707
  lastApprovedAt: 'Last Approved at',
8699
8708
  };
8700
- // Options are loaded in both view and edit modes because view mode
8701
- // needs them to resolve display labels (e.g. "active" → "Active").
8702
- // For API data sources, loading is deferred to edit mode to avoid
8703
- // unnecessary network calls when only labels are needed.
8704
8709
  function useFieldDataSource(fieldKey, fieldConfig, isReadonly) {
8705
8710
  const [options, setOptions] = React.useState([]);
8706
8711
  const { dataSourceRequestHandler, schemaData } = useWidgetContext();
@@ -8804,11 +8809,15 @@ const HeaderSectionWidget = ({ config }) => {
8804
8809
  const imageUrlVal = findValue('imageUrl');
8805
8810
  const [previewUrl, setPreviewUrl] = React.useState(null);
8806
8811
  React.useEffect(() => {
8807
- if (imageUrlVal instanceof File) {
8812
+ if (isFile(imageUrlVal)) {
8808
8813
  const url = URL.createObjectURL(imageUrlVal);
8809
8814
  setPreviewUrl(url);
8810
8815
  return () => URL.revokeObjectURL(url);
8811
8816
  }
8817
+ if (isSerializedFile(imageUrlVal) && imageUrlVal.data) {
8818
+ setPreviewUrl(`data:${imageUrlVal.type};base64,${imageUrlVal.data}`);
8819
+ return;
8820
+ }
8812
8821
  setPreviewUrl(null);
8813
8822
  }, [imageUrlVal]);
8814
8823
  const displayImageUrl = previewUrl || (typeof imageUrlVal === 'string' && imageUrlVal ? imageUrlVal : null);
@@ -8912,16 +8921,25 @@ const HeaderSectionWidget = ({ config }) => {
8912
8921
  }, [statusValue, statusOptions]);
8913
8922
  const statusColor = statusColors[String(statusValue).toLowerCase()] || 'var(--owt-color-text-muted, #6B7280)';
8914
8923
  const fileInputRef = React.useRef(null);
8915
- const handleImageUpload = React.useCallback((e) => {
8924
+ const handleImageUpload = React.useCallback(async (e) => {
8916
8925
  const file = e.target.files?.[0];
8917
8926
  if (!file || !paths.imageUrl)
8918
8927
  return;
8919
- dispatch(setValues(setValueByPath({ ...values }, paths.imageUrl, file)));
8920
- e.target.value = '';
8928
+ try {
8929
+ const serialized = await serializeFile(file);
8930
+ dispatch(setValues(setValueByPath({ ...values }, paths.imageUrl, serialized)));
8931
+ }
8932
+ catch (err) {
8933
+ console.error('[HeaderSectionWidget] Error serializing image:', err);
8934
+ }
8935
+ finally {
8936
+ e.target.value = '';
8937
+ }
8921
8938
  }, [paths.imageUrl, values, dispatch]);
8922
8939
  const handleImageDelete = React.useCallback(() => {
8923
8940
  if (!paths.imageUrl)
8924
8941
  return;
8942
+ setPreviewUrl(null);
8925
8943
  dispatch(setValues(setValueByPath({ ...values }, paths.imageUrl, null)));
8926
8944
  }, [paths.imageUrl, values, dispatch]);
8927
8945
  const cls = `header-section-widget-${widgetConfig['widget-id']}`;
@@ -10598,6 +10616,33 @@ function buildOrderedLevels(flat) {
10598
10616
  return ordered;
10599
10617
  }
10600
10618
  const GEO_SECTION_COLUMN_SLOTS = 3;
10619
+ /**
10620
+ * Suggested panel/widget column span from hierarchy depth:
10621
+ * ≤3 levels → 1, 4–5 → 2, ≥6 → 3.
10622
+ */
10623
+ function suggestedGeoColumnSpan(levelCount) {
10624
+ if (levelCount <= 0) {
10625
+ return 1;
10626
+ }
10627
+ if (levelCount <= 3) {
10628
+ return 1;
10629
+ }
10630
+ if (levelCount < 6) {
10631
+ return 2;
10632
+ }
10633
+ return GEO_SECTION_COLUMN_SLOTS;
10634
+ }
10635
+ /** Clamp span to 1…GEO_SECTION_COLUMN_SLOTS and at most the level count. */
10636
+ function resolveGeoColumnSpan(levelCount, explicitSpan) {
10637
+ const preferred = explicitSpan !== undefined && explicitSpan !== null
10638
+ ? explicitSpan
10639
+ : suggestedGeoColumnSpan(levelCount);
10640
+ const clamped = Math.min(GEO_SECTION_COLUMN_SLOTS, Math.max(1, Math.floor(preferred)));
10641
+ if (levelCount <= 0) {
10642
+ return clamped;
10643
+ }
10644
+ return Math.min(clamped, levelCount);
10645
+ }
10601
10646
  /** Slice ordered levels into section columns per counts, e.g. [3, 2, 0]. */
10602
10647
  function distributeLevelsToColumns(levels, columnCounts) {
10603
10648
  const columns = columnCounts.map(() => []);
@@ -10628,19 +10673,22 @@ function resolveGeoLevelColumns(levels, layout) {
10628
10673
  const columnCounts = layout.distribution === 'fixed'
10629
10674
  ? padColumnCounts(layout.columns, GEO_SECTION_COLUMN_SLOTS)
10630
10675
  : layout.columns;
10676
+ const columns = distributeLevelsToColumns(levels, columnCounts);
10677
+ const occupied = columns.filter((column) => column.length > 0).length;
10631
10678
  return {
10632
10679
  columnCounts,
10633
- columns: distributeLevelsToColumns(levels, columnCounts),
10680
+ columns,
10681
+ columnSpan: resolveGeoColumnSpan(levels.length, layout.columnSpan ?? occupied),
10634
10682
  };
10635
10683
  }
10636
- // Default (no explicit layout): fill top-to-bottom across up to
10637
- // GEO_SECTION_COLUMN_SLOTS columns, each holding a contiguous block of levels
10638
- // so the order stays correct when columns stack on small screens.
10639
- const columnCount = Math.min(GEO_SECTION_COLUMN_SLOTS, Math.max(levels.length, 1));
10640
- const columns = distributeLevelsContiguous(levels, columnCount);
10684
+ // Distribute top-to-bottom across the allotted column span so order stays
10685
+ // correct when columns stack on small screens.
10686
+ const columnSpan = resolveGeoColumnSpan(levels.length, layout?.columnSpan);
10687
+ const columns = distributeLevelsContiguous(levels, columnSpan);
10641
10688
  return {
10642
10689
  columnCounts: columns.map((column) => column.length),
10643
10690
  columns,
10691
+ columnSpan,
10644
10692
  };
10645
10693
  }
10646
10694
  function padColumnCounts(counts, slotCount) {
@@ -10783,6 +10831,42 @@ function getDeepestSelectedValue(orderedLevels, selectedValues) {
10783
10831
  }
10784
10832
  return deepest;
10785
10833
  }
10834
+ /** Build geo_code_hierarchy_json document from current selections (for save payload). */
10835
+ function buildHierarchyJson(orderedLevels, selectedValues, options, resolvedLabels = {}) {
10836
+ const hierarchy = [];
10837
+ for (const level of orderedLevels) {
10838
+ const levelValueId = selectedValues[level.level_id];
10839
+ if (!levelValueId) {
10840
+ break;
10841
+ }
10842
+ const optionLabel = options[level.level_id]?.find((item) => item.value === levelValueId)?.label;
10843
+ const mnemonic = optionLabel || resolvedLabels[levelValueId] || levelValueId;
10844
+ hierarchy.push({
10845
+ level_id: level.level_id,
10846
+ level: level.level_mnemonic,
10847
+ level_mnemonic: level.level_mnemonic,
10848
+ level_value_id: levelValueId,
10849
+ level_value_mnemonic: mnemonic,
10850
+ });
10851
+ }
10852
+ if (hierarchy.length === 0) {
10853
+ return null;
10854
+ }
10855
+ return {
10856
+ hierarchy,
10857
+ lowest_level_value_id: hierarchy[hierarchy.length - 1].level_value_id,
10858
+ };
10859
+ }
10860
+ /** Preserve string vs object storage shape used by the existing hierarchy field. */
10861
+ function formatHierarchyForPersist(document, previous) {
10862
+ if (document === null) {
10863
+ return typeof previous === 'string' ? '' : null;
10864
+ }
10865
+ if (typeof previous === 'string') {
10866
+ return JSON.stringify(document);
10867
+ }
10868
+ return document;
10869
+ }
10786
10870
  function clearDescendants(orderedLevels, fromIndex, selectedValues, options) {
10787
10871
  const nextSelected = { ...selectedValues };
10788
10872
  const nextOptions = { ...options };
@@ -10858,7 +10942,10 @@ function useGeoHierarchy({ config }) {
10858
10942
  const geoLayout = config['widget-geo-layout'];
10859
10943
  const hierarchyJsonPath = React.useMemo(() => resolveHierarchyPath(config), [config]);
10860
10944
  const dataPath = React.useMemo(() => resolveDataPath(config), [config]);
10861
- /** Read-only base: geo_code_hierarchy_json is never written — schema first, then store. */
10945
+ /**
10946
+ * Approved hierarchy for hydrate/display: schema first, then store.
10947
+ * Edits persist draft hierarchy into store values for save; schema keeps approved.
10948
+ */
10862
10949
  const baseHierarchyJson = React.useMemo(() => {
10863
10950
  if (!hierarchyJsonPath) {
10864
10951
  return null;
@@ -10958,17 +11045,23 @@ function useGeoHierarchy({ config }) {
10958
11045
  });
10959
11046
  return transformGeoValueOptions(payload);
10960
11047
  }, [fetchRawValues]);
10961
- /** Write only geo_lowest_level_value_id never geo_code_hierarchy_json. */
10962
- const persistDeepestValue = React.useCallback((nextSelectedValues, orderedLevels) => {
11048
+ /** Persist leaf id + current hierarchy structure for save (schema approved value stays preferred for read). */
11049
+ const persistDeepestValue = React.useCallback((nextSelectedValues, orderedLevels, nextOptions, nextResolvedLabels) => {
10963
11050
  if (initializingRef.current || hydratingRef.current || isReadonly) {
10964
11051
  return;
10965
11052
  }
10966
11053
  const deepest = getDeepestSelectedValue(orderedLevels, nextSelectedValues);
10967
11054
  selfPersistedValueRef.current = deepest ? String(deepest) : '';
11055
+ const hierarchyDocument = buildHierarchyJson(orderedLevels, nextSelectedValues, nextOptions, nextResolvedLabels);
11056
+ const hierarchyPayload = formatHierarchyForPersist(hierarchyDocument, baseHierarchyRef.current);
10968
11057
  const rawDataPath = config['widget-data-path'];
10969
11058
  const nextValue = deepest ?? null;
10970
11059
  if (rawDataPath && typeof rawDataPath === 'object') {
10971
- base.onChange({ value: nextValue });
11060
+ const payload = { value: nextValue };
11061
+ if ('hierarchy' in rawDataPath) {
11062
+ payload.hierarchy = hierarchyPayload;
11063
+ }
11064
+ base.onChange(payload);
10972
11065
  }
10973
11066
  else {
10974
11067
  base.onChange(nextValue);
@@ -11110,9 +11203,7 @@ function useGeoHierarchy({ config }) {
11110
11203
  nextSelectedValues = cleared.selectedValues;
11111
11204
  setSelectedValues(nextSelectedValues);
11112
11205
  setOptions(cleared.options);
11113
- if (nextValue || Object.values(nextSelectedValues).some((value) => value)) {
11114
- persistDeepestValue(nextSelectedValues, levels);
11115
- }
11206
+ persistDeepestValue(nextSelectedValues, levels, cleared.options, resolvedLabels);
11116
11207
  if (!nextValue || levelIndex >= levels.length - 1) {
11117
11208
  return;
11118
11209
  }
@@ -11124,9 +11215,17 @@ function useGeoHierarchy({ config }) {
11124
11215
  const message = error instanceof Error ? error.message : 'Failed to load child geo level values';
11125
11216
  setGeoError(message);
11126
11217
  }
11127
- }, [isReadonly, levels, selectedValues, options, persistDeepestValue, loadOptionsForLevel]);
11218
+ }, [
11219
+ isReadonly,
11220
+ levels,
11221
+ selectedValues,
11222
+ options,
11223
+ resolvedLabels,
11224
+ persistDeepestValue,
11225
+ loadOptionsForLevel,
11226
+ ]);
11128
11227
  const readonlyPath = React.useMemo(() => buildReadonlyPath(levels, selectedValues, options, resolvedLabels), [levels, selectedValues, options, resolvedLabels]);
11129
- const { columnCounts, columns: levelColumns } = React.useMemo(() => resolveGeoLevelColumns(levels, geoLayout), [levels, geoLayout]);
11228
+ const { columnCounts, columns: levelColumns, columnSpan } = React.useMemo(() => resolveGeoLevelColumns(levels, geoLayout), [levels, geoLayout]);
11130
11229
  const visibleColumns = React.useMemo(() => {
11131
11230
  const columnIndex = geoLayout?.columnIndex;
11132
11231
  if (columnIndex === undefined || columnIndex === null) {
@@ -11144,6 +11243,7 @@ function useGeoHierarchy({ config }) {
11144
11243
  options,
11145
11244
  resolvedLabels,
11146
11245
  columnCounts,
11246
+ columnSpan,
11147
11247
  visibleColumns,
11148
11248
  loadingLevels,
11149
11249
  loadingLevelId,
@@ -11213,11 +11313,12 @@ const GeoHierarchyWidget = ({ config }) => {
11213
11313
  isLevelEnabled,
11214
11314
  formatLevelLabel,
11215
11315
  };
11316
+ const layoutColumnCount = Math.max(visibleColumns.length, 1);
11216
11317
  const content = visibleColumns.length <= 1 ? (renderLevelRows({
11217
11318
  ...rowProps,
11218
11319
  columnLevels: visibleColumns[0]?.levels ?? levels,
11219
11320
  })) : (jsxRuntimeExports.jsx("div", { className: "flex flex-col lg:grid w-full", style: {
11220
- gridTemplateColumns: `repeat(${visibleColumns.length}, minmax(200px, 1fr))`,
11321
+ gridTemplateColumns: `repeat(${layoutColumnCount}, minmax(200px, 1fr))`,
11221
11322
  }, children: visibleColumns.map((column, position) => {
11222
11323
  const isLast = position === visibleColumns.length - 1;
11223
11324
  const columnClassName = [
@@ -11345,7 +11446,22 @@ const DocsWidget = ({ config }) => {
11345
11446
  fileInputRefs.current[docKey] = el;
11346
11447
  } })] })), hasFile && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("button", { type: "button", onClick: (e) => handlePreview(file, e), className: "inline-flex items-center px-3 py-1 rounded-md border border-gray-300 text-sm font-medium text-gray-900 bg-gray-50 focus:outline-none", title: fileName, children: t?.('common.view') ?? 'View' }), jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleRemove(docKey), className: "inline-flex items-center px-3 py-1 rounded-md border border-gray-300 text-sm font-medium text-gray-900 bg-gray-50 focus:outline-none", title: "Remove", children: t?.('common.remove') ?? 'Remove' })] }))] }) })] }) }, docKey));
11347
11448
  };
11348
- return (jsxRuntimeExports.jsxs("div", { className: isReadonly ? 'DocsDisplayWidget mb-[10px]' : 'DocsWidget mb-[10px]', children: [jsxRuntimeExports.jsx("div", { className: "flex flex-col lg:grid gap-x-8 gap-y-0", style: { gridTemplateColumns: `repeat(${docColumns.length || 1}, minmax(0, 1fr))` }, children: docColumns.map((column, columnIndex) => (jsxRuntimeExports.jsx("div", { className: "flex flex-col", children: column.map((doc) => renderSlot(doc)) }, `docs-column-${columnIndex}`))) }), jsxRuntimeExports.jsx(FilePreviewModal, { file: previewFile, isOpen: isPreviewOpen && !!previewFile, onClose: () => {
11449
+ return (jsxRuntimeExports.jsxs("div", { className: isReadonly ? 'DocsDisplayWidget mb-[10px]' : 'DocsWidget mb-[10px]', children: [jsxRuntimeExports.jsx("div", { className: "flex flex-col lg:grid w-full", style: { gridTemplateColumns: `repeat(${docColumns.length || 1}, minmax(0, 1fr))` }, children: docColumns.map((column, columnIndex) => {
11450
+ const isLast = columnIndex === docColumns.length - 1;
11451
+ const columnClassName = [
11452
+ 'flex flex-col min-w-0 relative',
11453
+ columnIndex > 0 ? 'lg:pl-10' : '',
11454
+ isLast ? '' : 'lg:pr-10',
11455
+ ]
11456
+ .filter(Boolean)
11457
+ .join(' ');
11458
+ return (jsxRuntimeExports.jsxs("div", { className: columnClassName, children: [!isLast && (jsxRuntimeExports.jsx("div", { className: "hidden lg:block absolute right-0 top-0 w-px", style: {
11459
+ bottom: '5px',
11460
+ backgroundColor: isReadonly
11461
+ ? 'var(--owt-panel-divider-color, #C4C4C4)'
11462
+ : 'var(--owt-color-primary, #F5BB1A)',
11463
+ } })), column.map((doc) => renderSlot(doc))] }, `docs-column-${columnIndex}`));
11464
+ }) }), jsxRuntimeExports.jsx(FilePreviewModal, { file: previewFile, isOpen: isPreviewOpen && !!previewFile, onClose: () => {
11349
11465
  setIsPreviewOpen(false);
11350
11466
  setPreviewFile(null);
11351
11467
  } })] }));