eru-grid 0.0.55 → 0.0.56

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.
@@ -1163,6 +1163,30 @@ function parseCellDate(value, pattern, keepTime = false) {
1163
1163
  * the interactive cell's job, and a composite that tried to host those would be
1164
1164
  * a layout engine. A column needing that is a `page` cell.
1165
1165
  */
1166
+ /**
1167
+ * A status field with its `open_status` / `close_status` lists flattened into
1168
+ * the `options` array the status control actually reads for its colours.
1169
+ *
1170
+ * Shared rather than done at the call site: the status cell has always built
1171
+ * this, and a composite part handed the raw field instead rendered every pill
1172
+ * in the neutral fallback — the colour lives on the option, and without the
1173
+ * flattened list there is no option to match the value against.
1174
+ */
1175
+ function toStatusOptionsField(field) {
1176
+ if (!field || normalizeDatatype(field.datatype) !== 'status')
1177
+ return null;
1178
+ const combined = [...(field.open_status || []), ...(field.close_status || [])];
1179
+ return {
1180
+ ...field,
1181
+ options: combined.map((status) => ({
1182
+ name: status?.name,
1183
+ color: status?.color || '',
1184
+ df: status?.df || false,
1185
+ })),
1186
+ open_status: field.open_status || [],
1187
+ close_status: field.close_status || [],
1188
+ };
1189
+ }
1166
1190
  function formatCellValue(value, field) {
1167
1191
  if (value === null || value === undefined || value === '')
1168
1192
  return '';
@@ -11165,11 +11189,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
11165
11189
  * Several of the row's own columns rendered as one cell — a name over an email,
11166
11190
  * an amount over a date.
11167
11191
  *
11168
- * View-only, and text-only, by design. Each part is formatted by its OWN
11169
- * field's rules (`formatCellValue`), so a currency part keeps its symbol and
11170
- * decimals and a date part keeps its pattern; the composite itself decides only
11171
- * arrangement and emphasis. Editing stays on the underlying columns, which are
11192
+ * View-only by design: editing stays on the underlying columns, which are
11172
11193
  * usually hidden once composed.
11194
+ *
11195
+ * A part whose datatype has a visual form of its own — a rating, a progress
11196
+ * bar, a status pill, tags, a priority, people, a checkbox — is drawn with that
11197
+ * control rather than as text, since "4" and "75" tell a reader far less than
11198
+ * four stars and a bar. Everything else is formatted by its OWN field's rules
11199
+ * (`formatCellValue`), so a currency part keeps its symbol and decimals and a
11200
+ * date part keeps its pattern — wrapping those in a control would render the
11201
+ * same string in a heavier box. The composite itself decides only arrangement
11202
+ * and emphasis.
11173
11203
  */
11174
11204
  class CompositeComponent {
11175
11205
  row = input(null, ...(ngDevMode ? [{ debugName: "row" }] : []));
@@ -11186,6 +11216,25 @@ class CompositeComponent {
11186
11216
  }, ...(ngDevMode ? [{ debugName: "labelPosition" }] : []));
11187
11217
  primaryCss = computed(() => cellTextStyleToCss(this.column()?.cell_style), ...(ngDevMode ? [{ debugName: "primaryCss" }] : []));
11188
11218
  secondaryCss = computed(() => cellTextStyleToCss(this.column()?.cell_style_secondary ?? DEFAULT_SECONDARY_STYLE), ...(ngDevMode ? [{ debugName: "secondaryCss" }] : []));
11219
+ /**
11220
+ * The third part and any beyond it.
11221
+ *
11222
+ * Falls back to the secondary style when unset, so every composite authored
11223
+ * before this existed keeps the look it had — three parts sharing one style
11224
+ * is precisely what this is for, not what it breaks.
11225
+ */
11226
+ tertiaryCss = computed(() => cellTextStyleToCss(this.column()?.cell_style_tertiary
11227
+ ?? this.column()?.cell_style_secondary
11228
+ ?? DEFAULT_SECONDARY_STYLE), ...(ngDevMode ? [{ debugName: "tertiaryCss" }] : []));
11229
+ /**
11230
+ * Primary, secondary, then tertiary for the rest — keyed on the part's
11231
+ * configured slot, never on where it ended up after empty parts were dropped.
11232
+ */
11233
+ cssForSlot(slot) {
11234
+ if (slot === 0)
11235
+ return this.primaryCss();
11236
+ return slot === 1 ? this.secondaryCss() : this.tertiaryCss();
11237
+ }
11189
11238
  /**
11190
11239
  * Falls back to the composite column itself when `composite_fields` is unset
11191
11240
  * or names nothing that exists, so a column switched to `composite` before it
@@ -11200,7 +11249,8 @@ class CompositeComponent {
11200
11249
  const row = this.row();
11201
11250
  const labels = column.composite_labels || [];
11202
11251
  const parts = names.map((name, index) => {
11203
- const field = byName.get(name);
11252
+ const field = byName.get(name) ?? null;
11253
+ const value = readRowValue(row, name);
11204
11254
  // Blank means no label for THIS part, never the field's own label as a
11205
11255
  // fallback: a composite is usually built from columns whose labels are
11206
11256
  // what the header already says, so falling back printed "Upload Date"
@@ -11209,15 +11259,98 @@ class CompositeComponent {
11209
11259
  return {
11210
11260
  name,
11211
11261
  label: String(labels[index] ?? '').trim(),
11212
- text: formatCellValue(readRowValue(row, name), field),
11262
+ text: formatCellValue(value, CompositeComponent.withRowSymbol(field, row)),
11263
+ field,
11264
+ value,
11265
+ numeric: CompositeComponent.isNumeric(field),
11266
+ render: CompositeComponent.selfDrawnDatatype(field),
11267
+ slot: index,
11213
11268
  };
11214
11269
  });
11215
- const populated = parts.filter(part => part.text !== '');
11270
+ // A drawn part counts as populated even with an empty formatted string: an
11271
+ // unrated row still shows its empty stars, and a 0% bar is a real reading.
11272
+ const populated = parts.filter(part => part.text !== '' || part.render !== '');
11216
11273
  if (populated.length > 0)
11217
11274
  return populated;
11218
- const own = formatCellValue(readRowValue(row, column.name), column);
11219
- return own === '' ? [] : [{ name: column.name, label: column.label, text: own }];
11275
+ const own = formatCellValue(readRowValue(row, column.name), CompositeComponent.withRowSymbol(column, row));
11276
+ return own === '' ? [] : [{
11277
+ name: column.name,
11278
+ label: column.label,
11279
+ text: own,
11280
+ numeric: CompositeComponent.isNumeric(column),
11281
+ field: column,
11282
+ value: readRowValue(row, column.name),
11283
+ render: '',
11284
+ slot: 0,
11285
+ }];
11220
11286
  }, ...(ngDevMode ? [{ debugName: "parts" }] : []));
11287
+ /**
11288
+ * Datatypes drawn with their own control instead of as text.
11289
+ *
11290
+ * The test is "does this type carry meaning a string cannot" — stars, a bar,
11291
+ * a coloured pill, chips, avatars, a tick. A currency or a date is already
11292
+ * fully said by `formatCellValue`, so it stays text.
11293
+ */
11294
+ static SELF_DRAWN = new Set([
11295
+ 'rating', 'progress', 'status', 'tag', 'priority', 'people', 'checkbox',
11296
+ ]);
11297
+ /**
11298
+ * The field with this ROW's currency symbol resolved onto it.
11299
+ *
11300
+ * `formatCellValue` only knows the column's static `symbol`, but a
11301
+ * multi-currency column names a sibling column in `symbol_field` and takes
11302
+ * the symbol from each record — which is how the currency CELL renders it
11303
+ * (it is handed the row for exactly this). Without it a composed currency
11304
+ * part lost its symbol and read as a bare number.
11305
+ */
11306
+ static withRowSymbol(field, row) {
11307
+ const symbolField = String(field?.symbol_field || '').trim();
11308
+ if (!field || !symbolField)
11309
+ return field;
11310
+ const raw = readRowValue(row, symbolField);
11311
+ const symbol = raw === null || raw === undefined ? '' : String(raw).trim();
11312
+ return symbol ? { ...field, symbol } : field;
11313
+ }
11314
+ /** Figures are right-aligned by their own cell, so a composed one is too. */
11315
+ static isNumeric(field) {
11316
+ const datatype = effectiveValueDatatype(field ?? undefined);
11317
+ return datatype === 'currency' || datatype === 'number';
11318
+ }
11319
+ static selfDrawnDatatype(field) {
11320
+ const datatype = normalizeDatatype(field?.datatype ?? '');
11321
+ return CompositeComponent.SELF_DRAWN.has(datatype) ? datatype : '';
11322
+ }
11323
+ /**
11324
+ * A status part needs its option colours flattened the same way the status
11325
+ * CELL does, or every pill renders in the neutral fallback.
11326
+ */
11327
+ statusConfig(field) {
11328
+ return toStatusOptionsField(field);
11329
+ }
11330
+ /** Rating takes a shape of its own rather than the raw Field. */
11331
+ ratingConfig(field) {
11332
+ const config = field;
11333
+ return {
11334
+ startValue: config?.start_value || 1,
11335
+ endValue: config?.end_value || 5,
11336
+ emojiValue: config?.emoji_value || 'star',
11337
+ };
11338
+ }
11339
+ /** Controls want their own types; a row value can arrive as anything. */
11340
+ asNumber(value) {
11341
+ const n = Number(value);
11342
+ return Number.isFinite(n) ? n : 0;
11343
+ }
11344
+ asText(value) {
11345
+ return value === null || value === undefined ? null : String(value);
11346
+ }
11347
+ asList(value) {
11348
+ if (Array.isArray(value))
11349
+ return value.map(v => String(v));
11350
+ if (value === null || value === undefined || value === '')
11351
+ return null;
11352
+ return String(value).split(',').map(v => v.trim()).filter(Boolean);
11353
+ }
11221
11354
  onDrilldown(event, part) {
11222
11355
  if (!this.isDrillable())
11223
11356
  return;
@@ -11225,11 +11358,20 @@ class CompositeComponent {
11225
11358
  this.drilldownClick.emit(part.text);
11226
11359
  }
11227
11360
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CompositeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11228
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CompositeComponent, isStandalone: true, selector: "eru-composite", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drilldownClick: "drilldownClick" }, ngImport: i0, template: "<div class=\"eru-composite\" [class.eru-composite--inline]=\"inline()\"\n [class.eru-composite--labelled]=\"labelPosition() !== 'none'\">\n @for (part of parts(); track part.name; let first = $first; let last = $last) {\n <span class=\"eru-composite__slot\">\n @if (labelPosition() === 'left' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"secondaryCss()\">{{ part.label }}</span>\n }\n <span class=\"eru-composite__part\" [class.eru-composite__part--secondary]=\"!first\"\n [class.eru-composite__part--drillable]=\"isDrillable() && first\" [ngStyle]=\"first ? primaryCss() : secondaryCss()\"\n [title]=\"part.text\" (click)=\"onDrilldown($event, part)\">{{ part.text }}</span>\n @if (labelPosition() === 'right' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"secondaryCss()\">{{ part.label }}</span>\n }\n </span>\n @if (inline() && !last && separator()) {\n <span class=\"eru-composite__sep\" [ngStyle]=\"secondaryCss()\">{{ separator() }}</span>\n }\n }\n</div>\n", styles: [".eru-composite{display:flex;flex-direction:column;justify-content:center;gap:3px;min-width:0;width:100%;height:100%;line-height:1.3;box-sizing:border-box;padding:0 var(--grid-cell-inset-x, 8px)}.eru-composite--inline{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:4px}.eru-composite__part{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.eru-composite__part--drillable{color:var(--grid-primary);cursor:pointer;text-decoration:underline}.eru-composite__sep{flex:0 0 auto}.eru-composite__part--secondary{line-height:1.15}.eru-composite__slot{display:contents}.eru-composite--labelled .eru-composite__slot{display:inline-flex;align-items:baseline;gap:4px;min-width:0}.eru-composite__label{flex:0 0 auto;white-space:nowrap;opacity:.75}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
11361
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CompositeComponent, isStandalone: true, selector: "eru-composite", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, isDrillable: { classPropertyName: "isDrillable", publicName: "isDrillable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { drilldownClick: "drilldownClick" }, ngImport: i0, template: "<div class=\"eru-composite\" [class.eru-composite--inline]=\"inline()\"\n [class.eru-composite--labelled]=\"labelPosition() !== 'none'\">\n @for (part of parts(); track part.name; let first = $first; let last = $last) {\n <span class=\"eru-composite__slot\">\n @if (labelPosition() === 'left' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n @if (part.render) {\n <!-- A datatype that draws itself. Rendered read-only with pointer events\n off (see .eru-composite__control): a composite is a view, so a rating\n here must not take a click that would edit the underlying column. The\n secondary slot scales the control down so the primary still leads. -->\n <span class=\"eru-composite__control\" [ngClass]=\"'eru-composite__control--' + part.render\"\n [class.eru-composite__control--secondary]=\"part.slot > 0\">\n @switch (part.render) {\n @case ('rating') {\n <eru-rating [value]=\"asNumber(part.value)\" [config]=\"ratingConfig(part.field)\" [isEditable]=\"false\"></eru-rating>\n }\n @case ('progress') {\n <eru-progress [value]=\"asNumber(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-progress>\n }\n @case ('status') {\n <eru-status [value]=\"asText(part.value)\" [config]=\"statusConfig(part.field)\" [isEditable]=\"false\"></eru-status>\n }\n @case ('tag') {\n <eru-tag [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-tag>\n }\n @case ('priority') {\n <eru-priority [value]=\"asText(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-priority>\n }\n @case ('people') {\n <eru-people [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-people>\n }\n @case ('checkbox') {\n <eru-checkbox [value]=\"part.value\" [config]=\"part.field\" [isEditable]=\"false\"></eru-checkbox>\n }\n }\n </span>\n } @else {\n <span class=\"eru-composite__part\" [class.eru-composite__part--secondary]=\"part.slot > 0\"\n [class.eru-composite__part--numeric]=\"part.numeric\"\n [class.eru-composite__part--drillable]=\"isDrillable() && part.slot === 0\" [ngStyle]=\"cssForSlot(part.slot)\"\n [title]=\"part.text\" (click)=\"onDrilldown($event, part)\">{{ part.text }}</span>\n }\n @if (labelPosition() === 'right' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n </span>\n @if (inline() && !last && separator()) {\n <span class=\"eru-composite__sep\" [ngStyle]=\"secondaryCss()\">{{ separator() }}</span>\n }\n }\n</div>\n", styles: [".eru-composite{display:flex;flex-direction:column;justify-content:center;gap:3px;min-width:0;width:100%;height:100%;line-height:1.3;box-sizing:border-box;padding:0 var(--grid-cell-inset-x, 8px)}.eru-composite--inline{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:4px}.eru-composite__part{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.eru-composite__part--drillable{color:var(--grid-primary);cursor:pointer;text-decoration:underline}.eru-composite__sep{flex:0 0 auto}.eru-composite__part--secondary{line-height:1.15}.eru-composite__slot{display:contents}.eru-composite--labelled .eru-composite__slot{display:inline-flex;align-items:baseline;gap:4px;min-width:0}.eru-composite__label{flex:0 0 auto;white-space:nowrap;opacity:.75}.eru-composite__control{display:inline-flex;align-items:center;justify-content:flex-start;min-width:0;pointer-events:none}.eru-composite__control--progress{display:flex;width:100%}.eru-composite__control--progress>eru-progress{display:block;flex:1 1 auto;width:100%;min-width:0}.eru-composite--inline .eru-composite__control--progress{width:auto;min-width:72px}.eru-composite__control>*{margin-left:0;margin-right:0}.eru-composite__control--status .status-display,.eru-composite__control--status .status-dot-display{margin:0}.eru-composite__control--tag .tag-display,.eru-composite__control--priority .priority-display{padding:0}.eru-composite__control--progress .progress-view-container{padding:0;max-height:none}.eru-composite__control--secondary{transform:scale(.78);transform-origin:left center;margin-right:-22%}.eru-composite:not(.eru-composite--inline) .eru-composite__part--numeric{text-align:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$4.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: RatingComponent, selector: "eru-rating", inputs: ["value", "config", "isEditable", "isActive"], outputs: ["valueChange", "editModeChange"] }, { kind: "component", type: ProgressComponent, selector: "eru-progress", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: StatusComponent, selector: "eru-status", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: TagComponent, selector: "eru-tag", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange", "newTagAdded"] }, { kind: "component", type: PriorityComponent, selector: "eru-priority", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "fieldSize", "eruGridStore"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: PeopleComponent, selector: "eru-people", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "columnWidth", "eruGridStore", "personCardTemplate"], outputs: ["valueChange", "blur", "focus", "drilldownClick", "editModeChange"] }, { kind: "component", type: CheckboxComponent, selector: "eru-checkbox", inputs: ["value", "config", "isEditable", "isActive", "isDrillable", "label"], outputs: ["valueChange", "change", "blur", "focus", "drilldownClick", "editModeChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
11229
11362
  }
11230
11363
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CompositeComponent, decorators: [{
11231
11364
  type: Component,
11232
- args: [{ selector: 'eru-composite', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"eru-composite\" [class.eru-composite--inline]=\"inline()\"\n [class.eru-composite--labelled]=\"labelPosition() !== 'none'\">\n @for (part of parts(); track part.name; let first = $first; let last = $last) {\n <span class=\"eru-composite__slot\">\n @if (labelPosition() === 'left' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"secondaryCss()\">{{ part.label }}</span>\n }\n <span class=\"eru-composite__part\" [class.eru-composite__part--secondary]=\"!first\"\n [class.eru-composite__part--drillable]=\"isDrillable() && first\" [ngStyle]=\"first ? primaryCss() : secondaryCss()\"\n [title]=\"part.text\" (click)=\"onDrilldown($event, part)\">{{ part.text }}</span>\n @if (labelPosition() === 'right' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"secondaryCss()\">{{ part.label }}</span>\n }\n </span>\n @if (inline() && !last && separator()) {\n <span class=\"eru-composite__sep\" [ngStyle]=\"secondaryCss()\">{{ separator() }}</span>\n }\n }\n</div>\n", styles: [".eru-composite{display:flex;flex-direction:column;justify-content:center;gap:3px;min-width:0;width:100%;height:100%;line-height:1.3;box-sizing:border-box;padding:0 var(--grid-cell-inset-x, 8px)}.eru-composite--inline{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:4px}.eru-composite__part{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.eru-composite__part--drillable{color:var(--grid-primary);cursor:pointer;text-decoration:underline}.eru-composite__sep{flex:0 0 auto}.eru-composite__part--secondary{line-height:1.15}.eru-composite__slot{display:contents}.eru-composite--labelled .eru-composite__slot{display:inline-flex;align-items:baseline;gap:4px;min-width:0}.eru-composite__label{flex:0 0 auto;white-space:nowrap;opacity:.75}\n"] }]
11365
+ args: [{ selector: 'eru-composite', standalone: true, imports: [
11366
+ CommonModule,
11367
+ RatingComponent,
11368
+ ProgressComponent,
11369
+ StatusComponent,
11370
+ TagComponent,
11371
+ PriorityComponent,
11372
+ PeopleComponent,
11373
+ CheckboxComponent,
11374
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"eru-composite\" [class.eru-composite--inline]=\"inline()\"\n [class.eru-composite--labelled]=\"labelPosition() !== 'none'\">\n @for (part of parts(); track part.name; let first = $first; let last = $last) {\n <span class=\"eru-composite__slot\">\n @if (labelPosition() === 'left' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n @if (part.render) {\n <!-- A datatype that draws itself. Rendered read-only with pointer events\n off (see .eru-composite__control): a composite is a view, so a rating\n here must not take a click that would edit the underlying column. The\n secondary slot scales the control down so the primary still leads. -->\n <span class=\"eru-composite__control\" [ngClass]=\"'eru-composite__control--' + part.render\"\n [class.eru-composite__control--secondary]=\"part.slot > 0\">\n @switch (part.render) {\n @case ('rating') {\n <eru-rating [value]=\"asNumber(part.value)\" [config]=\"ratingConfig(part.field)\" [isEditable]=\"false\"></eru-rating>\n }\n @case ('progress') {\n <eru-progress [value]=\"asNumber(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-progress>\n }\n @case ('status') {\n <eru-status [value]=\"asText(part.value)\" [config]=\"statusConfig(part.field)\" [isEditable]=\"false\"></eru-status>\n }\n @case ('tag') {\n <eru-tag [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-tag>\n }\n @case ('priority') {\n <eru-priority [value]=\"asText(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-priority>\n }\n @case ('people') {\n <eru-people [value]=\"asList(part.value)\" [config]=\"part.field\" [isEditable]=\"false\"></eru-people>\n }\n @case ('checkbox') {\n <eru-checkbox [value]=\"part.value\" [config]=\"part.field\" [isEditable]=\"false\"></eru-checkbox>\n }\n }\n </span>\n } @else {\n <span class=\"eru-composite__part\" [class.eru-composite__part--secondary]=\"part.slot > 0\"\n [class.eru-composite__part--numeric]=\"part.numeric\"\n [class.eru-composite__part--drillable]=\"isDrillable() && part.slot === 0\" [ngStyle]=\"cssForSlot(part.slot)\"\n [title]=\"part.text\" (click)=\"onDrilldown($event, part)\">{{ part.text }}</span>\n }\n @if (labelPosition() === 'right' && part.label) {\n <span class=\"eru-composite__label\" [ngStyle]=\"cssForSlot(part.slot)\">{{ part.label }}</span>\n }\n </span>\n @if (inline() && !last && separator()) {\n <span class=\"eru-composite__sep\" [ngStyle]=\"secondaryCss()\">{{ separator() }}</span>\n }\n }\n</div>\n", styles: [".eru-composite{display:flex;flex-direction:column;justify-content:center;gap:3px;min-width:0;width:100%;height:100%;line-height:1.3;box-sizing:border-box;padding:0 var(--grid-cell-inset-x, 8px)}.eru-composite--inline{flex-direction:row;align-items:baseline;justify-content:flex-start;gap:4px}.eru-composite__part{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.eru-composite__part--drillable{color:var(--grid-primary);cursor:pointer;text-decoration:underline}.eru-composite__sep{flex:0 0 auto}.eru-composite__part--secondary{line-height:1.15}.eru-composite__slot{display:contents}.eru-composite--labelled .eru-composite__slot{display:inline-flex;align-items:baseline;gap:4px;min-width:0}.eru-composite__label{flex:0 0 auto;white-space:nowrap;opacity:.75}.eru-composite__control{display:inline-flex;align-items:center;justify-content:flex-start;min-width:0;pointer-events:none}.eru-composite__control--progress{display:flex;width:100%}.eru-composite__control--progress>eru-progress{display:block;flex:1 1 auto;width:100%;min-width:0}.eru-composite--inline .eru-composite__control--progress{width:auto;min-width:72px}.eru-composite__control>*{margin-left:0;margin-right:0}.eru-composite__control--status .status-display,.eru-composite__control--status .status-dot-display{margin:0}.eru-composite__control--tag .tag-display,.eru-composite__control--priority .priority-display{padding:0}.eru-composite__control--progress .progress-view-container{padding:0;max-height:none}.eru-composite__control--secondary{transform:scale(.78);transform-origin:left center;margin-right:-22%}.eru-composite:not(.eru-composite--inline) .eru-composite__part--numeric{text-align:right}\n"] }]
11233
11375
  }], propDecorators: { row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], isDrillable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDrillable", required: false }] }], drilldownClick: [{ type: i0.Output, args: ["drilldownClick"] }] } });
11234
11376
  const DEFAULT_SECONDARY_STYLE = {
11235
11377
  font_size: 11,
@@ -11681,32 +11823,9 @@ class DataCellComponent {
11681
11823
  onSelectDrilldown(value) {
11682
11824
  this.handleDrilldown(value);
11683
11825
  }
11684
- // Status component handlers
11685
- getStatusConfig = computed(() => {
11686
- const config = this.columnCellConfiguration();
11687
- if (!config || config.datatype !== 'status') {
11688
- return null;
11689
- }
11690
- // Combine open and close status
11691
- const combinedStatus = [
11692
- ...(config.open_status || []),
11693
- ...(config.close_status || [])
11694
- ];
11695
- // Transform to Field format with options array
11696
- // Each option should have 'name' and 'color' properties
11697
- const options = combinedStatus.map((status) => ({
11698
- name: status.name,
11699
- color: status.color || '',
11700
- df: status.df || false
11701
- }));
11702
- // Return Field object with combined options and preserved open/close arrays
11703
- return {
11704
- ...config,
11705
- options: options,
11706
- open_status: config.open_status || [],
11707
- close_status: config.close_status || []
11708
- };
11709
- }, ...(ngDevMode ? [{ debugName: "getStatusConfig" }] : []));
11826
+ // Status component handlers. The flattening lives in the model so a composed
11827
+ // status part builds the same config from the same definition.
11828
+ getStatusConfig = computed(() => toStatusOptionsField(this.columnCellConfiguration()), ...(ngDevMode ? [{ debugName: "getStatusConfig" }] : []));
11710
11829
  onStatusBlur(value) {
11711
11830
  this.currentValue.set(value);
11712
11831
  this.eruGridStore().setActiveCell(null);
@@ -13402,6 +13521,14 @@ const COMPOSITE_FIELDS = [
13402
13521
  showWhen: f => f.composite_label_position === 'left' || f.composite_label_position === 'right',
13403
13522
  },
13404
13523
  { key: 'cell_style_secondary', label: 'Secondary value style', control: 'text_style' },
13524
+ {
13525
+ // Only once a third part exists: on a two-part composite the control would
13526
+ // style nothing, and the panel already runs long.
13527
+ key: 'cell_style_tertiary',
13528
+ label: 'Third value style',
13529
+ control: 'text_style',
13530
+ showWhen: f => (f.composite_fields || []).filter(Boolean).length > 2,
13531
+ },
13405
13532
  ];
13406
13533
  const PAGE_CELL_FIELDS = [
13407
13534
  { key: 'cell_page_id', label: 'Cell page', control: 'text' },
@@ -17124,5 +17251,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
17124
17251
  * Generated bundle index. Do not edit.
17125
17252
  */
17126
17253
 
17127
- export { ACTION_COLUMN_MIN_WIDTH, AttachmentComponent, CELL_RULE_OPERATORS, CheckboxComponent, ChipListComponent, ColumnConstraintsService, CompositeComponent, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EXPLICITLY_OVERRIDDEN_FIELD_KEYS, EmailComponent, EruGridComponent, EruGridService, EruGridStore, GRID_COLOR_TOKENS, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, NumericInputDirective, PRESENTATION_DATATYPES, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SEEDED_FIELD_KEYS, SELF_COLOURED_DATATYPES, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, abbreviateNumber, abbreviationScaleFor, cellRuleBarPercent, cellRuleMatches, cellRuleToCss, cellStatAlias, cellTextStyleToCss, collectColumnStatRequests, columnCellRules, composeColorValue, effectiveValueDatatype, evaluateRowCondition, formatCellValue, formatDateTimeWithPattern, formatDateWithPattern, formatNumberValue, hasOwnCellRules, matchCellRule, matchColorRange, normalizeCellRender, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseCellDate, parseCellDateTime, parseColorValue, parseDateWithPattern, readRowValue, resolveColumnRules, resolveRowValue, resolveStatValue, rowSelectionKey, statusPillColors, tagPillColors, toDateOnlyString, toDateTimeString };
17254
+ export { ACTION_COLUMN_MIN_WIDTH, AttachmentComponent, CELL_RULE_OPERATORS, CheckboxComponent, ChipListComponent, ColumnConstraintsService, CompositeComponent, CurrencyComponent, CustomVirtualScrollStrategy, DATA_TYPES, DATETIME_FORMATS, DATE_FORMATS, DateComponent, DatetimeComponent, DurationComponent, EXPLICITLY_OVERRIDDEN_FIELD_KEYS, EmailComponent, EruGridComponent, EruGridService, EruGridStore, GRID_COLOR_TOKENS, INHERITED_FIELD_KEYS, LocationComponent, MATERIAL_MODULES, MATERIAL_PROVIDERS, MONTH_SHORT_NAMES, NumberComponent, NumericInputDirective, PRESENTATION_DATATYPES, PRESET_CONFIG_DEFAULTS, PRESET_MANAGED_FIELDS, PeopleComponent, PhoneComponent, PriorityComponent, ProgressComponent, RatingComponent, SEEDED_FIELD_KEYS, SELF_COLOURED_DATATYPES, SelectComponent, StatusComponent, TagComponent, TextareaComponent, TextboxComponent, ThemeService, ThemeToggleComponent, WebsiteComponent, abbreviateNumber, abbreviationScaleFor, cellRuleBarPercent, cellRuleMatches, cellRuleToCss, cellStatAlias, cellTextStyleToCss, collectColumnStatRequests, columnCellRules, composeColorValue, effectiveValueDatatype, evaluateRowCondition, formatCellValue, formatDateTimeWithPattern, formatDateWithPattern, formatNumberValue, hasOwnCellRules, matchCellRule, matchColorRange, normalizeCellRender, normalizeDatatype, normalizeDateFormat, normalizeDateTimeFormat, parseCellDate, parseCellDateTime, parseColorValue, parseDateWithPattern, readRowValue, resolveColumnRules, resolveRowValue, resolveStatValue, rowSelectionKey, statusPillColors, tagPillColors, toDateOnlyString, toDateTimeString, toStatusOptionsField };
17128
17255
  //# sourceMappingURL=eru-grid.mjs.map