@ulb-darmstadt/shacl-form 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.1",
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.28",
73
75
  "mdui": "^2.1.4"
74
76
  }
75
77
  }
package/src/constants.ts CHANGED
@@ -17,9 +17,7 @@ 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')
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'
@@ -103,7 +103,7 @@ export class ShaclProperty extends HTMLElement {
103
103
  } else {
104
104
  // check if value is part of the data graph. if not, create a linked resource
105
105
  let linked = false
106
- if (value) {
106
+ if (value && !(value instanceof Literal)) {
107
107
  const clazz = this.getRdfClassToLinkOrCreate()
108
108
  if (clazz && this.template.config.store.countQuads(value, RDF_PREDICATE_TYPE, clazz, DATA_GRAPH) === 0) {
109
109
  // value is not in data graph, so must be a link in the shapes graph
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,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 } 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 { RokitSelect } 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
@@ -197,43 +200,39 @@ export class DefaultTheme extends Theme {
197
200
  }
198
201
 
199
202
  createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
200
- const editor = document.createElement('select')
203
+ const editor = new RokitSelect()
204
+ editor.dense = true
205
+ editor.clearable = true
206
+ editor.collapse = true
201
207
  const result = this.createDefaultTemplate(label, null, required, editor, template)
202
- let addEmptyOption = true
208
+ const ul = document.createElement('ul')
203
209
 
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
210
+ const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => {
211
+ const li = document.createElement('li')
212
+ let entryValue = ''
213
+ if (typeof entry.value === 'string') {
214
+ entryValue = entry.value
209
215
  } else {
210
216
  // this is needed for typed rdf literals
211
- itemValue = (item.value as N3Term).id
217
+ entryValue = (entry.value as N3Term).id
212
218
  }
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
219
+ li.innerText = entry.label ? entry.label : entryValue
220
+ li.dataset.value = entryValue
221
+ parent.appendChild(li)
222
+ if (entry.children?.length) {
223
+ const ul = document.createElement('ul')
224
+ li.appendChild(ul)
225
+ for (const child of entry.children) {
226
+ appendListEntry(child, ul)
218
227
  }
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
234
- }
235
- editor.prepend(emptyOption)
236
229
  }
230
+
231
+ for (const item of listEntries) {
232
+ appendListEntry(item, ul)
233
+ }
234
+
235
+ editor.appendChild(ul)
237
236
  if (value) {
238
237
  editor.value = value.value
239
238
  }
@@ -1,12 +1,13 @@
1
1
  import { ShaclPropertyTemplate } from '../property-template'
2
2
  import { Term } from '@rdfjs/types'
3
- import { Button, TextField, Select, MenuItem, Checkbox } from 'mdui'
3
+ import { Button, TextField, Checkbox } from 'mdui'
4
4
  import { Theme } from '../theme'
5
5
  import { InputListEntry, Editor } from '../theme'
6
- import { Literal } from 'n3'
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_XSD } from '../constants'
9
+ import { PREFIX_SHACL, PREFIX_XSD } from '../constants'
10
+ import { RokitSelect } from '@ro-kit/ui-widgets'
10
11
 
11
12
  export class MaterialTheme extends Theme {
12
13
  constructor() {
@@ -29,6 +30,8 @@ export class MaterialTheme extends Theme {
29
30
  }
30
31
  if (template?.nodeKind) {
31
32
  editor.dataset.nodeKind = template.nodeKind.value
33
+ } else if (value instanceof NamedNode) {
34
+ editor.dataset.nodeKind = PREFIX_SHACL + 'IRI'
32
35
  }
33
36
  if (template?.hasValue || template?.readonly) {
34
37
  editor.disabled = true
@@ -92,47 +95,38 @@ export class MaterialTheme extends Theme {
92
95
  }
93
96
 
94
97
  createListEditor(label: string, value: Term | null, required: boolean, listEntries: InputListEntry[], template?: ShaclPropertyTemplate): HTMLElement {
95
- const editor = new Select()
96
- editor.variant = 'outlined'
97
- editor.label = label
98
- editor.helper = template?.description?.value
98
+ const editor = new RokitSelect()
99
+ editor.dense = true
99
100
  editor.clearable = true
100
- // @ts-ignore
101
- const result = this.createDefaultTemplate('', null, required, editor, template)
102
- let addEmptyOption = true
101
+ const result = this.createDefaultTemplate(label, null, required, editor, template)
102
+ const ul = document.createElement('ul')
103
103
 
104
- for (const item of listEntries) {
105
- const option = new MenuItem()
106
- let itemValue = ''
107
- if (typeof item.value === 'string') {
108
- itemValue = item.value
104
+ const appendListEntry = (entry: InputListEntry, parent: HTMLUListElement) => {
105
+ const li = document.createElement('li')
106
+ let entryValue = ''
107
+ if (typeof entry.value === 'string') {
108
+ entryValue = entry.value
109
109
  } else {
110
110
  // this is needed for typed rdf literals
111
- itemValue = (item.value as N3Term).id
111
+ entryValue = (entry.value as N3Term).id
112
112
  }
113
- const itemLabel = item.label ? item.label : itemValue
114
- option.value = itemValue
115
- option.textContent = itemLabel || itemValue
116
- // if (value && value.value === itemValue) {
117
- // option.selected = true
118
- // }
119
- if (item.indent) {
120
- for (let i = 0; i < item.indent; i++) {
121
- option.innerHTML = '&#160;&#160;' + option.innerHTML
113
+ li.innerText = entry.label ? entry.label : entryValue
114
+ li.dataset.value = entryValue
115
+ parent.appendChild(li)
116
+ if (entry.children?.length) {
117
+ const ul = document.createElement('ul')
118
+ li.appendChild(ul)
119
+ for (const child of entry.children) {
120
+ appendListEntry(child, ul)
122
121
  }
123
122
  }
124
- if (itemValue === '') {
125
- addEmptyOption = false
126
- option.ariaLabel = 'blank'
127
- }
128
- editor.appendChild(option)
129
123
  }
130
- if (addEmptyOption) {
131
- // add an empty element
132
- const empty = new MenuItem()
133
- empty.ariaLabel = 'blank'
134
- editor.prepend(empty)
124
+
125
+ for (const item of listEntries) {
126
+ appendListEntry(item, ul)
135
127
  }
128
+
129
+ editor.appendChild(ul)
136
130
  if (value) {
137
131
  editor.value = value.value
138
132
  }
package/src/util.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Literal, NamedNode, Prefixes, Quad, Store } from 'n3'
2
- import { DATA_GRAPH, OWL_OBJECT_NAMED_INDIVIDUAL, PREFIX_FOAF, PREFIX_RDFS, PREFIX_SHACL, PREFIX_SKOS, RDFS_PREDICATE_SUBCLASS_OF, RDF_PREDICATE_TYPE, SHAPES_GRAPH, SKOS_PREDICATE_BROADER } from './constants'
2
+ import { DATA_GRAPH, PREFIX_FOAF, PREFIX_RDFS, PREFIX_SHACL, PREFIX_SKOS, RDFS_PREDICATE_SUBCLASS_OF, RDF_PREDICATE_TYPE, SHAPES_GRAPH } from './constants'
3
3
  import { Term } from '@rdfjs/types'
4
4
  import { InputListEntry } from './theme'
5
5
  import { ShaclPropertyTemplate } from './property-template'
@@ -53,10 +53,10 @@ export function findLabel(quads: Quad[], languages: string[]): string {
53
53
  findObjectValueByPredicate(quads, 'name', PREFIX_FOAF, languages)
54
54
  }
55
55
 
56
- export function createInputListEntries(subjects: Term[], shapesGraph: Store, languages: string[], indent?: number): InputListEntry[] {
56
+ export function createInputListEntries(subjects: Term[], shapesGraph: Store, languages: string[]): InputListEntry[] {
57
57
  const entries: InputListEntry[] = []
58
58
  for (const subject of subjects) {
59
- entries.push({ value: subject, label: findLabel(shapesGraph.getQuads(subject, null, null, null), languages), indent: indent })
59
+ entries.push({ value: subject, label: findLabel(shapesGraph.getQuads(subject, null, null, null), languages), children: [] })
60
60
  }
61
61
  return entries
62
62
  }
@@ -82,7 +82,7 @@ function findClassInstancesFromOwlImports(clazz: NamedNode, context: ShaclNode |
82
82
  }
83
83
  }
84
84
 
85
- export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate, indent = 0): InputListEntry[] {
85
+ export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplate): InputListEntry[] {
86
86
  let instances: Term[]
87
87
  // if template has sh:in, then just use that as class instances
88
88
  if (template.shaclIn) {
@@ -97,16 +97,24 @@ export function findInstancesOf(clazz: NamedNode, template: ShaclPropertyTemplat
97
97
  findClassInstancesFromOwlImports(clazz, template, template.config.store, instances)
98
98
  }
99
99
 
100
- const entries = createInputListEntries(instances, template.config.store, template.config.languages, indent)
100
+ const entries = createInputListEntries(instances, template.config.store, template.config.languages)
101
101
  // build inheritance tree only if sh:in is not defined
102
102
  if (template.shaclIn === undefined) {
103
+ // find sub classes via rdfs:subClassOf
103
104
  for (const subClass of template.config.store.getSubjects(RDFS_PREDICATE_SUBCLASS_OF, clazz, null)) {
104
- entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
105
- }
106
- if (template.config.store.getQuads(clazz, RDF_PREDICATE_TYPE, OWL_OBJECT_NAMED_INDIVIDUAL, null).length > 0) {
107
- entries.push(...createInputListEntries([ clazz ], template.config.store, template.config.languages, indent))
108
- for (const subClass of template.config.store.getSubjects(SKOS_PREDICATE_BROADER, clazz, null)) {
109
- entries.push(...findInstancesOf(subClass as NamedNode, template, indent + 1))
105
+ const subClassIntances = findInstancesOf(subClass as NamedNode, template)
106
+ for (const subClassIntance of subClassIntances) {
107
+ let isChild = false
108
+ // check if found sub class also is an instance of its super class. if yes, add it to the children of the InputListEntry.
109
+ for (const entry of entries) {
110
+ if (template.config.store.countQuads(subClassIntance.value, RDF_PREDICATE_TYPE, entry.value, null) > 0) {
111
+ entry.children!.push(subClassIntance)
112
+ isChild = true
113
+ }
114
+ }
115
+ if (!isChild) {
116
+ entries.push(subClassIntance)
117
+ }
110
118
  }
111
119
  }
112
120
  }
@@ -1,17 +0,0 @@
1
- import { BlankNode, NamedNode, Quad } from 'n3';
2
- import { Config } from './config';
3
- import { Term } from '@rdfjs/types';
4
- export declare class ShaclNodeTemplate {
5
- properties: (NamedNode | BlankNode)[];
6
- node: NamedNode | undefined;
7
- shaclAnd: string | undefined;
8
- shaclOr: Term[] | undefined;
9
- shaclXone: Term[] | undefined;
10
- targetClass: NamedNode | undefined;
11
- owlImports: NamedNode[];
12
- config: Config;
13
- extendedShapes: NamedNode[];
14
- constructor(quads: Quad[], config: Config);
15
- merge(quads: Quad[]): ShaclNodeTemplate;
16
- clone(): ShaclNodeTemplate;
17
- }
@@ -1,82 +0,0 @@
1
- import { BlankNode, NamedNode, Quad } from "n3"
2
- import { Config } from "./config"
3
- import { OWL_PREDICATE_IMPORTS, PREFIX_SHACL } from "./constants";
4
- import { Term } from "@rdfjs/types";
5
-
6
- const mappers: Record<string, (template: ShaclNodeTemplate, term: Term) => void> = {
7
- [`${PREFIX_SHACL}property`]: (template, term) => { template.properties.push(term as (BlankNode | NamedNode)) },
8
- [`${PREFIX_SHACL}and`]: (template, term) => { template.shaclAnd = term.value },
9
- [`${PREFIX_SHACL}node`]: (template, term) => { template.node = term as NamedNode },
10
- [`${PREFIX_SHACL}targetClass`]: (template, term) => { template.targetClass = term as NamedNode },
11
- [`${PREFIX_SHACL}or`]: (template, term) => {
12
- const list = template.config.lists[term.value]
13
- if (list?.length) {
14
- template.shaclOr = list
15
- } else {
16
- console.error('list for sh:or not found:', term.value, 'existing lists:', template.config.lists)
17
- }
18
- },
19
- [`${PREFIX_SHACL}xone`]: (template, term) => {
20
- const list = template.config.lists[term.value]
21
- if (list?.length) {
22
- template.shaclXone = list
23
- } else {
24
- console.error('list for sh:xone not found:', term.value, 'existing lists:', template.config.lists)
25
- }
26
- },
27
- [OWL_PREDICATE_IMPORTS.id]: (template, term) => { template.owlImports.push(term as NamedNode) }
28
- }
29
-
30
- export class ShaclNodeTemplate {
31
- properties: (NamedNode | BlankNode)[] = []
32
- node: NamedNode | undefined
33
- shaclAnd: string | undefined
34
- shaclOr: Term[] | undefined
35
- shaclXone: Term[] | undefined
36
- targetClass: NamedNode | undefined
37
-
38
- owlImports: NamedNode[] = []
39
- config: Config
40
- extendedShapes: NamedNode[] = []
41
-
42
- constructor(quads: Quad[], config: Config) {
43
- this.config = config
44
- this.merge(quads)
45
- }
46
-
47
- merge(quads: Quad[]): ShaclNodeTemplate {
48
- for (const quad of quads) {
49
- mappers[quad.predicate.id]?.call(this, this, quad.object)
50
- }
51
- // resolve extended shapes
52
- if (this.node || this.shaclAnd) {
53
- if (this.node) {
54
- this.extendedShapes.push(this.node)
55
- }
56
- if (this.shaclAnd) {
57
- const list = this.config.lists[this.shaclAnd]
58
- if (list?.length) {
59
- for (const node of list) {
60
- this.extendedShapes.push(node as NamedNode)
61
- }
62
- }
63
- }
64
- }
65
- return this
66
- }
67
-
68
- clone(): ShaclNodeTemplate {
69
- const copy = Object.assign({}, this)
70
- // arrays are not cloned but referenced, so create them manually
71
- copy.owlImports = [ ...this.owlImports ]
72
- if (this.shaclOr) {
73
- copy.shaclOr = [ ...this.shaclOr ]
74
- }
75
- if (this.shaclXone) {
76
- copy.shaclXone = [ ...this.shaclXone ]
77
- }
78
- copy.merge = this.merge.bind(copy)
79
- copy.clone = this.clone.bind(copy)
80
- return copy
81
- }
82
- }