@ajsf/material 22.3.3 → 22.5.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.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @ajsf/material
2
2
 
3
+ [![Repository coverage, SonarQube Cloud](https://sonarcloud.io/api/project_badges/measure?project=hamzahamidi_ajsf&metric=coverage)](https://sonarcloud.io/summary/new_code?id=hamzahamidi_ajsf)
4
+
3
5
  Angular Material framework for [AJSF](https://github.com/hamzahamidi/ajsf#readme), which builds Angular forms from JSON Schema.
4
6
 
5
7
  ## Getting started
@@ -63,10 +63,10 @@ class FlexLayoutRootComponent {
63
63
  baseline: 'baseline', stretch: 'stretch',
64
64
  }; }
65
65
  getFlexBasis(node) {
66
- return (node?.options || {}).fxFlex || this.getFlexAttribute(node, 'flex-basis');
66
+ return node?.options?.fxFlex || this.getFlexAttribute(node, 'flex-basis');
67
67
  }
68
68
  getMaxWidth(node) {
69
- const fxFlex = (node?.options || {}).fxFlex;
69
+ const fxFlex = node?.options?.fxFlex;
70
70
  return fxFlex && `${fxFlex}`.trim().endsWith('%') ? fxFlex : null;
71
71
  }
72
72
  getAlignSelf(node) {
@@ -79,14 +79,14 @@ class FlexLayoutRootComponent {
79
79
  return options.order || options.fxFlexOrder || null;
80
80
  }
81
81
  getOffset(node) {
82
- return (node?.options || {}).fxFlexOffset || null;
82
+ return node?.options?.fxFlexOffset || null;
83
83
  }
84
84
  // Set attributes for flexbox child
85
85
  // (container attributes are set in flex-layout-section.component)
86
86
  getFlexAttribute(node, attribute) {
87
87
  const index = ['flex-grow', 'flex-shrink', 'flex-basis'].indexOf(attribute);
88
- return ((node.options || {}).flex || '').split(/\s+/)[index] ||
89
- (node.options || {})[attribute] || ['1', '1', 'auto'][index];
88
+ return (node.options?.flex || '').split(/\s+/)[index] ||
89
+ node.options?.[attribute] || ['1', '1', 'auto'][index];
90
90
  }
91
91
  showWidget(layoutNode) {
92
92
  return this.jsf.evaluateCondition(layoutNode, this.dataIndex);
@@ -201,7 +201,7 @@ class FlexLayoutSectionComponent {
201
201
  'space-evenly': 'space-evenly', stretch: 'stretch', baseline: 'baseline',
202
202
  }; }
203
203
  fxAlign(position) {
204
- const value = `${(this.options || {}).fxLayoutAlign || ''}`.trim().split(/\s+/)[position];
204
+ const value = `${this.options?.fxLayoutAlign || ''}`.trim().split(/\s+/)[position];
205
205
  return FlexLayoutSectionComponent.ALIGN[value] || null;
206
206
  }
207
207
  getJustifyContent() {
@@ -223,10 +223,11 @@ class FlexLayoutSectionComponent {
223
223
  case 'display':
224
224
  return flexActive ? 'flex' : 'initial';
225
225
  case 'flex-direction':
226
- case 'flex-wrap':
226
+ case 'flex-wrap': {
227
227
  const index = ['flex-direction', 'flex-wrap'].indexOf(attribute);
228
228
  return (this.options['flex-flow'] || '').split(/\s+/)[index] ||
229
229
  this.options[attribute] || ['column', 'nowrap'][index];
230
+ }
230
231
  case 'justify-content':
231
232
  case 'align-items':
232
233
  case 'align-content':
@@ -574,7 +575,7 @@ class MaterialAddReferenceComponent {
574
575
  }
575
576
  get showAddButton() {
576
577
  return !this.layoutNode.arrayItem ||
577
- this.layoutIndex[this.layoutIndex.length - 1] < this.options.maxItems;
578
+ this.layoutIndex.at(-1) < this.options.maxItems;
578
579
  }
579
580
  addItem(event) {
580
581
  event.preventDefault();
@@ -1409,6 +1410,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
1409
1410
  type: Input
1410
1411
  }] } });
1411
1412
 
1413
+ function hasTemplateExpression(text) {
1414
+ let start = -1;
1415
+ for (let index = 0; index < text.length; index += 1) {
1416
+ const character = text[index];
1417
+ if (character === '\n' || character === '\r' || character === '\u2028' || character === '\u2029') {
1418
+ start = -1;
1419
+ continue;
1420
+ }
1421
+ if (start === -1 && character === '{' && text[index + 1] === '{') {
1422
+ start = index;
1423
+ }
1424
+ else if (start !== -1 && character === '}' && text[index + 1] === '}') {
1425
+ if (index > start + 2) {
1426
+ return true;
1427
+ }
1428
+ start = -1;
1429
+ }
1430
+ }
1431
+ return false;
1432
+ }
1412
1433
  class MaterialDesignFrameworkComponent {
1413
1434
  constructor(changeDetector, jsf) {
1414
1435
  this.changeDetector = changeDetector;
@@ -1431,11 +1452,15 @@ class MaterialDesignFrameworkComponent {
1431
1452
  return false;
1432
1453
  }
1433
1454
  // If array length <= minItems, don't allow removing any items
1434
- return this.parentArray.items.length - 1 <= this.parentArray.options.minItems ? false :
1435
- // For removable list items, allow removing any item
1436
- this.layoutNode.arrayItemType === 'list' ? true :
1437
- // For removable tuple items, only allow removing last item in list
1438
- this.layoutIndex[this.layoutIndex.length - 1] === this.parentArray.items.length - 2;
1455
+ if (this.parentArray.items.length - 1 <= this.parentArray.options.minItems) {
1456
+ return false;
1457
+ }
1458
+ // For removable list items, allow removing any item
1459
+ if (this.layoutNode.arrayItemType === 'list') {
1460
+ return true;
1461
+ }
1462
+ // For removable tuple items, only allow removing last item in list
1463
+ return this.layoutIndex.at(-1) === this.parentArray.items.length - 2;
1439
1464
  }
1440
1465
  ngOnInit() {
1441
1466
  this.initializeFramework();
@@ -1465,7 +1490,7 @@ class MaterialDesignFrameworkComponent {
1465
1490
  if (!['$ref', 'advancedfieldset', 'authfieldset', 'button', 'card',
1466
1491
  'checkbox', 'expansion-panel', 'help', 'message', 'msg', 'section',
1467
1492
  'submit', 'tabarray', 'tabs'].includes(this.layoutNode.type) &&
1468
- /{{.+?}}/.test(this.widgetOptions.title || '')) {
1493
+ hasTemplateExpression(this.widgetOptions.title || '')) {
1469
1494
  this.dynamicTitle = this.widgetOptions.title;
1470
1495
  this.updateTitle();
1471
1496
  }
@@ -1486,17 +1511,17 @@ class MaterialDesignFrameworkComponent {
1486
1511
  }
1487
1512
  }
1488
1513
  updateTitle() {
1489
- this.widgetLayoutNode.options.title = this.jsf.parseText(this.dynamicTitle, this.jsf.getFormControlValue(this), this.jsf.getFormControlGroup(this).value, this.dataIndex[this.dataIndex.length - 1]);
1514
+ this.widgetLayoutNode.options.title = this.jsf.parseText(this.dynamicTitle, this.jsf.getFormControlValue(this), this.jsf.getFormControlGroup(this).value, this.dataIndex.at(-1));
1490
1515
  }
1491
1516
  removeItem() {
1492
1517
  this.jsf.removeItem(this);
1493
1518
  }
1494
1519
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: MaterialDesignFrameworkComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1.JsonSchemaFormService }], target: i0.ɵɵFactoryTarget.Component }); }
1495
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: MaterialDesignFrameworkComponent, isStandalone: false, selector: "material-design-framework", inputs: { layoutNode: "layoutNode", layoutIndex: "layoutIndex", dataIndex: "dataIndex" }, usesOnChanges: true, ngImport: i0, template: "<div\n [class.array-item]=\"widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref'\"\n [orderable]=\"isOrderable\"\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\">\n @if (showRemoveButton) {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n height=\"18\" width=\"18\" viewBox=\"0 0 24 24\"\n class=\"close-button\"\n (click)=\"removeItem()\">\n <path\n d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z\"/>\n </svg>\n }\n <select-widget-widget\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\"></select-widget-widget>\n</div>\n@if (widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref') {\n <div class=\"spacer\"></div>\n}\n", styles: [".array-item{border-radius:2px;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;padding:6px;position:relative;transition:all .28s cubic-bezier(.4,0,.2,1)}.close-button{cursor:pointer;position:absolute;top:6px;right:6px;fill:#0006;visibility:hidden;z-index:500}.close-button:hover{fill:#000c}.array-item:hover>.close-button{visibility:visible}.spacer{margin:6px 0}[draggable=true]:hover{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;cursor:move;z-index:10}[draggable=true].drag-target-top{box-shadow:0 -2px #000;position:relative;z-index:20}[draggable=true].drag-target-bottom{box-shadow:0 2px #000;position:relative;z-index:20}\n"], dependencies: [{ kind: "component", type: i1.SelectWidgetComponent, selector: "select-widget-widget", inputs: ["layoutNode", "layoutIndex", "dataIndex"] }, { kind: "directive", type: i1.OrderableDirective, selector: "[orderable]", inputs: ["orderable", "layoutNode", "layoutIndex", "dataIndex"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
1520
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: MaterialDesignFrameworkComponent, isStandalone: false, selector: "material-design-framework", inputs: { layoutNode: "layoutNode", layoutIndex: "layoutIndex", dataIndex: "dataIndex" }, usesOnChanges: true, ngImport: i0, template: "<div\n [class.array-item]=\"widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref'\"\n [orderable]=\"isOrderable\"\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\">\n @if (showRemoveButton) {\n <button\n type=\"button\"\n class=\"close-button\"\n aria-label=\"Remove item\"\n (click)=\"removeItem()\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n height=\"18\" width=\"18\" viewBox=\"0 0 24 24\"\n aria-hidden=\"true\">\n <path\n d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z\"/>\n </svg>\n </button>\n }\n <select-widget-widget\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\"></select-widget-widget>\n</div>\n@if (widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref') {\n <div class=\"spacer\"></div>\n}\n", styles: [".array-item{border-radius:2px;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;padding:6px;position:relative;transition:box-shadow .28s cubic-bezier(.4,0,.2,1)}.close-button{background:transparent;border:0;cursor:pointer;padding:0;position:absolute;top:6px;right:6px;fill:#0006;opacity:0;pointer-events:none;z-index:500}.close-button:hover{fill:#000c}.array-item:hover>.close-button,.array-item:focus-within>.close-button{opacity:1;pointer-events:auto}.spacer{margin:6px 0}[draggable=true]:hover{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;cursor:move;z-index:10}[draggable=true].drag-target-top{box-shadow:0 -2px #000;position:relative;z-index:20}[draggable=true].drag-target-bottom{box-shadow:0 2px #000;position:relative;z-index:20}\n"], dependencies: [{ kind: "component", type: i1.SelectWidgetComponent, selector: "select-widget-widget", inputs: ["layoutNode", "layoutIndex", "dataIndex"] }, { kind: "directive", type: i1.OrderableDirective, selector: "[orderable]", inputs: ["orderable", "layoutNode", "layoutIndex", "dataIndex"] }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
1496
1521
  }
1497
1522
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: MaterialDesignFrameworkComponent, decorators: [{
1498
1523
  type: Component,
1499
- args: [{ selector: 'material-design-framework', changeDetection: ChangeDetectionStrategy.Eager, standalone: false, template: "<div\n [class.array-item]=\"widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref'\"\n [orderable]=\"isOrderable\"\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\">\n @if (showRemoveButton) {\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n height=\"18\" width=\"18\" viewBox=\"0 0 24 24\"\n class=\"close-button\"\n (click)=\"removeItem()\">\n <path\n d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z\"/>\n </svg>\n }\n <select-widget-widget\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\"></select-widget-widget>\n</div>\n@if (widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref') {\n <div class=\"spacer\"></div>\n}\n", styles: [".array-item{border-radius:2px;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;padding:6px;position:relative;transition:all .28s cubic-bezier(.4,0,.2,1)}.close-button{cursor:pointer;position:absolute;top:6px;right:6px;fill:#0006;visibility:hidden;z-index:500}.close-button:hover{fill:#000c}.array-item:hover>.close-button{visibility:visible}.spacer{margin:6px 0}[draggable=true]:hover{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;cursor:move;z-index:10}[draggable=true].drag-target-top{box-shadow:0 -2px #000;position:relative;z-index:20}[draggable=true].drag-target-bottom{box-shadow:0 2px #000;position:relative;z-index:20}\n"] }]
1524
+ args: [{ selector: 'material-design-framework', changeDetection: ChangeDetectionStrategy.Eager, standalone: false, template: "<div\n [class.array-item]=\"widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref'\"\n [orderable]=\"isOrderable\"\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\">\n @if (showRemoveButton) {\n <button\n type=\"button\"\n class=\"close-button\"\n aria-label=\"Remove item\"\n (click)=\"removeItem()\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n height=\"18\" width=\"18\" viewBox=\"0 0 24 24\"\n aria-hidden=\"true\">\n <path\n d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z\"/>\n </svg>\n </button>\n }\n <select-widget-widget\n [dataIndex]=\"dataIndex\"\n [layoutIndex]=\"layoutIndex\"\n [layoutNode]=\"widgetLayoutNode\"></select-widget-widget>\n</div>\n@if (widgetLayoutNode?.arrayItem && widgetLayoutNode?.type !== '$ref') {\n <div class=\"spacer\"></div>\n}\n", styles: [".array-item{border-radius:2px;box-shadow:0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;padding:6px;position:relative;transition:box-shadow .28s cubic-bezier(.4,0,.2,1)}.close-button{background:transparent;border:0;cursor:pointer;padding:0;position:absolute;top:6px;right:6px;fill:#0006;opacity:0;pointer-events:none;z-index:500}.close-button:hover{fill:#000c}.array-item:hover>.close-button,.array-item:focus-within>.close-button{opacity:1;pointer-events:auto}.spacer{margin:6px 0}[draggable=true]:hover{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;cursor:move;z-index:10}[draggable=true].drag-target-top{box-shadow:0 -2px #000;position:relative;z-index:20}[draggable=true].drag-target-bottom{box-shadow:0 2px #000;position:relative;z-index:20}\n"] }]
1500
1525
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1.JsonSchemaFormService }], propDecorators: { layoutNode: [{
1501
1526
  type: Input
1502
1527
  }], layoutIndex: [{
@@ -1968,7 +1993,13 @@ class MaterialOneOfComponent {
1968
1993
  const type = this.layoutNode.type;
1969
1994
  this.isFieldset = (type === 'selectfieldset' || type === 'optionfieldset')
1970
1995
  && Array.isArray(this.layoutNode.items) && this.layoutNode.items.length > 0;
1971
- if (this.isFieldset && !this.options.titleMap && !this.options.enum) {
1996
+ this.initializeSelectList();
1997
+ this.initializeFieldsetValueMap();
1998
+ this.initializeControl();
1999
+ this.selectInitialFieldsetValue();
2000
+ }
2001
+ initializeSelectList() {
2002
+ if (this.isFieldset && !this.hasExplicitFieldsetOptions()) {
1972
2003
  this.selectList = this.layoutNode.items.map((item, i) => ({
1973
2004
  name: item.options?.legend || item.options?.title || item.name || `Option ${i + 1}`,
1974
2005
  value: i
@@ -1977,26 +2008,37 @@ class MaterialOneOfComponent {
1977
2008
  else {
1978
2009
  this.selectList = buildTitleMap(this.options.titleMap || this.options.enumNames, this.options.enum, !!this.options.required, !!this.options.flatList);
1979
2010
  }
1980
- if (this.isFieldset && (this.options.titleMap || this.options.enum)) {
1981
- let idx = 0;
1982
- for (const entry of this.selectList) {
1983
- if (entry.value != null && entry.value !== '') {
1984
- if (idx === 0) {
1985
- this.selectedValue = entry.value;
1986
- }
1987
- this.fieldsetValueMap.set(entry.value, idx++);
1988
- }
2011
+ }
2012
+ initializeFieldsetValueMap() {
2013
+ if (!this.isFieldset || !this.hasExplicitFieldsetOptions()) {
2014
+ return;
2015
+ }
2016
+ let index = 0;
2017
+ for (const entry of this.selectList) {
2018
+ if (entry.value == null || entry.value === '') {
2019
+ continue;
1989
2020
  }
2021
+ if (index === 0) {
2022
+ this.selectedValue = entry.value;
2023
+ }
2024
+ this.fieldsetValueMap.set(entry.value, index++);
1990
2025
  }
1991
- if (!this.isFieldset || this.options.titleMap || this.options.enum) {
2026
+ }
2027
+ initializeControl() {
2028
+ if (!this.isFieldset || this.hasExplicitFieldsetOptions()) {
1992
2029
  this.jsf.initializeControl(this, !this.options.readonly);
1993
2030
  }
2031
+ }
2032
+ selectInitialFieldsetValue() {
1994
2033
  if (this.isFieldset && this.controlValue != null) {
1995
2034
  this.selectedValue = this.controlValue;
1996
2035
  this._selectedItem = this.fieldsetValueMap.get(this.controlValue)
1997
2036
  ?? (typeof this.controlValue === 'number' ? this.controlValue : 0);
1998
2037
  }
1999
2038
  }
2039
+ hasExplicitFieldsetOptions() {
2040
+ return this.options.titleMap || this.options.enum;
2041
+ }
2000
2042
  selectChild(event) {
2001
2043
  this.selectedValue = event.value;
2002
2044
  this._selectedItem =