@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.
- package/README.md +7 -4
- package/dist/constants.d.ts +1 -2
- package/dist/form-bootstrap.js +86 -85
- package/dist/form-default.js +83 -82
- package/dist/form-material.js +119 -118
- package/dist/plugins/leaflet.js +8 -8
- package/dist/plugins/mapbox.js +16 -16
- package/dist/property.d.ts +1 -0
- package/dist/theme.d.ts +1 -1
- package/dist/util.d.ts +2 -2
- package/package.json +8 -6
- package/src/constants.ts +2 -3
- package/src/constraints.ts +0 -1
- package/src/form.ts +17 -5
- package/src/group.ts +17 -18
- package/src/property-template.ts +1 -0
- package/src/property.ts +24 -22
- package/src/styles.css +12 -20
- package/src/theme.ts +1 -1
- package/src/themes/default.css +1 -1
- package/src/themes/default.ts +55 -44
- package/src/themes/material.ts +38 -37
- package/src/util.ts +19 -11
- package/dist/node-template.d.ts +0 -17
- package/src/node-template.ts +0 -82
package/dist/property.d.ts
CHANGED
|
@@ -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
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[]
|
|
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
|
|
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.
|
|
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.
|
|
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": "^
|
|
57
|
-
"vite-plugin-dts": "^4.5.
|
|
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.
|
|
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')
|
package/src/constraints.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
195
|
-
parent.
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
}
|
package/src/property-template.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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 {
|
|
1
|
+
form { display:block; --label-width: 8em; --caret-size: 10px; }
|
|
2
2
|
form.mode-edit { padding-left: 1em; }
|
|
3
|
-
form
|
|
4
|
-
shacl-node, .
|
|
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:
|
|
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:
|
|
21
|
-
.editor:not([type='checkbox'])
|
|
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 {
|
|
26
|
-
.
|
|
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 > .
|
|
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,
|
|
9
|
+
export type InputListEntry = { value: Term | string, label?: string, children?: InputListEntry[] }
|
|
10
10
|
|
|
11
11
|
export abstract class Theme {
|
|
12
12
|
stylesheet: CSSStyleSheet
|
package/src/themes/default.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.editor:not([type='checkbox']) { border: 1px solid #DDD;
|
|
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); }
|
package/src/themes/default.ts
CHANGED
|
@@ -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
|
|
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 =
|
|
93
|
-
editor.
|
|
97
|
+
editor = new RokitTextArea()
|
|
98
|
+
editor.resize = 'auto'
|
|
94
99
|
}
|
|
95
100
|
else {
|
|
96
|
-
editor =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (typeof
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (item.indent) {
|
|
216
|
-
for (let i = 0; i < item.indent; i++) {
|
|
217
|
-
option.innerHTML = '  ' + 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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
}
|