@ulb-darmstadt/shacl-form 1.8.1 → 1.8.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.
@@ -7,6 +7,7 @@ import { RokitSelect } from '@ro-kit/ui-widgets';
7
7
  export declare class ShaclProperty extends HTMLElement {
8
8
  template: ShaclPropertyTemplate;
9
9
  addButton: RokitSelect | undefined;
10
+ container: HTMLElement;
10
11
  constructor(shaclSubject: BlankNode | NamedNode, parent: ShaclNode, config: Config, valueSubject?: NamedNode | BlankNode);
11
12
  addPropertyInstance(value?: Term): HTMLElement;
12
13
  updateControls(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulb-darmstadt/shacl-form",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "SHACL form generator",
5
5
  "main": "dist/form-default.js",
6
6
  "module": "dist/form-default.js",
@@ -71,7 +71,7 @@
71
71
  "uuid": "^11.1.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "@ro-kit/ui-widgets": "^0.0.28",
74
+ "@ro-kit/ui-widgets": "^0.0.29",
75
75
  "mdui": "^2.1.4"
76
76
  }
77
77
  }
package/src/constants.ts CHANGED
@@ -23,4 +23,5 @@ export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
23
23
  export const SHACL_PREDICATE_PROPERTY = DataFactory.namedNode(PREFIX_SHACL + 'property')
24
24
  export const SHACL_PREDICATE_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'class')
25
25
  export const SHACL_PREDICATE_TARGET_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'targetClass')
26
- export const SHACL_PREDICATE_NODE_KIND = DataFactory.namedNode(PREFIX_SHACL + 'nodeKind')
26
+ export const SHACL_PREDICATE_NODE_KIND = DataFactory.namedNode(PREFIX_SHACL + 'nodeKind')
27
+ export const XSD_DATATYPE_STRING = DataFactory.namedNode(PREFIX_XSD + 'string')
@@ -13,7 +13,6 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
13
13
  constraintElement.classList.add('shacl-or-constraint')
14
14
 
15
15
  const optionElements: InputListEntry[] = []
16
- optionElements.push({ label: '--- please choose ---', value: '' })
17
16
 
18
17
  if (context instanceof ShaclNode) {
19
18
  const properties: ShaclProperty[][] = []
package/src/form.ts CHANGED
@@ -7,6 +7,7 @@ import { Editor, Theme } from './theme'
7
7
  import { serialize } from './serialize'
8
8
  import { Validator } from 'shacl-engine'
9
9
  import { setSharedShapesGraph } from './loader'
10
+ import { RokitCollapsible } from '@ro-kit/ui-widgets'
10
11
 
11
12
  export class ShaclForm extends HTMLElement {
12
13
  static get observedAttributes() { return Config.dataAttributes() }
@@ -88,7 +89,12 @@ export class ShaclForm extends HTMLElement {
88
89
  this.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }))
89
90
  } else {
90
91
  // focus first invalid element
91
- (this.form.querySelector(':scope .invalid > .editor') as HTMLElement)?.focus()
92
+ let invalidEditor = this.form.querySelector(':scope .invalid > .editor')
93
+ if (invalidEditor) {
94
+ (invalidEditor as HTMLElement).focus()
95
+ } else {
96
+ this.form.querySelector(':scope .invalid')?.scrollIntoView()
97
+ }
92
98
  }
93
99
  })
94
100
  }
@@ -176,10 +182,16 @@ export class ShaclForm extends HTMLElement {
176
182
  if (result.path?.length) {
177
183
  const path = result.path[0].predicates[0]
178
184
  // try to find most specific editor elements first
179
- let invalidElements = this.form.querySelectorAll(`:scope shacl-node[data-node-id='${focusNode.id}'] > shacl-property > .property-instance[data-path='${path.id}'] > .editor, :scope shacl-node[data-node-id='${focusNode.id}'] > .shacl-group > shacl-property > .property-instance[data-path='${path.id}'] > .editor`)
185
+ let invalidElements = this.form.querySelectorAll(`
186
+ :scope shacl-node[data-node-id='${focusNode.id}'] > shacl-property > .property-instance[data-path='${path.id}'] > .editor,
187
+ :scope shacl-node[data-node-id='${focusNode.id}'] > shacl-property > .shacl-group > .property-instance[data-path='${path.id}'] > .editor,
188
+ :scope shacl-node[data-node-id='${focusNode.id}'] > .shacl-group > shacl-property > .property-instance[data-path='${path.id}'] > .editor,
189
+ :scope shacl-node[data-node-id='${focusNode.id}'] > .shacl-group > shacl-property > .shacl-group > .property-instance[data-path='${path.id}'] > .editor`)
180
190
  if (invalidElements.length === 0) {
181
191
  // if no editors found, select respective node. this will be the case for node shape violations.
182
- invalidElements = this.form.querySelectorAll(`:scope [data-node-id='${focusNode.id}'] > * > [data-path='${path.id}']`)
192
+ invalidElements = this.form.querySelectorAll(`
193
+ :scope [data-node-id='${focusNode.id}'] > shacl-property > .property-instance[data-path='${path.id}'],
194
+ :scope [data-node-id='${focusNode.id}'] > shacl-property > .shacl-group > .property-instance[data-path='${path.id}']`)
183
195
  }
184
196
 
185
197
  for (const invalidElement of invalidElements) {
@@ -191,8 +203,8 @@ export class ShaclForm extends HTMLElement {
191
203
  parent.classList.remove('valid')
192
204
  parent.appendChild(this.createValidationErrorDisplay(result))
193
205
  do {
194
- if (parent.classList.contains('collapsible')) {
195
- parent.classList.add('open')
206
+ if (parent instanceof RokitCollapsible) {
207
+ parent.open = true
196
208
  }
197
209
  parent = parent.parentElement
198
210
  } while (parent)
package/src/group.ts CHANGED
@@ -1,35 +1,34 @@
1
1
  import { PREFIX_RDFS } from './constants'
2
2
  import { Config } from './config'
3
3
  import { findObjectValueByPredicate } from './util'
4
+ import { RokitCollapsible } from '@ro-kit/ui-widgets'
4
5
 
5
6
  export function createShaclGroup(groupSubject: string, config: Config): HTMLElement {
6
- const group = document.createElement('div')
7
- group.dataset['subject'] = groupSubject
8
- group.classList.add('shacl-group')
9
7
  let name = groupSubject
10
8
  const quads = config.store.getQuads(groupSubject, null, null, null)
11
9
  const label = findObjectValueByPredicate(quads, "label", PREFIX_RDFS, config.languages)
12
10
  if (label) {
13
11
  name = label
14
12
  }
15
- const order = findObjectValueByPredicate(quads, "order")
16
- if (order) {
17
- group.style.order = order
18
- }
19
- const header = document.createElement('h1')
20
- header.innerText = name
21
- group.appendChild(header)
22
13
 
14
+ let group: HTMLElement
23
15
  if (config.attributes.collapse !== null) {
24
- group.classList.add('collapsible')
25
- if (config.attributes.collapse === 'open') {
26
- group.classList.add('open')
27
- }
28
- header.classList.add('activator')
29
- header.addEventListener('click', () => {
30
- group.classList.toggle('open')
31
- })
16
+ group = new RokitCollapsible()
17
+ group.classList.add('collapsible');
18
+ (group as RokitCollapsible).open = config.attributes.collapse === 'open';
19
+ (group as RokitCollapsible).label = name
20
+ } else {
21
+ group = document.createElement('div')
22
+ const header = document.createElement('h1')
23
+ header.innerText = name
24
+ group.appendChild(header)
25
+ }
32
26
 
27
+ group.dataset['subject'] = groupSubject
28
+ group.classList.add('shacl-group')
29
+ const order = findObjectValueByPredicate(quads, "order")
30
+ if (order) {
31
+ group.style.order = order
33
32
  }
34
33
  return group
35
34
  }
@@ -135,6 +135,7 @@ export class ShaclPropertyTemplate {
135
135
  clone(): ShaclPropertyTemplate {
136
136
  const copy = Object.assign({}, this)
137
137
  // arrays are not cloned but referenced, so create them manually
138
+ copy.extendedShapes = [ ...this.extendedShapes ]
138
139
  copy.owlImports = [ ...this.owlImports ]
139
140
  if (this.languageIn) {
140
141
  copy.languageIn = [ ...this.languageIn ]
package/src/property.ts CHANGED
@@ -9,15 +9,25 @@ import { Editor, fieldFactory, InputListEntry } from './theme'
9
9
  import { toRDF } from './serialize'
10
10
  import { findPlugin } from './plugin'
11
11
  import { DATA_GRAPH, RDF_PREDICATE_TYPE, SHACL_PREDICATE_TARGET_CLASS } from './constants'
12
- import { RokitButton, RokitSelect } from '@ro-kit/ui-widgets'
12
+ import { RokitButton, RokitCollapsible, RokitSelect } from '@ro-kit/ui-widgets'
13
13
 
14
14
  export class ShaclProperty extends HTMLElement {
15
15
  template: ShaclPropertyTemplate
16
16
  addButton: RokitSelect | undefined
17
+ container: HTMLElement
17
18
 
18
19
  constructor(shaclSubject: BlankNode | NamedNode, parent: ShaclNode, config: Config, valueSubject?: NamedNode | BlankNode) {
19
20
  super()
20
21
  this.template = new ShaclPropertyTemplate(config.store.getQuads(shaclSubject, null, null, null), parent, config)
22
+ if (this.template.extendedShapes.length && this.template.config.attributes.collapse !== null && (!this.template.maxCount || this.template.maxCount > 1)) {
23
+ const collapsible = new RokitCollapsible()
24
+ collapsible.classList.add('collapsible', 'shacl-group');
25
+ collapsible.open = config.attributes.collapse === 'open';
26
+ collapsible.label = this.template.label;
27
+ this.container = collapsible
28
+ } else {
29
+ this.container = this
30
+ }
21
31
 
22
32
  if (this.template.order !== undefined) {
23
33
  this.style.order = `${this.template.order}`
@@ -28,7 +38,7 @@ export class ShaclProperty extends HTMLElement {
28
38
 
29
39
  if (config.editMode && !parent.linked) {
30
40
  this.addButton = this.createAddButton()
31
- this.appendChild(this.addButton)
41
+ this.container.appendChild(this.addButton)
32
42
  }
33
43
 
34
44
  // bind existing values
@@ -65,21 +75,10 @@ export class ShaclProperty extends HTMLElement {
65
75
  this.updateControls()
66
76
  }
67
77
 
68
- if (this.template.extendedShapes.length && this.template.config.attributes.collapse !== null && (!this.template.maxCount || this.template.maxCount > 1)) {
78
+ if (this.container instanceof RokitCollapsible) {
69
79
  // in view mode, show collapsible only when we have something to show
70
- if ((config.editMode && !parent.linked) || this.childElementCount > 0) {
71
- const collapsible = this
72
- collapsible.classList.add('collapsible')
73
- if (this.template.config.attributes.collapse === 'open') {
74
- collapsible.classList.add('open')
75
- }
76
- const activator = document.createElement('h1')
77
- activator.classList.add('activator')
78
- activator.innerText = this.template.label
79
- activator.addEventListener('click', () => {
80
- collapsible.classList.toggle('open')
81
- })
82
- this.prepend(activator)
80
+ if ((config.editMode && !parent.linked) || this.container.childElementCount > 0) {
81
+ this.appendChild(this.container)
83
82
  }
84
83
  }
85
84
  }
@@ -113,9 +112,9 @@ export class ShaclProperty extends HTMLElement {
113
112
  instance = createPropertyInstance(this.template, value, undefined, linked || this.template.parent.linked)
114
113
  }
115
114
  if (this.addButton) {
116
- this.insertBefore(instance!, this.addButton)
115
+ this.container.insertBefore(instance!, this.addButton)
117
116
  } else {
118
- this.appendChild(instance!)
117
+ this.container.appendChild(instance!)
119
118
  }
120
119
  return instance!
121
120
  }
@@ -136,10 +135,13 @@ export class ShaclProperty extends HTMLElement {
136
135
  const mayAdd = this.template.maxCount === undefined || instanceCount < this.template.maxCount
137
136
  this.classList.toggle('may-remove', mayRemove)
138
137
  this.classList.toggle('may-add', mayAdd)
138
+ if (mayAdd && this.addButton?.input) {
139
+ this.addButton.input.updateMinWidth()
140
+ }
139
141
  }
140
142
 
141
143
  toRDF(graph: Store, subject: NamedNode | BlankNode) {
142
- for (const instance of this.querySelectorAll(':scope > .property-instance')) {
144
+ for (const instance of this.querySelectorAll(':scope > .property-instance, :scope > .collapsible > .property-instance')) {
143
145
  const pathNode = DataFactory.namedNode((instance as HTMLElement).dataset.path!)
144
146
  if (instance.firstChild instanceof ShaclNode) {
145
147
  const shapeSubject = instance.firstChild.toRDF(graph)
@@ -247,7 +249,7 @@ export class ShaclProperty extends HTMLElement {
247
249
  } else {
248
250
  // user wants to link existing instance
249
251
  const value = JSON.parse(addButton.value) as Term
250
- this.insertBefore(createPropertyInstance(this.template, value, true, true), addButton)
252
+ this.container.insertBefore(createPropertyInstance(this.template, value, true, true), addButton)
251
253
  }
252
254
  addButton.value = ''
253
255
  })
package/src/styles.css CHANGED
@@ -1,30 +1,28 @@
1
- form { box-sizing: border-box; display:block; --label-width: 8em; --caret-size: 10px; }
1
+ form { display:block; --label-width: 8em; --caret-size: 10px; }
2
2
  form.mode-edit { padding-left: 1em; }
3
- form *, form ::after, form ::before { box-sizing: inherit; }
4
- shacl-node, .shacl-group { display: flex; flex-direction: column; width: 100%; position: relative; }
3
+ form, form * { box-sizing: border-box; }
4
+ shacl-node, .collapsible::part(content) { display: flex; flex-direction: column; width: 100%; position: relative; }
5
5
  shacl-node .remove-button { margin-left: 4px; margin-top: 1px; }
6
6
  shacl-node .add-button { color: #555; background-color: transparent; margin: 4px 24px 0 0; border: 0; }
7
7
  shacl-node .add-button:hover { color:#222; }
8
8
  shacl-node .add-button:focus { box-shadow: none; }
9
- shacl-node h1 { font-size: 1.1rem; border-bottom: 1px solid; margin-top: 4px; color: #555; }
10
- shacl-property { display: flex; flex-direction: column; align-items: end; position: relative; }
9
+ shacl-node h1 { font-size: 16px; border-bottom: 1px solid #AAA; margin-top: 4px; color: #555; }
10
+ shacl-property:not(:has(>.collapsible)), shacl-property>.collapsible::part(content) { display: flex; flex-direction: column; align-items: end; position: relative; }
11
11
  shacl-property:not(.may-add) > .add-button { display: none; }
12
12
  shacl-property:not(.may-remove) > .property-instance > .remove-button:not(.persistent) { visibility: hidden; }
13
13
  shacl-property:not(.may-remove) > .shacl-or-constraint > .remove-button:not(.persistent) { visibility: hidden; }
14
14
  .mode-view .shacl-group:not(:has(shacl-property)) { display: none; }
15
15
  .property-instance, .shacl-or-constraint { display: flex; align-items: flex-start; padding: 4px 0; width: 100%; position: relative; }
16
+ .shacl-or-constraint > div { display: flex; flex-grow: 1; }
16
17
  .shacl-or-constraint label { display: inline-block; word-break: break-word; width: var(--label-width); line-height: 1em; padding-top: 0.15em; padding-right: 1em; flex-shrink: 0; position: relative; }
17
18
  .property-instance label[title] { cursor: help; text-decoration: underline dashed #AAA; }
18
19
  .property-instance.linked label:after, label.linked:after { content: '\1F517'; font-size: 0.6em; padding-left: 6px; }
19
20
  .mode-edit .property-instance label.required::before { color: red; content: '\2736'; font-size: 0.6rem; position: absolute; left: -1.4em; top: 0.15rem; }
20
- .property-instance.valid::before { position: absolute; left: calc(var(--label-width) - 1em); top: 3px; color: green; content: '\2713'; }
21
- .editor:not([type='checkbox']), .shacl-or-constraint select { flex-grow: 1; }
22
- .shacl-or-constraint select { border: 1px solid #DDD; padding: 2px 4px; }
23
- select { overflow: hidden; text-overflow: ellipsis; }
21
+ .property-instance.valid::before { content: ''; position: absolute; left: calc(var(--label-width) - 1em); top:0.5em; width: 0.9em; height: 0.9em; background: url('data:image/svg+xml;utf8,<svg viewBox="0 0 1024 1024" fill="green" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M866.133333 258.133333L362.666667 761.6l-204.8-204.8L98.133333 618.666667 362.666667 881.066667l563.2-563.2z"/></svg>'); }
22
+ .editor:not([type='checkbox']) { flex-grow: 1; }
24
23
  textarea.editor { resize: vertical; }
25
- .lang-chooser { position: absolute; top: 6px; right: 26px; border: 0; background-color: #e9e9ed; padding: 2px 4px; max-width: 40px; width: 40px; box-sizing: content-box; }
26
- .lang-chooser+.editor { padding-right: 55px; }
27
- .validation-error { position: absolute; left: calc(var(--label-width) - 1em); top: 3px; color: red; cursor: help; }
24
+ .lang-chooser { border: 0; background-color: #e9e9ed; padding: 2px 4px; align-self: flex-start; }
25
+ .validation-error { position: absolute; left: calc(var(--label-width) - 1em); top: 0.5em; color: red; cursor: help; }
28
26
  .validation-error::before { content: '\26a0' }
29
27
  .validation-error.node { left: -1em; }
30
28
  .invalid > .editor { border-color: red !important; }
@@ -43,15 +41,9 @@ a, a:visited { color: inherit; }
43
41
  0% { opacity: 0; transform: scaleY(0.8); }
44
42
  100% { opacity: 1; transform: scaleY(1); }
45
43
  }
46
-
47
- .collapsible > .activator { display: flex; justify-content: space-between; align-items: center; cursor: pointer; width: 100%; border: 0; padding: 8px 0; transition: 0.2s; }
48
- .collapsible > .activator:hover, .collapsible.open > .activator { background-color: #F5F5F5; }
49
- .collapsible > .activator::after { content:''; width: var(--caret-size); height: var(--caret-size); border-style: none solid solid none; border-width: calc(0.3 * var(--caret-size)); transform: rotate(45deg); transition: transform .15s ease-out; margin-right: calc(0.5 * var(--caret-size)); }
50
- .collapsible.open > .activator::after { transform: rotate(225deg); }
51
- .collapsible > *:not(.activator) { transition: all 0.2s ease-out; opacity: 1; }
52
- .collapsible:not(.open) > *:not(.activator) { max-height: 0; padding: 0; opacity: 0; overflow: hidden; }
44
+ .collapsible::part(label) { font-weight: 600; }
45
+ .collapsible > .property-instance:nth-child(even) { background-color: #F8F8F8; }
53
46
  .collapsible > .property-instance > shacl-node > h1 { display: none; }
54
- .collapsible.open > .property-instance:nth-child(odd) { background-color: #F5F5F5; }
55
47
  .ref-link { cursor: pointer; }
56
48
  .ref-link:hover { text-decoration: underline; }
57
49
  .node-id-display { color: #999; font-size: 11px; }
@@ -1,4 +1,4 @@
1
- .editor:not([type='checkbox']) { border: 1px solid #DDD; padding: 2px 4px; }
1
+ .editor:not([type='checkbox']) { border: 1px solid #DDD; }
2
2
  .property-instance label { display: inline-flex; word-break: break-word; line-height: 1em; padding-top: 0.15em; padding-right: 1em; flex-shrink: 0; position: relative; }
3
3
  .property-instance:not(:first-child) > label:not(.persistent) { visibility: hidden; max-height: 0; }
4
4
  .mode-edit .property-instance label { width: var(--label-width); }
@@ -1,11 +1,11 @@
1
1
  import { Term } from '@rdfjs/types'
2
2
  import { ShaclPropertyTemplate } from "../property-template"
3
3
  import { Editor, InputListEntry, Theme } from "../theme"
4
- import { PREFIX_SHACL, PREFIX_XSD } from '../constants'
4
+ import { PREFIX_SHACL, PREFIX_XSD, XSD_DATATYPE_STRING } from '../constants'
5
5
  import { Literal, NamedNode } from 'n3'
6
6
  import { Term as N3Term } from 'n3'
7
7
  import css from './default.css?raw'
8
- import { RokitSelect } from '@ro-kit/ui-widgets'
8
+ import { RokitInput, RokitSelect, RokitTextArea } from '@ro-kit/ui-widgets'
9
9
 
10
10
  export class DefaultTheme extends Theme {
11
11
  idCtr = 0
@@ -62,7 +62,7 @@ export class DefaultTheme extends Theme {
62
62
  }
63
63
 
64
64
  createDateEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
65
- const editor: Editor = document.createElement('input')
65
+ const editor = new RokitInput()
66
66
  if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
67
67
  editor.type = 'datetime-local'
68
68
  // this enables seconds in dateTime input
@@ -71,6 +71,8 @@ export class DefaultTheme extends Theme {
71
71
  else {
72
72
  editor.type = 'date'
73
73
  }
74
+ editor.clearable = true
75
+ editor.dense = true
74
76
  editor.classList.add('pr-0')
75
77
  const result = this.createDefaultTemplate(label, null, required, editor, template)
76
78
  if (value) {
@@ -92,17 +94,16 @@ export class DefaultTheme extends Theme {
92
94
  createTextEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
93
95
  let editor
94
96
  if (template.singleLine === false) {
95
- editor = document.createElement('textarea')
96
- editor.rows = 5
97
+ editor = new RokitTextArea()
98
+ editor.resize = 'auto'
97
99
  }
98
100
  else {
99
- editor = document.createElement('input')
100
- editor.type = 'text'
101
- if (template.pattern) {
102
- editor.pattern = template.pattern
103
- }
101
+ editor = new RokitInput()
102
+ }
103
+ editor.dense = true
104
+ if (template.pattern) {
105
+ editor.pattern = template.pattern
104
106
  }
105
-
106
107
  if (template.minLength) {
107
108
  editor.minLength = template.minLength
108
109
  }
@@ -126,10 +127,12 @@ export class DefaultTheme extends Theme {
126
127
  } else {
127
128
  langChooser = document.createElement('input')
128
129
  langChooser.maxLength = 5 // e.g. en-US
130
+ langChooser.size = 5
129
131
  langChooser.placeholder = 'lang?'
130
132
  }
131
133
  langChooser.title = 'Language of the text'
132
134
  langChooser.classList.add('lang-chooser')
135
+ langChooser.slot = 'suffix'
133
136
  // if lang chooser changes, fire a change event on the text input instead. this is for shacl validation handling.
134
137
  langChooser.addEventListener('change', (ev) => {
135
138
  ev.stopPropagation();
@@ -142,7 +145,7 @@ export class DefaultTheme extends Theme {
142
145
  langChooser.value = value.language
143
146
  }
144
147
  editor.dataset.lang = langChooser.value
145
- editor.after(langChooser)
148
+ editor.appendChild(langChooser)
146
149
  return result
147
150
  }
148
151
 
@@ -182,8 +185,10 @@ export class DefaultTheme extends Theme {
182
185
  }
183
186
 
184
187
  createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
185
- const editor = document.createElement('input')
188
+ const editor = new RokitInput()
186
189
  editor.type = 'number'
190
+ editor.clearable = true
191
+ editor.dense = true
187
192
  editor.classList.add('pr-0')
188
193
  const min = template.minInclusive !== undefined ? template.minInclusive : template.minExclusive !== undefined ? template.minExclusive + 1 : undefined
189
194
  const max = template.maxInclusive !== undefined ? template.maxInclusive : template.maxExclusive !== undefined ? template.maxExclusive - 1 : undefined
@@ -201,25 +206,29 @@ export class DefaultTheme extends Theme {
201
206
 
202
207
  createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
203
208
  const editor = new RokitSelect()
204
- editor.dense = true
205
209
  editor.clearable = true
206
- editor.collapse = true
210
+ editor.dense = true
207
211
  const result = this.createDefaultTemplate(label, null, required, editor, template)
208
212
  const ul = document.createElement('ul')
209
-
213
+ let isFlatList = true
214
+
210
215
  const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => {
211
216
  const li = document.createElement('li')
212
- let entryValue = ''
213
217
  if (typeof entry.value === 'string') {
214
- entryValue = entry.value
218
+ li.dataset.value = entry.value
219
+ li.innerText = entry.label ? entry.label : entry.value
215
220
  } else {
216
- // this is needed for typed rdf literals
217
- entryValue = (entry.value as N3Term).id
221
+ if (entry.value instanceof Literal && entry.value.datatype.equals(XSD_DATATYPE_STRING)) {
222
+ li.dataset.value = entry.value.value
223
+ } else {
224
+ // this is needed for typed rdf literals
225
+ li.dataset.value = (entry.value as N3Term).id
226
+ }
227
+ li.innerText = entry.label ? entry.label : entry.value.value
218
228
  }
219
- li.innerText = entry.label ? entry.label : entryValue
220
- li.dataset.value = entryValue
221
229
  parent.appendChild(li)
222
230
  if (entry.children?.length) {
231
+ isFlatList = false
223
232
  const ul = document.createElement('ul')
224
233
  li.appendChild(ul)
225
234
  for (const child of entry.children) {
@@ -231,6 +240,9 @@ export class DefaultTheme extends Theme {
231
240
  for (const item of listEntries) {
232
241
  appendListEntry(item, ul)
233
242
  }
243
+ if (!isFlatList) {
244
+ editor.collapse = true
245
+ }
234
246
 
235
247
  editor.appendChild(ul)
236
248
  if (value) {
@@ -6,7 +6,7 @@ import { InputListEntry, Editor } from '../theme'
6
6
  import { Literal, NamedNode } from 'n3'
7
7
  import { Term as N3Term } from 'n3'
8
8
  import css from './material.css?raw'
9
- import { PREFIX_SHACL, PREFIX_XSD } from '../constants'
9
+ import { PREFIX_SHACL, PREFIX_XSD, XSD_DATATYPE_STRING } from '../constants'
10
10
  import { RokitSelect } from '@ro-kit/ui-widgets'
11
11
 
12
12
  export class MaterialTheme extends Theme {
@@ -96,24 +96,28 @@ export class MaterialTheme extends Theme {
96
96
 
97
97
  createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
98
98
  const editor = new RokitSelect()
99
- editor.dense = true
100
99
  editor.clearable = true
101
100
  const result = this.createDefaultTemplate(label, null, required, editor, template)
102
101
  const ul = document.createElement('ul')
103
-
102
+ let isFlatList = true
103
+
104
104
  const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => {
105
105
  const li = document.createElement('li')
106
- let entryValue = ''
107
106
  if (typeof entry.value === 'string') {
108
- entryValue = entry.value
107
+ li.dataset.value = entry.value
108
+ li.innerText = entry.label ? entry.label : entry.value
109
109
  } else {
110
- // this is needed for typed rdf literals
111
- entryValue = (entry.value as N3Term).id
110
+ if (entry.value instanceof Literal && entry.value.datatype.equals(XSD_DATATYPE_STRING)) {
111
+ li.dataset.value = entry.value.value
112
+ } else {
113
+ // this is needed for typed rdf literals
114
+ li.dataset.value = (entry.value as N3Term).id
115
+ }
116
+ li.innerText = entry.label ? entry.label : entry.value.value
112
117
  }
113
- li.innerText = entry.label ? entry.label : entryValue
114
- li.dataset.value = entryValue
115
118
  parent.appendChild(li)
116
119
  if (entry.children?.length) {
120
+ isFlatList = false
117
121
  const ul = document.createElement('ul')
118
122
  li.appendChild(ul)
119
123
  for (const child of entry.children) {
@@ -125,6 +129,9 @@ export class MaterialTheme extends Theme {
125
129
  for (const item of listEntries) {
126
130
  appendListEntry(item, ul)
127
131
  }
132
+ if (!isFlatList) {
133
+ editor.collapse = true
134
+ }
128
135
 
129
136
  editor.appendChild(ul)
130
137
  if (value) {