@openg2p/registry-widgets 1.0.1 → 1.0.2

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/index.js CHANGED
@@ -3516,6 +3516,39 @@ const sectionValidate = (section, currentSchemaData, dispatch) => {
3516
3516
  return isValid;
3517
3517
  };
3518
3518
 
3519
+ /** Root class on readonly label/value rows; SectionRenderer scopes overflow/ellipsis rules here. */
3520
+ const READONLY_VALUE_ROW_ROOT_CLASSES = [
3521
+ 'TextDisplayWidget',
3522
+ 'TextAreaDisplayWidget',
3523
+ 'SelectDisplayWidget',
3524
+ 'PhoneDisplayWidget',
3525
+ 'NumberDisplayWidget',
3526
+ 'CurrencyDisplayWidget',
3527
+ 'RadioDisplayWidget',
3528
+ 'DateDisplayWidget',
3529
+ 'DateTimeDisplayWidget',
3530
+ 'CheckboxDisplayWidget',
3531
+ 'BooleanDisplayWidget',
3532
+ 'FileDisplayWidget',
3533
+ 'DisplayFieldWidget',
3534
+ ];
3535
+ /** Rows whose value is one line in .flex-1 > .text-gray-900 (ellipsis; full string via title on the element). */
3536
+ const READONLY_SINGLE_LINE_VALUE_ROW_CLASSES = [
3537
+ 'TextDisplayWidget',
3538
+ 'SelectDisplayWidget',
3539
+ 'PhoneDisplayWidget',
3540
+ 'NumberDisplayWidget',
3541
+ 'CurrencyDisplayWidget',
3542
+ 'RadioDisplayWidget',
3543
+ 'DateDisplayWidget',
3544
+ 'DateTimeDisplayWidget',
3545
+ 'CheckboxDisplayWidget',
3546
+ 'BooleanDisplayWidget',
3547
+ 'DisplayFieldWidget',
3548
+ ];
3549
+ function scopedClassSelectors(sectionClassId, classNames) {
3550
+ return classNames.map((c) => `.${sectionClassId} .${c}`).join(',\n ');
3551
+ }
3519
3552
  /**
3520
3553
  * Renders a section with its panels
3521
3554
  *
@@ -3580,6 +3613,9 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
3580
3613
  const sectionId = sectionToRender['section-id'];
3581
3614
  const gridId = `section-panels-${sectionId}`;
3582
3615
  const sectionClassId = `section-${sectionId}`;
3616
+ const readonlyValueRowRootsCss = React.useMemo(() => scopedClassSelectors(sectionClassId, READONLY_VALUE_ROW_ROOT_CLASSES), [sectionClassId]);
3617
+ const readonlyValueRowFlex1Css = React.useMemo(() => READONLY_VALUE_ROW_ROOT_CLASSES.map((c) => `.${sectionClassId} .${c} > .flex-1`).join(',\n '), [sectionClassId]);
3618
+ const readonlySingleLineValueTextCss = React.useMemo(() => READONLY_SINGLE_LINE_VALUE_ROW_CLASSES.map((c) => `.${sectionClassId} .${c} > .flex-1 > .text-gray-900`).join(',\n '), [sectionClassId]);
3583
3619
  // IntakeForm mode: accordion expand/collapse state (supports toggle)
3584
3620
  const [standaloneExpanded, setStandaloneExpanded] = React.useState(true); // For sectionIndex undefined (standalone use)
3585
3621
  const isExpandedFromContainer = typeof sectionIndex === 'number' && expandedSectionIndex === sectionIndex;
@@ -4061,12 +4097,24 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4061
4097
  });
4062
4098
  }
4063
4099
  if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
4100
+ let profileImage = null;
4101
+ for (const record of newSchemaData) {
4102
+ if (typeof record === 'object' && record !== null) {
4103
+ for (const [key, value] of Object.entries(record)) {
4104
+ if (value instanceof File) {
4105
+ profileImage = value;
4106
+ record[key] = '';
4107
+ }
4108
+ }
4109
+ }
4110
+ }
4064
4111
  try {
4065
4112
  const sectionchanges = {
4066
4113
  section_id: dbSectionId ?? originalSection['section-id'],
4067
4114
  section_register_id: sectionRegisterId,
4068
4115
  records: [...newSchemaData],
4069
- files: [...sectionFiles]
4116
+ files: [...sectionFiles],
4117
+ ...(profileImage ? { image: profileImage } : {}),
4070
4118
  };
4071
4119
  await onSectionSave(sectionchanges);
4072
4120
  }
@@ -4109,12 +4157,24 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4109
4157
  });
4110
4158
  }
4111
4159
  if (JSON.stringify(oldSchemaData) !== JSON.stringify(newSchemaData)) {
4160
+ let profileImage = null;
4161
+ for (const record of newSchemaData) {
4162
+ if (typeof record === 'object' && record !== null) {
4163
+ for (const [key, value] of Object.entries(record)) {
4164
+ if (value instanceof File) {
4165
+ profileImage = value;
4166
+ record[key] = '';
4167
+ }
4168
+ }
4169
+ }
4170
+ }
4112
4171
  try {
4113
4172
  await onSectionSave({
4114
4173
  section_id: dbSectionId ?? originalSection['section-id'],
4115
4174
  section_register_id: sectionRegisterId,
4116
4175
  records: [...newSchemaData],
4117
4176
  files: [...sectionFiles],
4177
+ ...(profileImage ? { image: profileImage } : {}),
4118
4178
  });
4119
4179
  }
4120
4180
  catch (error) {
@@ -4185,20 +4245,27 @@ const SectionRenderer = ({ section, dataSourceRequestHandler: propDataSourceRequ
4185
4245
  white-space: nowrap !important;
4186
4246
  }
4187
4247
  /* Readonly: prevent flex row from overflowing panel */
4188
- .${sectionClassId} .TextDisplayWidget {
4248
+ ${readonlyValueRowRootsCss} {
4189
4249
  min-width: 0 !important;
4190
4250
  overflow: hidden !important;
4191
4251
  }
4192
- .${sectionClassId} .TextDisplayWidget > .flex-1 {
4252
+ ${readonlyValueRowFlex1Css} {
4193
4253
  min-width: 0 !important;
4194
4254
  overflow: hidden !important;
4195
4255
  }
4196
- /* Readonly value text truncation */
4197
- .${sectionClassId} .TextDisplayWidget > .flex-1 > .text-gray-900 {
4256
+ /* Readonly value: single-line ellipsis; full value via title on the value node */
4257
+ ${readonlySingleLineValueTextCss} {
4198
4258
  overflow: hidden;
4199
4259
  text-overflow: ellipsis;
4200
4260
  white-space: nowrap;
4201
4261
  }
4262
+ /* Readonly textarea: break unbroken long tokens; title on pre keeps full text on hover */
4263
+ .${sectionClassId} .TextAreaDisplayWidget > .flex-1 > pre {
4264
+ min-width: 0;
4265
+ max-width: 100%;
4266
+ overflow-wrap: anywhere;
4267
+ word-break: break-word;
4268
+ }
4202
4269
 
4203
4270
  /* Only apply fixed height when in edit mode */
4204
4271
  .${sectionClassId}[data-edit-mode="true"] {
@@ -8088,10 +8155,10 @@ const DisplayWidget = ({ config }) => {
8088
8155
  const label = translateConfig(widgetConfig['widget-label']);
8089
8156
  // If no label, render as paragraph text
8090
8157
  if (!label || label.trim() === '') {
8091
- return (jsxRuntimeExports.jsx("div", { className: "mb-3 text-base text-gray-700", title: String(displayValue ?? ''), children: displayValue }));
8158
+ return (jsxRuntimeExports.jsx("div", { className: "DisplayFieldWidget mb-3 min-w-0 w-full overflow-hidden text-ellipsis whitespace-nowrap text-base text-gray-700", title: String(displayValue ?? ''), children: displayValue }));
8092
8159
  }
8093
- // With label, render as key-value pair
8094
- return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] }), jsxRuntimeExports.jsx("div", { className: "flex-1 text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue })] }));
8160
+ // With label, render as key-value pair (structure matches other readonly widgets for SectionRenderer ellipsis)
8161
+ return (jsxRuntimeExports.jsxs("div", { className: "mb-[10px] DisplayFieldWidget flex flex-col sm:flex-row sm:items-start", children: [jsxRuntimeExports.jsxs("div", { className: "text-base text-gray-600 font-medium md:min-w-[120px] sm:pr-4 mb-1 sm:mb-0", style: { fontFamily: 'Roboto, sans-serif' }, title: label, children: [label, ":"] }), jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntimeExports.jsx("div", { className: "text-base text-gray-900 font-medium", title: String(displayValue ?? ''), children: displayValue }) })] }));
8095
8162
  };
8096
8163
 
8097
8164
  const TableCellSelect = ({ config, value, onValueChange }) => {
@@ -9059,7 +9126,18 @@ const HeaderSectionWidget = ({ config }) => {
9059
9126
  result = searchIn(schemaData);
9060
9127
  return result;
9061
9128
  }, [paths, values, schemaData]);
9062
- const imageUrl = findValue('image') || null;
9129
+ const imageVal = findValue('image');
9130
+ const imageUrlVal = findValue('imageUrl');
9131
+ const [previewUrl, setPreviewUrl] = React.useState(null);
9132
+ React.useEffect(() => {
9133
+ if (imageVal instanceof File) {
9134
+ const url = URL.createObjectURL(imageVal);
9135
+ setPreviewUrl(url);
9136
+ return () => URL.revokeObjectURL(url);
9137
+ }
9138
+ setPreviewUrl(null);
9139
+ }, [imageVal]);
9140
+ const displayImageUrl = previewUrl || (typeof imageUrlVal === 'string' && imageUrlVal ? imageUrlVal : null);
9063
9141
  const displayName = findValue('name') || '';
9064
9142
  const functionalId = findValue('functionalId') || '';
9065
9143
  const statusValue = findValue('status') || '';
@@ -9092,6 +9170,18 @@ const HeaderSectionWidget = ({ config }) => {
9092
9170
  return opt ? opt.label : String(statusValue);
9093
9171
  }, [statusValue, statusOptions]);
9094
9172
  const statusColor = statusColors[String(statusValue).toLowerCase()] || '#6B7280';
9173
+ // ── Image edit helpers ───────────────────────────────────────
9174
+ const fileInputRef = React.useRef(null);
9175
+ const handleImageUpload = React.useCallback((e) => {
9176
+ const file = e.target.files?.[0];
9177
+ if (!file)
9178
+ return;
9179
+ updateFieldValue('image', file);
9180
+ e.target.value = '';
9181
+ }, [updateFieldValue]);
9182
+ const handleImageDelete = React.useCallback(() => {
9183
+ updateFieldValue('image', '');
9184
+ }, [updateFieldValue]);
9095
9185
  // ── Scoped class for CSS isolation ────────────────────────────
9096
9186
  const cls = `header-section-widget-${widgetConfig['widget-id']}`;
9097
9187
  // ── RENDER ────────────────────────────────────────────────────
@@ -9152,6 +9242,56 @@ const HeaderSectionWidget = ({ config }) => {
9152
9242
  border-radius: 8px;
9153
9243
  }
9154
9244
 
9245
+ .${cls} .hdr-avatar-wrapper {
9246
+ position: relative;
9247
+ width: ${imageSize}px;
9248
+ height: ${imageSize}px;
9249
+ flex-shrink: 0;
9250
+ }
9251
+
9252
+ .${cls} .hdr-avatar-overlay {
9253
+ position: absolute;
9254
+ inset: 0;
9255
+ border-radius: 8px;
9256
+ background: rgba(0, 0, 0, 0.55);
9257
+ display: flex;
9258
+ flex-direction: column;
9259
+ align-items: center;
9260
+ justify-content: center;
9261
+ gap: 6px;
9262
+ opacity: 0;
9263
+ transition: opacity 0.2s;
9264
+ }
9265
+
9266
+ .${cls} .hdr-avatar-wrapper:hover .hdr-avatar-overlay {
9267
+ opacity: 1;
9268
+ }
9269
+
9270
+ .${cls} .hdr-avatar-action {
9271
+ display: flex;
9272
+ align-items: center;
9273
+ gap: 5px;
9274
+ padding: 5px 14px;
9275
+ border: none;
9276
+ border-radius: 4px;
9277
+ background: rgba(255, 255, 255, 0.92);
9278
+ color: #374151;
9279
+ font-size: 0.7rem;
9280
+ font-weight: 500;
9281
+ cursor: pointer;
9282
+ font-family: Roboto, sans-serif;
9283
+ transition: background 0.15s;
9284
+ white-space: nowrap;
9285
+ }
9286
+
9287
+ .${cls} .hdr-avatar-action:hover {
9288
+ background: #fff;
9289
+ }
9290
+
9291
+ .${cls} .hdr-avatar-action--delete {
9292
+ color: #DC2626;
9293
+ }
9294
+
9155
9295
  .${cls} .hdr-info {
9156
9296
  display: flex;
9157
9297
  flex-direction: column;
@@ -9256,13 +9396,13 @@ const HeaderSectionWidget = ({ config }) => {
9256
9396
  min-width: 0;
9257
9397
  }
9258
9398
  }
9259
- ` }), jsxRuntimeExports.jsxs("div", { className: cls, children: [jsxRuntimeExports.jsxs("div", { className: "hdr-left", children: [jsxRuntimeExports.jsxs("div", { children: [imageUrl ? (jsxRuntimeExports.jsx("img", { src: imageUrl, alt: displayName || 'Profile', className: "hdr-avatar", onError: (e) => {
9399
+ ` }), jsxRuntimeExports.jsxs("div", { className: cls, children: [jsxRuntimeExports.jsxs("div", { className: "hdr-left", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-wrapper", children: [displayImageUrl ? (jsxRuntimeExports.jsx("img", { src: displayImageUrl, alt: displayName || 'Profile', className: "hdr-avatar", onError: (e) => {
9260
9400
  e.target.style.display = 'none';
9261
9401
  const placeholder = e.target
9262
9402
  .parentElement?.querySelector('.hdr-avatar-placeholder');
9263
9403
  if (placeholder)
9264
9404
  placeholder.style.display = 'flex';
9265
- } })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: imageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: statusReason || '-' })) : (jsxRuntimeExports.jsx("input", { type: "text", className: "hdr-input", value: statusReason, placeholder: getLabel('enterReason'), onChange: (e) => updateFieldValue('statusReason', e.target.value) }))] })] })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-right", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedAt || '-' })] })] })] })] }));
9405
+ } })) : null, jsxRuntimeExports.jsx("div", { className: "hdr-avatar-placeholder", style: { display: displayImageUrl ? 'none' : 'flex' }, children: jsxRuntimeExports.jsx("img", { src: img$f, alt: "Profile Placeholder" }) }), !isReadonly && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "hdr-avatar-overlay", children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action", onClick: () => fileInputRef.current?.click(), children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntimeExports.jsx("polyline", { points: "17 8 12 3 7 8" }), jsxRuntimeExports.jsx("line", { x1: "12", y1: "3", x2: "12", y2: "15" })] }), "Upload"] }), jsxRuntimeExports.jsxs("button", { type: "button", className: "hdr-avatar-action hdr-avatar-action--delete", onClick: handleImageDelete, children: [jsxRuntimeExports.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntimeExports.jsx("polyline", { points: "3 6 5 6 21 6" }), jsxRuntimeExports.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }), "Delete"] })] }), jsxRuntimeExports.jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: 'none' }, onChange: handleImageUpload })] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-info", children: [displayName && jsxRuntimeExports.jsx("div", { className: "hdr-name", children: displayName }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('functionalId'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: functionalId || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsx("span", { className: "hdr-field-label", children: getLabel('status') }), isReadonly ? (statusLabel ? (jsxRuntimeExports.jsx("span", { className: "hdr-status-badge", style: { backgroundColor: statusColor }, children: statusLabel })) : (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: "-" }))) : (jsxRuntimeExports.jsxs("select", { className: "hdr-select", value: statusValue, onChange: (e) => updateFieldValue('status', e.target.value), children: [jsxRuntimeExports.jsx("option", { value: "", children: getLabel('select') }), statusOptions.map((opt) => (jsxRuntimeExports.jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }))] }), jsxRuntimeExports.jsxs("div", { className: "hdr-field-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-field-label", children: [getLabel('statusReason'), " :"] }), isReadonly ? (jsxRuntimeExports.jsx("span", { className: "hdr-field-value", children: statusReason || '-' })) : (jsxRuntimeExports.jsx("input", { type: "text", className: "hdr-input", value: statusReason, placeholder: getLabel('enterReason'), onChange: (e) => updateFieldValue('statusReason', e.target.value) }))] })] })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-right", children: [jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('createdAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: createdAt || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedBy'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedBy || '-' })] }), jsxRuntimeExports.jsxs("div", { className: "hdr-meta-row", children: [jsxRuntimeExports.jsxs("span", { className: "hdr-meta-label", children: [getLabel('lastApprovedAt'), " :"] }), jsxRuntimeExports.jsx("span", { className: "hdr-meta-value", children: lastApprovedAt || '-' })] })] })] })] }));
9266
9406
  };
9267
9407
 
9268
9408
  /**