Skip to content

Commit

Permalink
improved theming and documentation
Browse files Browse the repository at this point in the history
s-tittel committed Nov 1, 2023
1 parent 8b42716 commit 245d128
Showing 20 changed files with 386 additions and 537 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -60,9 +60,11 @@ data-values | RDF triples (e.g. a turtle string) to use as existing data graph t
data-values-url | When `data-values` is not set, the data graph triples are loaded from this URL
data-value-subject | The subject (id) of the generated data. If this is not set, a blank node with a new UUID will be used. If `data-values` or `data-values-url` is set, this id is also used to find the root node in the data graph to fill the form
data-language | Language to use if shapes contain langStrings, e.g. in `sh:name` or `rdfs:label`. Default is [`navigator.language`](https://www.w3schools.com/jsref/prop_nav_language.asp)
data‑ignore‑owl‑imports | By default, `owl:imports` URLs are fetched and the resulting RDF triples are added to the shapes graph. Set this attribute to any value in order to disable this feature
data-loading | Text to display while the web component is initializing. Default: `"Loading..."`
data‑ignore‑owl‑imports | By default, `owl:imports` URLs are fetched and the resulting RDF triples are added to the shapes graph. Setting this attribute to any value disables this feature
data-mode | When set to `"view"`, turns the web component into a viewer that displays the given data graph without editing functionality
data-submit-button | Whether to add a submit button to the form. The value of this attribute is used as the button label. `submit` events will only fire after successful validation
data-collapse | When this attribute is present or set to any value, `sh:group`s and properties with `sh:node` and `sh:maxCount` > 1 are displayed in a collapsible accordion-like widget. This enhances readability in edit and view mode
data-submit-button | [Only used in edit mode] Whether to add a submit button to the form. The value of this attribute is used as the button label. `submit` events will only fire after successful validation
### Element functions
```typescript
@@ -92,15 +94,15 @@ Sets a callback function that is called when a SHACL property has an `sh:class`
- `example:Instance a example:Class`
- `example:Instance a owl:NamedIndividual; skos:broader example:Class`
You can also use `rdfs:subClassOf` or `skos:broader` to build class hierarchies.
Class hierarchies can be built using `rdfs:subClassOf` or `skos:broader`.
## Theming
`<shacl-form>` comes in 3 different bundles, each providing a specific theme:
Theme | Import statement
--- | ---
Native (opinionated raw HTML) | `import '@ulb-darmstadt/shacl-form/index.js'`
[Bootstrap](./src/themes/bootstrap.ts) | `import '@ulb-darmstadt/shacl-form/bootstrap.js'`
[Material Design](./src/themes/material.ts) | `import '@ulb-darmstadt/shacl-form/material.js'`
Default (custom CSS) | `import '@ulb-darmstadt/shacl-form/index.js'`
[Bootstrap](./src/themes/bootstrap.ts) [alpha status] | `import '@ulb-darmstadt/shacl-form/bootstrap.js'`
[Material Design](./src/themes/material.ts) [alpha status] | `import '@ulb-darmstadt/shacl-form/material.js'`
Custom themes can be employed by implementing class [Theme](./src/theme.ts) yourself, then activating it with function `setTheme()` on the `<shacl-form>` element.
Custom themes can be employed by implementing class [Theme](./src/theme.ts), then activating it with function `setTheme()` on the `<shacl-form>` element.
9 changes: 5 additions & 4 deletions demo/complex-example.ttl
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ example:ArchitectureModelDataset
sh:node example:Dataset ;

sh:property [ sh:description "Location of the building" ;
sh:name "Location" ;
sh:name "Locations" ;
sh:node example:Location ;
sh:path dcterms:spatial
] ;
@@ -101,9 +101,9 @@ example:Dataset
sh:name "Issued" ;
sh:path dcterms:issued
] ;
sh:property [ sh:name "Attribution" ;
sh:property [ sh:name "Attributions" ;
sh:node example:Attribution ;
sh:path prov:qualifiedAttribution
sh:path prov:qualifiedAttribution ;
] ;
sh:targetClass dcat:Dataset .

@@ -122,7 +122,8 @@ example:Location
sh:path geo:asWKT ;
sh:pattern "^POINT\\([+\\-]?(?:[0-9]*[.])?[0-9]+ [+\\-]?(?:[0-9]*[.])?[0-9]+\\)|POLYGON\\(\\((?:[+\\-]?(?:[0-9]*[.])?[0-9]+[ ,]?){3,}\\)\\)$"
] ;
sh:property [ sh:description "Description of the location" ;
sh:property [ dash:singleLine false ;
sh:description "Description of the location" ;
sh:maxCount 1 ;
sh:name "Description" ;
sh:path dcterms:description
51 changes: 32 additions & 19 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -19,7 +19,6 @@
import '@ulb-darmstadt/shacl-form/index.js'
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/dark.min.css">
<link href=" https://cdn.jsdelivr.net/npm/[email protected]/css/fonts.min.css " rel="stylesheet">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
</head>

@@ -30,6 +29,7 @@ <h1>&lt;shacl-form&gt; demo</h1>
<div class="menu">
<a id="intro" href="#intro">Basic usage</a>
<a id="datatypes" href="#datatypes">Data types</a>
<a id="theming" href="#theming">Theming</a>
<a id="complex-example" href="#complex-example">Complex example</a>
<a id="try-your-own" href="#try-your-own">Try your own</a>
</div>
@@ -70,11 +70,14 @@ <h1>&lt;shacl-form&gt; demo</h1>
</script>

<template id="template-intro">
<script type="module">
import '@ulb-darmstadt/shacl-form/index.js'
</script>
<p>
<a href="https://github.com/ULB-Darmstadt/shacl-form">&lt;shacl-form&gt;</a> is an HTML5 web component that takes <a href="https://www.w3.org/TR/shacl/">SHACL shapes</a> as input and generates an HTML form, allowing to enter data that conform to the given shapes.
See the <a href="https://github.com/ULB-Darmstadt/shacl-form">README</a> for documentation of all element attributes, functions and supported input/output RDF formats.
Basic usage example:
</p>
<p>Basic usage example:</p>
<pre>
<code>
&lt;html&gt;
@@ -173,21 +176,28 @@ <h1>&lt;shacl-form&gt; demo</h1>
</script>
</template>

<template id="template-theming">
<div class="wrapper">
<fieldset><legend>Default</legend><iframe src="theme-default.html"></iframe></fieldset>
<fieldset><legend>Material</legend><iframe src="theme-material.html"></iframe></fieldset>
<fieldset><legend>Bootstrap</legend><iframe src="theme-bootstrap.html"></iframe></fieldset>
</div>
</template>

<template id="template-complex-example">
<p>This is a more complex example, demonstrating the advanced features of &lt;shacl-form&gt;. See below for an explanation.</p>
<div class="wrapper">
<fieldset><legend>SHACL shapes as input to the form</legend><pre id="shacl-shape-input"></pre></fieldset>
<fieldset><legend>Data graph as input to the form</legend><pre id="shacl-data-input"></pre></fieldset>
<fieldset><legend>Generated form</legend><shacl-form id="shacl-form" data-submit-button data-mode="view2" data-value-subject="http://example.org/4f2a8de3-9fc8-40a9-9237-d5964520ec54"></shacl-form></fieldset>
<fieldset><legend>Generated form</legend><shacl-form id="shacl-form" data-submit-button data-collapse="open2" data-mode="view2" data-value-subject="http://example.org/4f2a8de3-9fc8-40a9-9237-d5964520ec54"></shacl-form></fieldset>
</div>
<fieldset id="shacl-output" class="mt-1"><legend>Output generated by the form</legend><pre></pre></fieldset>
<script type="module">
import { MapBoxPlugin } from '@ulb-darmstadt/shacl-form/plugins/mapbox.js'
import { MapboxPlugin } from '@ulb-darmstadt/shacl-form/plugins/mapbox.js'

setTimeout(async () => {
const form = document.getElementById("shacl-form")
form.registerPlugin(new MapBoxPlugin({ datatype: 'http://www.opengis.net/ont/geosparql#wktLiteral' }, 'pk.eyJ1IjoiaHViZXJtb3NlciIsImEiOiJja3c2NDI2MXAwbWx0MnVudnJiOGV6NjRqIn0.va4IWkUk-USoL2Z8FylNzA'))
// form.importPlugin('MapBoxPlugin', '@ulb-darmstadt/shacl-form/plugins/mapbox.js', { datatype: 'http://www.opengis.net/ont/geosparql#wktLiteral' })
form.registerPlugin(new MapboxPlugin({ datatype: 'http://www.opengis.net/ont/geosparql#wktLiteral' }, 'pk.eyJ1IjoiaHViZXJtb3NlciIsImEiOiJja3c2NDI2MXAwbWx0MnVudnJiOGV6NjRqIn0.va4IWkUk-USoL2Z8FylNzA'))

const shapes = document.getElementById("shacl-shape-input")
const data = document.getElementById("shacl-data-input")
@@ -220,7 +230,7 @@ <h1>&lt;shacl-form&gt; demo</h1>
form.dataset['values'] = dataTTL
})
</script>
<p class="mt-1">The above uses the following features:</p>
<p class="mt-1">The example above uses the following features:</p>
<h2>SHACL shape inheritance</h2>
<p></p>
<h2>Providing additional data to the shapes graph</h2>
@@ -231,30 +241,32 @@ <h2>Providing additional data to the shapes graph</h2>
<li></li>
</ul>
</p>
<h2>Binding a data graph to the form</h2>
<h2>Binding a data graph</h2>
<p></p>
<h2>SHACL "or" constraint</h2>
<p>
&lt;shacl-form&gt; supports using the <a href="https://www.w3.org/TR/shacl/#OrConstraintComponent"><span class="code">sh:or</span></a> constraint to let users select between different options on nodes or properties.
In the example above, the node shape <span class="code">example:Attribution</span> defines a property with <span class="code">sh:path prov:agent</span>,
whose values either conform to node shape <span class="code">example:Person</span> or <span class="code">example:Organisation</span>. After selecting one of the options, the respective input fields are added to the form.
</p>
<p>When binding an existing data graph to the form, the <span class="code">sh:or</span></a> constraint is tried to be resolved depending on the respective value:</p>
<p>When binding an existing data graph to the form, the <span class="code">sh:or</span></a> constraint is tried to be resolved depending on the respective data value:</p>
<ul>
<li><p class="my-0">For RDF literals, a <span class="code">sh:or</span> option with a matching <span class="code">sh:datatype</span> is chosen</p></li>
<li><p class="my-0">For RDF literals, an <span class="code">sh:or</span> option with a matching <span class="code">sh:datatype</span> is chosen</p></li>
<li><p class="my-0">For blank nodes or named nodes, the <span class="code">rdf:type</span> of the value is tried to be matched with a node shape having a corresponding <span class="code">sh:targetClass</span> or with a property shape having a corresponding <span class="code">sh:class</span></p></li>
</ul>
<h2>Plugins</h2>
<p>The Javascript of this page contains the following code:</p>
<pre>
<code>
import { MapBoxPlugin } from '@ulb-darmstadt/shacl-form'
<p>Plugins can modify the rendering of the form and add functionality to edit and view certain RDF datatypes or predicates (or a combination of both). As an example, the JavaScript of this page contains the following code:</p>
<pre><code>
import { MapBoxPlugin } from '@ulb-darmstadt/shacl-form/plugins/mapbox.js'
const form = document.getElementById("shacl-form")
form.registerPlugin(new MapBoxPlugin({ datatype: 'http://www.opengis.net/ont/geosparql#wktLiteral' }))
</code>
</pre>
<p>Plugins can modify the rendering of the form and add functionality to edit certain RDF datatypes or predicates (or a combination of both).</p>
shacl groups
form.registerPlugin(new MapBoxPlugin({ datatype: 'http://www.opengis.net/ont/geosparql#wktLiteral' }, API_KEY))
</code></pre>
<p>
In effect, whenever a SHACL property has a <span class="code">sh:datatype</span> of <span class="code">http://www.opengis.net/ont/geosparql#wktLiteral</span>, the plugin is called to create the editor and/or viewer HTML elements.
This specific plugin uses <a href="https://docs.mapbox.com/mapbox-gl-js/guides/">Mapbox GL</a> to edit and view geometry expressed with <a href="http://giswiki.org/wiki/Well_Known_Text">well known text</a> on a map.
</p>

shacl groups
lang strings (languageIn)

</template>
@@ -280,6 +292,7 @@ <h2>Plugins</h2>
output.classList.toggle('invalid', !ev.detail?.valid)
output.querySelector("pre").innerText = form.serialize()
})
shapes.focus()
})
</script>
</template>
12 changes: 5 additions & 7 deletions demo/style.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
:root, shacl-form [data-bs-theme="light"] {
--brand-color: #008877;
--bs-body-color-rgb: 255, 0, 0 !important;
}

:root { --brand-color: #008877; }
body {
font-family: sans-serif;
display: grid;
@@ -83,10 +79,11 @@ a:visited { color: inherit; }
}

.content h2 {
margin-top: 1em; color: #444;
margin: 1em 0 0.4em 0; font-size: 1.2em;
}

.content p { margin-top: 0; line-height: 1.3em; font-size: 20px; color: #444; }
.content p { margin: 9px 0; line-height: 1.1em; font-size: 18px; color: #444; }
.content pre { margin: 0; }

.wrapper {
display: grid;
@@ -115,6 +112,7 @@ textarea#shacl-shape-input { resize: vertical; }

fieldset { border-color: var(--brand-color); }
fieldset pre { font-size: 0.7em; }
iframe { width: 100%; height: 80vh; border: 0; }
.wrapper fieldset pre { max-height: 400px; }

.footer {
16 changes: 16 additions & 0 deletions demo/theme-bootstrap.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
:root, shacl-form [data-bs-theme="light"] {
--brand-color: #008877;
--bs-body-color-rgb: 255, 0, 0 !important;
}
body { font-family: sans-serif; }
</style>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ulb-darmstadt/shacl-form/dist/bootstrap.js"></script>
</head>
<body>
<shacl-form id="shacl-form" data-shapes-url="complex-example.ttl"></shacl-form>
</body>
</html>
12 changes: 12 additions & 0 deletions demo/theme-default.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; }
</style>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ulb-darmstadt/shacl-form/dist/index.js"></script>
</head>
<body>
<shacl-form id="shacl-form" data-shapes-url="complex-example.ttl"></shacl-form>
</body>
</html>
13 changes: 13 additions & 0 deletions demo/theme-material.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; }
</style>
<link href=" https://cdn.jsdelivr.net/npm/[email protected]/css/fonts.min.css " rel="stylesheet">
<script type="module" src="https://cdn.jsdelivr.net/npm/@ulb-darmstadt/shacl-form/dist/material.js"></script>
</head>
<body>
<shacl-form id="shacl-form" data-shapes-url="complex-example.ttl"></shacl-form>
</body>
</html>
585 changes: 230 additions & 355 deletions package-lock.json

Large diffs are not rendered by default.

114 changes: 0 additions & 114 deletions public/index.html

This file was deleted.

2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,9 @@ export class ElementAttributes {
valueSubject: string | null = null
mode: string | null = null
language: string = navigator.language
loading: string = 'Loading\u2026'
ignoreOwlImports: string | null = null
collapse: string | null = null
submitButton: string | null = null
}

1 change: 0 additions & 1 deletion src/constraints.ts
Original file line number Diff line number Diff line change
@@ -45,7 +45,6 @@ export function createShaclOrConstraint(options: Term[], context: ShaclNode | Sh
const select = editor.querySelector('.editor') as Editor
select.onchange = () => {
if (select.value) {

constraintElement.replaceWith(createPropertyInstance(context.template.clone().merge(values[parseInt(select.value)]), undefined, true))
}
}
12 changes: 9 additions & 3 deletions src/form.ts
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ export class ShaclForm extends HTMLElement {
clearTimeout(this.initDebounceTimeout)
this.initDebounceTimeout = setTimeout(async () => {
// remove all child elements from form and show loading indicator
this.form.replaceChildren(document.createTextNode('Loading...'))
this.form.replaceChildren(document.createTextNode(this.config.attributes.loading))
try {
await this.config.loader.loadGraphs()
// remove loading indicator
@@ -102,7 +102,7 @@ export class ShaclForm extends HTMLElement {
errorDisplay.innerText = String(e)
this.form.replaceChildren(errorDisplay)
}
}, 50)
}, 200)
}

public serialize(format = 'text/turtle'): string {
@@ -169,10 +169,16 @@ export class ShaclForm extends HTMLElement {
if (invalidElement.classList.contains('editor')) {
// this is a property shape violation
if (!ignoreEmptyValues || invalidElement['value']) {
const parent = invalidElement.parentElement!
let parent: HTMLElement | null = invalidElement.parentElement!
parent.classList.add('invalid')
parent.classList.remove('valid')
parent.appendChild(this.createValidationErrorDisplay(result))
do {
if (parent.classList.contains('collapsible')) {
parent.classList.add('open')
}
parent = parent.parentElement
} while (parent)
}
} else if (!ignoreEmptyValues) {
// this is a node shape violation
14 changes: 13 additions & 1 deletion src/group.ts
Original file line number Diff line number Diff line change
@@ -16,8 +16,20 @@ export function createShaclGroup(groupSubject: string, config: Config): HTMLElem
if (order) {
group.style.order = order
}
const header = document.createElement('h2')
const header = document.createElement('h1')
header.innerText = name
group.appendChild(header)

if (config.attributes.collapse !== null) {
group.classList.add('collapsible')
if (config.attributes.collapse === 'open') {
group.classList.add('open')
}
header.classList.add('activator')
header.addEventListener('click', () => {
group.classList.toggle('open')
})

}
return group
}
2 changes: 1 addition & 1 deletion src/plugins/mapbox.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ const dialogTemplate = `
<button class="closeButton" type="button" onclick="this.parentElement.close()">&#x2715;</button>
</dialog>`

export class MapBoxPlugin extends Plugin {
export class MapboxPlugin extends Plugin {
map: mapboxgl.Map
draw: MapboxDraw
currentEditor: Editor | undefined
15 changes: 15 additions & 0 deletions src/property.ts
Original file line number Diff line number Diff line change
@@ -55,6 +55,21 @@ export class ShaclProperty extends HTMLElement {
this.addEventListener('change', () => { this.updateControls() })
this.updateControls()
}

if (this.template.node && this.template.config.attributes.collapse !== null && (!this.template.maxCount || this.template.maxCount > 1)) {
const collapsible = this
collapsible.classList.add('collapsible')
if (this.template.config.attributes.collapse === 'open') {
collapsible.classList.add('open')
}
const activator = document.createElement('h1')
activator.classList.add('activator')
activator.innerText = this.template.label
activator.addEventListener('click', () => {
collapsible.classList.toggle('open')
})
this.prepend(activator)
}
}

addPropertyInstance(value?: Term): HTMLElement {
31 changes: 17 additions & 14 deletions src/styles.css
Original file line number Diff line number Diff line change
@@ -4,38 +4,41 @@ shacl-node, :host .shacl-group { display: flex; flex-direction: column; width: 1
shacl-node .control-button { cursor: pointer; }
shacl-node .control-button:not(:hover) { border-color: transparent; background: 0; }
shacl-node .remove-button { margin-left: 4px; }
shacl-node .add-button { font-size: 0.7rem; color: #555; margin-right: 24px; text-decoration:none; }
shacl-node .add-button { font-size: 0.7rem; color: #555; margin-right: 24px; text-decoration:none; padding: 6px 0; }
shacl-node .add-button:before { content: '+'; margin-right: 0.2em; }
shacl-node .add-button:hover { color: inherit; }
shacl-node h1 { font-size: 1.1rem; border-bottom: 1px solid; margin-top: 2px; }
shacl-node shacl-node h1 { font-size: 1rem; color: #555; }
shacl-node h1 { font-size: 1.1rem; border-bottom: 1px solid; margin-top: 4px; color: #555; }
shacl-property { display: flex; flex-direction: column; align-items: end; position: relative; }
shacl-property:not(.may-add) > .add-button { display: none; }
shacl-property:not(.may-remove) > .property-instance > .remove-button:not(.persistent) { visibility: hidden; }
shacl-property:not(.may-remove) > .shacl-or-constraint > .remove-button:not(.persistent) { visibility: hidden; }
:host .shacl-group { margin-bottom: 1em; padding-bottom: 1em; }
:host .shacl-group h2 { font-size: 1rem; border-bottom: 1px solid; margin-top: 0; color: #555; }
:host .property-instance, :host .shacl-or-constraint { display: flex; align-items: flex-start; margin-top: 8px; width: 100%; position: relative; }
:host .property-instance, :host .shacl-or-constraint { display: flex; align-items: flex-start; padding: 4px 0; width: 100%; position: relative; }
:host .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; }
:host .property-instance label[title] { cursor: help; text-decoration: underline dashed #AAA; }
:host .mode-edit .property-instance label.required::before { color: red; content: '\2736'; font-size: 0.6rem; position: absolute; left: -1.4em; top: 0.15rem; }
:host .property-instance.valid::before { position: absolute; left: calc(var(--label-width) - 1em); top: 2px; color: green; content: '\2713'; }
:host .property-instance.valid::before { position: absolute; left: calc(var(--label-width) - 1em); top: 6px; color: green; content: '\2713'; }
:host .editor:not([type='checkbox']), :host .shacl-or-constraint select { flex-grow: 1; }
:host .shacl-or-constraint select { border: 1px solid #DDD; padding: 2px 4px; }
:host textarea.editor { resize: vertical; }
:host .lang-chooser { position: absolute; top: 1px; right: 24px; border: 0; background-color: #e9e9ed; padding: 2px 4px; max-width: 40px; width: 40px; box-sizing: content-box; }
:host .lang-chooser { position: absolute; top: 5px; right: 24px; border: 0; background-color: #e9e9ed; padding: 2px 4px; max-width: 40px; width: 40px; box-sizing: content-box; }
:host .lang-chooser+.editor { padding-right: 55px; }
:host .validation-error { position: absolute; left: calc(var(--label-width) - 1em); top: 2px; color: red; cursor: help; }
:host .validation-error { position: absolute; left: calc(var(--label-width) - 1em); top: 6px; color: red; cursor: help; }
:host .validation-error::before { content: '\26a0' }
:host .validation-error.node { left: -1em; }
:host .invalid > .editor { border-color: red !important; }
:host .ml-0 { margin-left: 0 !important; }
:host .pr-0 { padding-right: 0 !important; }
:host .mode-view .property-instance:not(:first-child) > label { visibility: hidden; height: 0; }
:host .mode-view .property-instance:not(:first-child) > label { visibility: hidden; }

.flex-break {
flex-basis: 100%;
height: 0;
}
.lang { opacity: 0.65; font-size: 0.6em; }
a, a:visited { color: inherit; }
a, a:visited { color: inherit; }

.collapsible > .activator { cursor: pointer; width: 100%; padding: 8px 0; border: 0; }
.collapsible > .activator:hover, .collapsible.open > .activator { background-color: #F5F5F5; }
.collapsible > .activator::after { content: '\25BC'; float: right; margin: 0 5px; font-size: 14px; }
.collapsible.open > .activator::after { content: '\25B2'; }
/* .collapsible > *:not(.activator) { overflow: hidden; } */
.collapsible:not(.open) > *:not(.activator) { display: none; }
.collapsible shacl-node > h1 { display: none; }
.collapsible.open > .property-instance:nth-child(odd) { background-color: #F5F5F5; }
7 changes: 4 additions & 3 deletions src/themes/bootstrap.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
:host .lang-chooser { top: 0; right: 43px; font-size: 0.7em; }
:host form.mode-edit { --label-width: 0em; }
:host .property-instance.valid::before { top: 6px; }
:host .validation-error { top: 6px; }
:host .lang-chooser { right: 32px; font-size: 0.8em; }
:host .property-instance::after { content: attr(data-description); position: absolute; bottom: -12px; left: 13px; font-size: 12px; opacity: 0.7;}
.form-floating[data-description] { margin-bottom: 18px; }
.remove-button { padding: 6px; }
8 changes: 2 additions & 6 deletions src/themes/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -26,12 +26,8 @@ export class BootstrapTheme extends NativeTheme {
const labelElem = result.querySelector('label')
labelElem?.classList.add('form-label')
if (labelElem?.title) {
const flexBreak = document.createElement('div')
flexBreak.classList.add('flex-break')
result.appendChild(flexBreak)
const description = document.createElement('div')
description.innerText = labelElem.title
result.appendChild(description)
result.dataset.description = labelElem.title
labelElem.removeAttribute('title')
}

result.prepend(editor)
2 changes: 1 addition & 1 deletion src/themes/native.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
:host .editor:not([type='checkbox']) { border: 1px solid #DDD; padding: 2px 4px; }
:host .property-instance 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; }
:host .property-instance:not(:first-child) > label { visibility: hidden; height: 0; }
:host .property-instance:not(:first-child) > label { visibility: hidden; max-height: 0; }
1 change: 0 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@ module.exports = [
resolve: { extensions: ['.tsx', '.ts', '.js'] },
devServer: {
static: [
// { directory: path.join(__dirname, 'public'), serveIndex: true },
{ directory: path.join(__dirname, 'demo'), serveIndex: true },
],
compress: true,

0 comments on commit 245d128

Please sign in to comment.