@ulb-darmstadt/shacl-form 1.8.0 → 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/dist/theme.d.ts CHANGED
@@ -12,7 +12,7 @@ export type Editor = HTMLElement & {
12
12
  export type InputListEntry = {
13
13
  value: Term | string;
14
14
  label?: string;
15
- indent?: number;
15
+ children?: InputListEntry[];
16
16
  };
17
17
  export declare abstract class Theme {
18
18
  stylesheet: CSSStyleSheet;
package/dist/util.d.ts CHANGED
@@ -6,8 +6,8 @@ export declare function findObjectValueByPredicate(quads: Quad[], predicate: str
6
6
  export declare function findObjectByPredicate(quads: Quad[], predicate: string, prefix?: string, languages?: string[]): Term | undefined;
7
7
  export declare function focusFirstInputElement(context: HTMLElement): void;
8
8
  export declare function findLabel(quads: Quad[], languages: string[]): string;
9
- export declare function createInputListEntries(subjects: Term[], shapesGraph: Store, languages: string[], indent?: number): InputListEntry[];
9
+ export declare function createInputListEntries(subjects: Term[], shapesGraph: Store, languages: string[]): InputListEntry[];
10
10
  export declare function removePrefixes(id: string, prefixes: Prefixes): string;
11
- export declare function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate, indent?: number): InputListEntry[];
11
+ export declare function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate): InputListEntry[];
12
12
  export declare function isURL(input: string): boolean;
13
13
  export declare function prioritizeByLanguage(languages: string[], text1?: Literal, text2?: Literal): Literal | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulb-darmstadt/shacl-form",
3
- "version": "1.8.0",
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",
@@ -10,7 +10,9 @@
10
10
  ".": {
11
11
  "import": "./dist/form-default.js",
12
12
  "types": "./dist/form-default.d.ts"
13
- }
13
+ },
14
+ "./*.ts": "./src/*.ts",
15
+ "./plugins/*.ts": "./src/plugins/*.ts"
14
16
  },
15
17
  "files": [
16
18
  "dist",
@@ -45,7 +47,7 @@
45
47
  },
46
48
  "devDependencies": {
47
49
  "@types/jsonld": "^1.5.15",
48
- "@types/leaflet": "^1.9.19",
50
+ "@types/leaflet": "^1.9.20",
49
51
  "@types/leaflet-editable": "^1.2.6",
50
52
  "@types/leaflet.fullscreen": "^3.0.2",
51
53
  "@types/mapbox__mapbox-gl-draw": "^1.4.9",
@@ -53,8 +55,8 @@
53
55
  "@types/uuid": "^10.0.0",
54
56
  "rollup-plugin-peer-deps-external": "^2.2.4",
55
57
  "typescript": "^5.8.3",
56
- "vite": "^6.2.5",
57
- "vite-plugin-dts": "^4.5.3"
58
+ "vite": "^7.0.4",
59
+ "vite-plugin-dts": "^4.5.4"
58
60
  },
59
61
  "dependencies": {
60
62
  "@mapbox/mapbox-gl-draw": "^1.5.0",
@@ -69,7 +71,7 @@
69
71
  "uuid": "^11.1.0"
70
72
  },
71
73
  "peerDependencies": {
72
- "@ro-kit/ui-widgets": "^0.0.27",
74
+ "@ro-kit/ui-widgets": "^0.0.29",
73
75
  "mdui": "^2.1.4"
74
76
  }
75
77
  }
package/src/constants.ts CHANGED
@@ -17,12 +17,11 @@ export const DATA_GRAPH = DataFactory.namedNode('loaded-data')
17
17
  export const RDF_PREDICATE_TYPE = DataFactory.namedNode(PREFIX_RDF + 'type')
18
18
  export const DCTERMS_PREDICATE_CONFORMS_TO = DataFactory.namedNode(PREFIX_DCTERMS + 'conformsTo')
19
19
  export const RDFS_PREDICATE_SUBCLASS_OF = DataFactory.namedNode(PREFIX_RDFS + 'subClassOf')
20
- export const SKOS_PREDICATE_BROADER = DataFactory.namedNode(PREFIX_SKOS + 'broader')
21
20
  export const OWL_PREDICATE_IMPORTS = DataFactory.namedNode(PREFIX_OWL + 'imports')
22
- export const OWL_OBJECT_NAMED_INDIVIDUAL = DataFactory.namedNode(PREFIX_OWL + 'NamedIndividual')
23
21
  export const SHACL_OBJECT_NODE_SHAPE = DataFactory.namedNode(PREFIX_SHACL + 'NodeShape')
24
22
  export const SHACL_OBJECT_IRI = DataFactory.namedNode(PREFIX_SHACL + 'IRI')
25
23
  export const SHACL_PREDICATE_PROPERTY = DataFactory.namedNode(PREFIX_SHACL + 'property')
26
24
  export const SHACL_PREDICATE_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'class')
27
25
  export const SHACL_PREDICATE_TARGET_CLASS = DataFactory.namedNode(PREFIX_SHACL + 'targetClass')
28
- 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
@@ -1,4 +1,4 @@
1
- import { BlankNode, DataFactory, NamedNode, Quad, Store } from 'n3'
1
+ import { BlankNode, DataFactory, Literal, NamedNode, Quad, Store } from 'n3'
2
2
  import { Term } from '@rdfjs/types'
3
3
  import { ShaclNode } from './node'
4
4
  import { createShaclOrConstraint, resolveShaclOrConstraintOnProperty } from './constraints'
@@ -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
  }
@@ -103,7 +102,7 @@ export class ShaclProperty extends HTMLElement {
103
102
  } else {
104
103
  // check if value is part of the data graph. if not, create a linked resource
105
104
  let linked = false
106
- if (value) {
105
+ if (value && !(value instanceof Literal)) {
107
106
  const clazz = this.getRdfClassToLinkOrCreate()
108
107
  if (clazz && this.template.config.store.countQuads(value, RDF_PREDICATE_TYPE, clazz, DATA_GRAPH) === 0) {
109
108
  // value is not in data graph, so must be a link in the shapes graph
@@ -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; }
package/src/theme.ts CHANGED
@@ -6,7 +6,7 @@ import { ShaclPropertyTemplate } from './property-template'
6
6
  import css from './styles.css?raw'
7
7
 
8
8
  export type Editor = HTMLElement & { value: string, type?: string, shaclDatatype?: NamedNode<string>, binaryData?: string, checked?: boolean, disabled?: boolean }
9
- export type InputListEntry = { value: Term | string, label?: string, indent?: number }
9
+ export type InputListEntry = { value: Term | string, label?: string, children?: InputListEntry[] }
10
10
 
11
11
  export abstract class Theme {
12
12
  stylesheet: CSSStyleSheet
@@ -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,10 +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_XSD } from '../constants'
5
- import { Literal } from 'n3'
4
+ import { PREFIX_SHACL, PREFIX_XSD, XSD_DATATYPE_STRING } from '../constants'
5
+ import { Literal, NamedNode } from 'n3'
6
6
  import { Term as N3Term } from 'n3'
7
7
  import css from './default.css?raw'
8
+ import { RokitInput, RokitSelect, RokitTextArea } from '@ro-kit/ui-widgets'
8
9
 
9
10
  export class DefaultTheme extends Theme {
10
11
  idCtr = 0
@@ -30,6 +31,8 @@ export class DefaultTheme extends Theme {
30
31
  }
31
32
  if (template?.nodeKind) {
32
33
  editor.dataset.nodeKind = template.nodeKind.value
34
+ } else if (value instanceof NamedNode) {
35
+ editor.dataset.nodeKind = PREFIX_SHACL + 'IRI'
33
36
  }
34
37
  if (template?.hasValue || template?.readonly) {
35
38
  editor.disabled = true
@@ -59,7 +62,7 @@ export class DefaultTheme extends Theme {
59
62
  }
60
63
 
61
64
  createDateEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
62
- const editor: Editor = document.createElement('input')
65
+ const editor = new RokitInput()
63
66
  if (template.datatype?.value === PREFIX_XSD + 'dateTime') {
64
67
  editor.type = 'datetime-local'
65
68
  // this enables seconds in dateTime input
@@ -68,6 +71,8 @@ export class DefaultTheme extends Theme {
68
71
  else {
69
72
  editor.type = 'date'
70
73
  }
74
+ editor.clearable = true
75
+ editor.dense = true
71
76
  editor.classList.add('pr-0')
72
77
  const result = this.createDefaultTemplate(label, null, required, editor, template)
73
78
  if (value) {
@@ -89,17 +94,16 @@ export class DefaultTheme extends Theme {
89
94
  createTextEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
90
95
  let editor
91
96
  if (template.singleLine === false) {
92
- editor = document.createElement('textarea')
93
- editor.rows = 5
97
+ editor = new RokitTextArea()
98
+ editor.resize = 'auto'
94
99
  }
95
100
  else {
96
- editor = document.createElement('input')
97
- editor.type = 'text'
98
- if (template.pattern) {
99
- editor.pattern = template.pattern
100
- }
101
+ editor = new RokitInput()
102
+ }
103
+ editor.dense = true
104
+ if (template.pattern) {
105
+ editor.pattern = template.pattern
101
106
  }
102
-
103
107
  if (template.minLength) {
104
108
  editor.minLength = template.minLength
105
109
  }
@@ -123,10 +127,12 @@ export class DefaultTheme extends Theme {
123
127
  } else {
124
128
  langChooser = document.createElement('input')
125
129
  langChooser.maxLength = 5 // e.g. en-US
130
+ langChooser.size = 5
126
131
  langChooser.placeholder = 'lang?'
127
132
  }
128
133
  langChooser.title = 'Language of the text'
129
134
  langChooser.classList.add('lang-chooser')
135
+ langChooser.slot = 'suffix'
130
136
  // if lang chooser changes, fire a change event on the text input instead. this is for shacl validation handling.
131
137
  langChooser.addEventListener('change', (ev) => {
132
138
  ev.stopPropagation();
@@ -139,7 +145,7 @@ export class DefaultTheme extends Theme {
139
145
  langChooser.value = value.language
140
146
  }
141
147
  editor.dataset.lang = langChooser.value
142
- editor.after(langChooser)
148
+ editor.appendChild(langChooser)
143
149
  return result
144
150
  }
145
151
 
@@ -179,8 +185,10 @@ export class DefaultTheme extends Theme {
179
185
  }
180
186
 
181
187
  createNumberEditor(label: string, value: Term | null, required: boolean, template: ShaclPropertyTemplate): HTMLElement {
182
- const editor = document.createElement('input')
188
+ const editor = new RokitInput()
183
189
  editor.type = 'number'
190
+ editor.clearable = true
191
+ editor.dense = true
184
192
  editor.classList.add('pr-0')
185
193
  const min = template.minInclusive !== undefined ? template.minInclusive : template.minExclusive !== undefined ? template.minExclusive + 1 : undefined
186
194
  const max = template.maxInclusive !== undefined ? template.maxInclusive : template.maxExclusive !== undefined ? template.maxExclusive - 1 : undefined
@@ -197,43 +205,46 @@ export class DefaultTheme extends Theme {
197
205
  }
198
206
 
199
207
  createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
200
- const editor = document.createElement('select')
208
+ const editor = new RokitSelect()
209
+ editor.clearable = true
210
+ editor.dense = true
201
211
  const result = this.createDefaultTemplate(label, null, required, editor, template)
202
- let addEmptyOption = true
203
-
204
- for (const item of listEntries) {
205
- const option = document.createElement('option')
206
- let itemValue = ''
207
- if (typeof item.value === 'string') {
208
- itemValue = item.value
212
+ const ul = document.createElement('ul')
213
+ let isFlatList = true
214
+
215
+ const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => {
216
+ const li = document.createElement('li')
217
+ if (typeof entry.value === 'string') {
218
+ li.dataset.value = entry.value
219
+ li.innerText = entry.label ? entry.label : entry.value
209
220
  } else {
210
- // this is needed for typed rdf literals
211
- itemValue = (item.value as N3Term).id
212
- }
213
- option.innerHTML = item.label ? item.label : itemValue
214
- option.value = itemValue
215
- if (item.indent) {
216
- for (let i = 0; i < item.indent; i++) {
217
- option.innerHTML = '&#160;&#160;' + option.innerHTML
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
218
226
  }
227
+ li.innerText = entry.label ? entry.label : entry.value.value
219
228
  }
220
- if (value && value.value === itemValue) {
221
- option.selected = true
222
- }
223
- if (itemValue === '') {
224
- addEmptyOption = false
225
- }
226
- editor.appendChild(option)
227
- }
228
- if (addEmptyOption) {
229
- // add an empty element
230
- const emptyOption = document.createElement('option')
231
- emptyOption.value = ''
232
- if (!value) {
233
- emptyOption.selected = true
229
+ parent.appendChild(li)
230
+ if (entry.children?.length) {
231
+ isFlatList = false
232
+ const ul = document.createElement('ul')
233
+ li.appendChild(ul)
234
+ for (const child of entry.children) {
235
+ appendListEntry(child, ul)
236
+ }
234
237
  }
235
- editor.prepend(emptyOption)
236
238
  }
239
+
240
+ for (const item of listEntries) {
241
+ appendListEntry(item, ul)
242
+ }
243
+ if (!isFlatList) {
244
+ editor.collapse = true
245
+ }
246
+
247
+ editor.appendChild(ul)
237
248
  if (value) {
238
249
  editor.value = value.value
239
250
  }