├── .changeset ├── README.md ├── beige-grapes-knock.md ├── chilled-feet-hide.md ├── config.json ├── dry-shoes-cover.md ├── dull-baboons-suffer.md ├── few-clouds-care.md ├── friendly-rings-repair.md ├── hungry-guests-boil.md ├── ninety-kiwis-kiss.md ├── quiet-fireants-join.md ├── seven-monkeys-beam.md ├── six-islands-thank.md ├── wild-icons-prove.md └── wise-pumas-eat.md ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── pr-commenter.yml └── workflows │ ├── build.yml │ ├── netlify.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .husky └── pre-commit ├── .nvmrc ├── LICENSE ├── README.md ├── codecov.yml ├── demos ├── examples │ ├── CHANGELOG.md │ ├── DescriptionTooltip.ts │ ├── ErrorSummary │ │ └── index.ts │ ├── InlineNestedShapes │ │ └── index.ts │ ├── LanguageMultiSelect │ │ └── index.ts │ ├── NestedShapesIndividually │ │ ├── components.ts │ │ └── renderer.ts │ ├── StarRating │ │ ├── index.ts │ │ └── star-rating.ts │ ├── XoneRenderer │ │ └── index.ts │ └── package.json ├── lit-html │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── custom-elements.json │ ├── index.html │ ├── package.json │ ├── src │ │ ├── configure.ts │ │ ├── env.ts │ │ ├── menu.ts │ │ ├── menu │ │ │ ├── formMenu.ts │ │ │ ├── resource.ts │ │ │ └── shape.ts │ │ ├── shaperone-playground-lit.ts │ │ └── state │ │ │ ├── config.ts │ │ │ ├── models │ │ │ ├── components.ts │ │ │ ├── index.ts │ │ │ ├── playground.ts │ │ │ ├── renderer.ts │ │ │ ├── resource.ts │ │ │ └── shape.ts │ │ │ └── store.ts │ └── vite.config.js └── storybook │ ├── .storybook │ ├── main.ts │ ├── preview-head.html │ └── preview.tsx │ ├── data │ └── simple │ │ └── john-doe.ttl │ ├── global.d.ts │ ├── lib │ ├── env.ts │ └── shaperone-demo.ts │ ├── package.json │ ├── shapes │ └── simple │ │ ├── datatypes.ttl │ │ ├── first-last.ttl │ │ └── unrestricted.ttl │ ├── stories │ ├── Getting Started.mdx │ ├── Shoelace.stories.ts │ ├── Simple.stories.ts │ └── render.ts │ └── vite.config.js ├── dist ├── .nojekyll ├── _coverpage.md ├── _media │ ├── GitHub-Mark-32px.png │ ├── anatomy.png │ ├── configuration.png │ ├── logo.png │ ├── multi-select.png │ ├── prism.css │ ├── stack.png │ └── star-rating.png ├── _sidebar.md ├── advanced.md ├── anatomy.md ├── components │ └── implement.md ├── configuration.md ├── core.md ├── editors.md ├── editors │ ├── dash.md │ ├── matchers.md │ └── metadata.md ├── extensions │ ├── hydra.md │ └── plugins.md ├── getting-started.md ├── index.html ├── overview.md ├── plugins │ └── variables.js ├── renderer │ ├── core.md │ └── web-components.md ├── validation.md └── variables.json ├── package-lock.json ├── package.json ├── packages ├── core-tests │ ├── CHANGELOG.md │ ├── DashEditors.test.ts │ ├── components.test.ts │ ├── lib │ │ ├── components.test.ts │ │ └── property.test.ts │ ├── models │ │ ├── components │ │ │ └── reducers.test.ts │ │ ├── editors │ │ │ ├── effects │ │ │ │ └── loadDash.test.ts │ │ │ ├── lib │ │ │ │ └── match.test.ts │ │ │ └── reducers │ │ │ │ ├── addMatchers.test.ts │ │ │ │ ├── addMetadata.test.ts │ │ │ │ └── decorate.test.ts │ │ ├── forms │ │ │ ├── effects │ │ │ │ ├── addObject.test.ts │ │ │ │ ├── lib │ │ │ │ │ └── syncProperties.test.ts │ │ │ │ ├── pushFocusNode.test.ts │ │ │ │ ├── removeObject.test.ts │ │ │ │ ├── replaceObjects.test.ts │ │ │ │ ├── resources │ │ │ │ │ └── setRoot.test.ts │ │ │ │ ├── selectShape.test.ts │ │ │ │ ├── setObjectValue.test.ts │ │ │ │ ├── setPropertyObjects.test.ts │ │ │ │ ├── shapes │ │ │ │ │ └── setGraph.test.ts │ │ │ │ ├── updateObject.test.ts │ │ │ │ └── validate.test.ts │ │ │ ├── lib │ │ │ │ └── stateBuilder.test.ts │ │ │ └── reducers │ │ │ │ ├── addFormField.test.ts │ │ │ │ ├── editors.test.ts │ │ │ │ ├── multiEditors.test.ts │ │ │ │ ├── popFocusNode.test.ts │ │ │ │ ├── properties.test.ts │ │ │ │ ├── pushFocusNode.test.ts │ │ │ │ ├── removeObject.test.ts │ │ │ │ ├── replaceFocusNodes.test.ts │ │ │ │ ├── selectEditor.test.ts │ │ │ │ ├── selectGroup.test.ts │ │ │ │ ├── selectShape.test.ts │ │ │ │ ├── truncateFocusNodes.test.ts │ │ │ │ ├── updateObject.test.ts │ │ │ │ └── validationReport.test.ts │ │ ├── resources │ │ │ ├── effects │ │ │ │ └── forms │ │ │ │ │ ├── addFormField.test.ts │ │ │ │ │ ├── clearValue.test.ts │ │ │ │ │ ├── createFocusNodeState.test.ts │ │ │ │ │ ├── removeObject.test.ts │ │ │ │ │ ├── setObjectValue.test.ts │ │ │ │ │ └── setPropertyObjects.test.ts │ │ │ ├── lib │ │ │ │ └── objectValue.test.ts │ │ │ └── reducers │ │ │ │ └── setRoot.test.ts │ │ ├── shapes │ │ │ ├── lib │ │ │ │ └── index.test.ts │ │ │ └── reducers │ │ │ │ └── setGraph.test.ts │ │ └── validation │ │ │ └── reducers │ │ │ └── setValidator.test.ts │ ├── package.json │ └── test-setup.ts ├── core │ ├── CHANGELOG.md │ ├── DashEditors.ts │ ├── components.ts │ ├── env.ts │ ├── esbuild.config.js │ ├── index.ts │ ├── lib │ │ ├── RecursivePartial.ts │ │ ├── components.ts │ │ ├── components │ │ │ ├── autoComplete.ts │ │ │ ├── base │ │ │ │ └── instancesSelect.ts │ │ │ ├── blankNode.ts │ │ │ ├── booleanSelect.ts │ │ │ ├── datePicker.ts │ │ │ ├── dateTimePicker.ts │ │ │ ├── details.ts │ │ │ ├── enumSelect.ts │ │ │ ├── instancesSelect.ts │ │ │ ├── richText.ts │ │ │ ├── textArea.ts │ │ │ ├── textAreaWithLang.ts │ │ │ ├── textField.ts │ │ │ ├── textFieldWithLang.ts │ │ │ └── uri.ts │ │ ├── datatypes.ts │ │ ├── env │ │ │ ├── ConstantsFactory.ts │ │ │ └── NamespaceFactory.ts │ │ ├── graph.ts │ │ ├── mixins.ts │ │ ├── order.ts │ │ └── property.ts │ ├── metadata.ts │ ├── models │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ │ └── decorate.ts │ │ │ └── reducers.ts │ │ ├── editors │ │ │ ├── effects │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ │ └── match.ts │ │ │ └── reducers │ │ │ │ ├── addMatchers.ts │ │ │ │ ├── addMetadata.ts │ │ │ │ └── decorate.ts │ │ ├── forms │ │ │ ├── effects │ │ │ │ ├── addObject.ts │ │ │ │ ├── components │ │ │ │ │ └── index.ts │ │ │ │ ├── createFocusNodeState.ts │ │ │ │ ├── editors │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lib │ │ │ │ │ └── syncProperties.ts │ │ │ │ ├── notify.ts │ │ │ │ ├── pushFocusNode.ts │ │ │ │ ├── removeObject.ts │ │ │ │ ├── replaceObjects.ts │ │ │ │ ├── resources │ │ │ │ │ ├── index.ts │ │ │ │ │ └── setRoot.ts │ │ │ │ ├── selectShape.ts │ │ │ │ ├── setObjectValue.ts │ │ │ │ ├── setPropertyObjects.ts │ │ │ │ ├── shapes │ │ │ │ │ ├── index.ts │ │ │ │ │ └── setGraph.ts │ │ │ │ ├── updateObject.ts │ │ │ │ └── validate.ts │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ │ ├── objectid.ts │ │ │ │ ├── property.ts │ │ │ │ └── stateBuilder.ts │ │ │ ├── objectStateProducer.ts │ │ │ └── reducers │ │ │ │ ├── addFormField.ts │ │ │ │ ├── editors.ts │ │ │ │ ├── multiEditors.ts │ │ │ │ ├── popFocusNode.ts │ │ │ │ ├── properties.ts │ │ │ │ ├── removeObject.ts │ │ │ │ ├── replaceFocusNodes.ts │ │ │ │ ├── selectEditor.ts │ │ │ │ ├── selectGroup.ts │ │ │ │ ├── selectShape.ts │ │ │ │ ├── truncateFocusNodes.ts │ │ │ │ ├── updateObject.ts │ │ │ │ └── validation.ts │ │ ├── resources │ │ │ ├── effects │ │ │ │ └── forms │ │ │ │ │ ├── addFormField.ts │ │ │ │ │ ├── clearValue.ts │ │ │ │ │ ├── createFocusNodeState.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── lib │ │ │ │ │ └── index.ts │ │ │ │ │ ├── removeObject.ts │ │ │ │ │ ├── setObjectValue.ts │ │ │ │ │ └── setPropertyObjects.ts │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ │ └── objectValue.ts │ │ │ └── reducers │ │ │ │ └── setRoot.ts │ │ ├── shapes │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ │ ├── PropertyShape.ts │ │ │ │ └── index.ts │ │ │ └── reducers.ts │ │ └── validation │ │ │ ├── index.ts │ │ │ └── reducers │ │ │ └── setValidator.ts │ ├── ns.ts │ ├── package.json │ ├── renderer.ts │ ├── state │ │ ├── effectsPlugin.ts │ │ └── index.ts │ ├── store.ts │ └── tsconfig.json ├── hydra │ ├── CHANGELOG.md │ ├── README.md │ ├── components.ts │ ├── index.ts │ ├── lib │ │ └── components │ │ │ ├── autocomplete.ts │ │ │ ├── instancesSelector.ts │ │ │ ├── multiInstanceSelector.ts │ │ │ └── searchDecorator.ts │ ├── package.json │ ├── test │ │ ├── helpers │ │ │ └── alcaeus.ts │ │ └── lib │ │ │ └── components │ │ │ ├── _support.ts │ │ │ ├── autoComplete.test.ts │ │ │ ├── instanceSelector.test.ts │ │ │ ├── multiInstanceSelector.test.ts │ │ │ └── searchDecorator.test.ts │ └── tsconfig.json ├── rdf-validate-shacl │ ├── CHANGELOG.md │ ├── README.md │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── testing │ ├── CHANGELOG.md │ ├── HydraFactory.ts │ ├── env.ts │ ├── index.ts │ ├── models │ │ ├── editors.ts │ │ └── form.ts │ ├── nodeFactory.ts │ ├── package.json │ ├── renderer.ts │ ├── sinon.ts │ └── util.ts ├── wc-material │ ├── CHANGELOG.md │ ├── README.md │ ├── components.ts │ ├── components │ │ ├── select.ts │ │ ├── textArea.ts │ │ └── textField.ts │ ├── custom-elements.json │ ├── directives │ │ └── validity.ts │ ├── elements │ │ ├── SelectableMenuMixin.ts │ │ ├── mwc-editor-toggle.ts │ │ ├── mwc-item-lite.ts │ │ ├── mwc-property-menu.ts │ │ ├── mwc-shape-selector.ts │ │ └── wc-menu.ts │ ├── package.json │ ├── renderer │ │ ├── index.ts │ │ └── tabs.ts │ ├── test │ │ ├── components │ │ │ ├── booleanSelect.test.ts │ │ │ ├── date.test.ts │ │ │ ├── enumSelect.test.ts │ │ │ ├── instancesSelect.test.ts │ │ │ ├── textField.test.ts │ │ │ └── urlEditor.test.ts │ │ └── renderer │ │ │ └── index.test.ts │ └── tsconfig.json ├── wc-shoelace │ ├── CHANGELOG.md │ ├── README.md │ ├── component-extras.ts │ ├── components.ts │ ├── components │ │ ├── autocomplete.ts │ │ ├── enumSelect.ts │ │ ├── instancesSelect.ts │ │ ├── multiInstancesSelect.ts │ │ └── select.ts │ ├── custom-elements.json │ ├── elements │ │ ├── sh-sl-autocomplete.ts │ │ ├── sh-sl-object.ts │ │ ├── sh-sl-property.ts │ │ └── sh-sl-with-lang-editor.ts │ ├── lib │ │ ├── components.ts │ │ ├── difference.ts │ │ └── handlers.ts │ ├── package.json │ ├── renderer │ │ ├── boolean.ts │ │ ├── details.ts │ │ └── input.ts │ ├── settings.ts │ ├── templates.ts │ ├── test │ │ ├── components.test.ts │ │ ├── components │ │ │ ├── autocomplete.test.ts │ │ │ ├── enumSelect.test.ts │ │ │ ├── instancesSelect.test.ts │ │ │ ├── multiInstancesSelect.test.ts │ │ │ └── textField.test.ts │ │ ├── elements │ │ │ ├── __snapshots__ │ │ │ │ └── sh-sl-property.test.snap.js │ │ │ ├── sh-sl-autocomplete.test.ts │ │ │ ├── sh-sl-property.test.ts │ │ │ └── sh-sl-with-lang-editor.test.ts │ │ └── templates.test.ts │ └── tsconfig.json ├── wc-vaadin │ ├── .eslintrc.json │ ├── CHANGELOG.md │ ├── components.ts │ ├── components │ │ ├── booleanSelect.ts │ │ ├── date.ts │ │ ├── enumSelect.ts │ │ ├── instancesSelect.ts │ │ ├── text-area.ts │ │ ├── text-field.ts │ │ ├── url-editor.ts │ │ └── validation.ts │ ├── package.json │ ├── renderer │ │ └── accordion.ts │ ├── test │ │ ├── components.test.ts │ │ └── components │ │ │ ├── __snapshots__ │ │ │ ├── date.test.snap.js │ │ │ ├── enumSelect.test.snap.js │ │ │ ├── textField.test.snap.js │ │ │ └── urlEditor.test.snap.js │ │ │ ├── booleanSelect.test.ts │ │ │ ├── date.test.ts │ │ │ ├── enumSelect.test.ts │ │ │ ├── instancesSelect.test.ts │ │ │ ├── textField.test.ts │ │ │ └── urlEditor.test.ts │ └── tsconfig.json └── wc │ ├── CHANGELOG.md │ ├── NativeComponents.ts │ ├── README.md │ ├── ShaperoneForm.ts │ ├── components │ ├── connect.ts │ ├── index.ts │ ├── lib │ │ └── textFieldType.ts │ ├── readonly.ts │ └── validity.ts │ ├── configure.ts │ ├── custom-elements.json │ ├── demo │ ├── index.html │ └── index.ts │ ├── index.ts │ ├── lib │ ├── eventTarget.ts │ └── shaperone-form.ts │ ├── package.json │ ├── renderer │ ├── decorator.ts │ ├── editor.ts │ ├── focusNode.ts │ ├── group.ts │ ├── index.ts │ ├── model.ts │ ├── object.ts │ └── property.ts │ ├── store.ts │ ├── templates.ts │ ├── test │ ├── NativeComponents.test.ts │ ├── __snapshots__ │ │ └── NativeComponents.test.snap.js │ ├── decorator.test.ts │ ├── renderer │ │ ├── decorator.test.ts │ │ ├── editor.test.ts │ │ ├── focusNode.test.ts │ │ ├── group.test.ts │ │ ├── model.test.ts │ │ ├── object.test.ts │ │ └── property.test.ts │ ├── shaperone-form.test.ts │ └── store.test.ts │ ├── tsconfig.json │ └── vite.config.js ├── tsconfig.build.json ├── tsconfig.json ├── typedoc.json ├── types ├── concat-merge │ └── index.d.ts ├── global.d.ts └── multiselect-combo-box │ └── index.d.ts ├── vite.config.js └── web-test-runner.config.mjs /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/beige-grapes-knock.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-core": minor 3 | --- 4 | 5 | Fixed typo in exported type (`MiminalEnvironment` => `MinimalEnvironment`) 6 | -------------------------------------------------------------------------------- /.changeset/chilled-feet-hide.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-wc": minor 3 | --- 4 | 5 | Removed the `window.Shaperone.DEBUG` flag in favor of component `debug` property 6 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.1.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "linked": [], 6 | "access": "restricted", 7 | "baseBranch": "master", 8 | "updateInternalDependencies": "patch" 9 | } -------------------------------------------------------------------------------- /.changeset/dry-shoes-cover.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-core": patch 3 | --- 4 | 5 | Fixes setting shapes from dataset 6 | -------------------------------------------------------------------------------- /.changeset/dull-baboons-suffer.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-wc-material": patch 3 | "@hydrofoil/shaperone-wc-vaadin": patch 4 | --- 5 | 6 | Updated core package 7 | -------------------------------------------------------------------------------- /.changeset/few-clouds-care.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-wc": patch 3 | "@hydrofoil/shaperone-wc-material": patch 4 | "@hydrofoil/shaperone-wc-vaadin": patch 5 | --- 6 | 7 | Updated `lit` to v3 8 | -------------------------------------------------------------------------------- /.changeset/friendly-rings-repair.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-wc": patch 3 | "@hydrofoil/shaperone-wc-material": patch 4 | "@hydrofoil/shaperone-wc-vaadin": patch 5 | --- 6 | 7 | Replace directive `spread` with one from `@open-wc/lit-helpers` 8 | -------------------------------------------------------------------------------- /.changeset/hungry-guests-boil.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-core": patch 3 | "@hydrofoil/shaperone-wc": patch 4 | --- 5 | 6 | Native components: `xsd:decimal` would not accept decimal point as a valid input. 7 | -------------------------------------------------------------------------------- /.changeset/ninety-kiwis-kiss.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-wc": patch 3 | --- 4 | 5 | Native templates: The ability to add and remove property values 6 | -------------------------------------------------------------------------------- /.changeset/quiet-fireants-join.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-core": patch 3 | "@hydrofoil/shaperone-wc": patch 4 | --- 5 | 6 | Improved state management by separating the state of each element instance 7 | -------------------------------------------------------------------------------- /.changeset/seven-monkeys-beam.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-core": patch 3 | "@hydrofoil/shaperone-wc": minor 4 | --- 5 | 6 | `configure` is now async and required to register the element 7 | -------------------------------------------------------------------------------- /.changeset/six-islands-thank.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-core": patch 3 | --- 4 | 5 | When no shape is selected and only one `sh:NodeShape` exists in graph, that shape will be used 6 | -------------------------------------------------------------------------------- /.changeset/wild-icons-prove.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-wc": minor 3 | --- 4 | 5 | Big change how elements are configured. Global config can be set using the `configure.js` module. 6 | 7 | ```js 8 | import { configure } from '@hydrofoil/shaperone-wc' 9 | 10 | await configure(({ components, editors, renderer, validation }) => { 11 | 12 | }) 13 | ``` 14 | 15 | Additionally, each component can be customised further by providing calling `configure` method of the component. 16 | 17 | ```html 18 | 19 | 20 | 29 | ``` 30 | -------------------------------------------------------------------------------- /.changeset/wise-pumas-eat.md: -------------------------------------------------------------------------------- 1 | --- 2 | "@hydrofoil/shaperone-wc": minor 3 | --- 4 | 5 | Fixes an issue that the form would not render when the node was `<>` (empty string URI) 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [*.json] 24 | indent_size = 2 25 | 26 | [*.{html,js,md}] 27 | block_comment_start = /** 28 | block_comment = * 29 | block_comment_end = */ 30 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | types/ 2 | node_modules/ 3 | dist/ 4 | *.snap.js 5 | packages/rdx/ 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@open-wc/eslint-config", 4 | "@tpluscode" 5 | ], 6 | "env": { 7 | "browser": true 8 | }, 9 | "parserOptions": { 10 | "project": "./tsconfig.json" 11 | }, 12 | "settings": { 13 | "import/core-modules": [ "@captaincodeman/rdx" ] 14 | }, 15 | "rules": { 16 | "import/no-extraneous-dependencies": ["error", {"devDependencies": [ 17 | "**/test/**", 18 | "**/demo/**", 19 | "packages/core-tests/**" 20 | ]}], 21 | "no-shadow": "off", 22 | "max-len": ["error", 200], 23 | "wc/guard-super-call": "off", 24 | "class-methods-use-this": "off", 25 | "no-param-reassign": ["error", { "props": false }], 26 | "@typescript-eslint/no-explicit-any": "off", 27 | "lit/no-classfield-shadowing": "warn" 28 | }, 29 | "overrides": [{ 30 | "files": "**/test/**/*.ts", 31 | "rules": { 32 | "no-unused-expressions": "warn", 33 | "babel/no-unused-expressions": "off", 34 | "lit-a11y/accessible-name": "off" 35 | } 36 | },{ 37 | "files": "packages/core-tests/**/*.ts", 38 | "rules": { 39 | "no-unused-expressions": "warn", 40 | "babel/no-unused-expressions": "off" 41 | } 42 | }, { 43 | "files": "packages/core/lib/components/*.ts", 44 | "rules": { 45 | "@typescript-eslint/no-empty-interface": "off" 46 | } 47 | }] 48 | } 49 | -------------------------------------------------------------------------------- /.github/pr-commenter.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | on-update: nothing 3 | snippets: 4 | - id: preview-url 5 | files: 6 | - 'packages/**/*' 7 | - 'demos/**/*' 8 | body: "Netlify preview deployed to [{{ url }}]({{ url }})" 9 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@v4 15 | with: 16 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 17 | fetch-depth: 0 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 18 23 | 24 | - name: Install Dependencies 25 | run: npm ci 26 | 27 | - name: Create Release Pull Request or Publish to npm 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 32 | publish: yarn release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.js 3 | *.js.map 4 | !*.config.js 5 | !*.conf.js 6 | coverage/ 7 | !test-setup.js 8 | *.d.ts 9 | *.d.ts.map 10 | dist/api/ 11 | dist/playground/ 12 | dist/shaperone-form/ 13 | dist/storybook/ 14 | !types/**/*.d.ts 15 | *.log 16 | !*.snap.js 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/rdx"] 2 | path = packages/rdx 3 | url = git@github.com:tpluscode/rdx.git 4 | branch = shaperone 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | npx wsrun -m analyze 6 | git add **/custom-elements.json **/README.md 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 hypermedia.app 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shaperone 2 | Form UI elements driven by SHACL Shapes 3 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - demos 3 | - packages/core-tests/test-setup.js 4 | - karma.conf.js 5 | - types 6 | - packages/*/test 7 | - packages/testing 8 | - '**/*.js' 9 | - web-test-runner.config.mjs 10 | -------------------------------------------------------------------------------- /demos/examples/DescriptionTooltip.ts: -------------------------------------------------------------------------------- 1 | import type { TemplateResult } from 'lit' 2 | import { html } from '@hydrofoil/shaperone-wc' 3 | import type { ComponentDecorator } from '@hydrofoil/shaperone-core/models/components' 4 | import type { PropertyShape } from '@rdfine/shacl' 5 | 6 | function wrap(shape: PropertyShape, result: TemplateResult) { 7 | if (shape.description) { 8 | return html`
${result}
` 9 | } 10 | 11 | return result 12 | } 13 | 14 | export const DescriptionTooltip: ComponentDecorator = { 15 | applicableTo() { 16 | return true 17 | }, 18 | decorate(component) { 19 | if ('lazyRender' in component) { 20 | return { 21 | ...component, 22 | lazyRender: async () => { 23 | const render = await component.lazyRender() 24 | return function (params, actions) { 25 | return wrap(params.property.shape, render.call(this, params, actions)) 26 | } 27 | }, 28 | } 29 | } 30 | 31 | return { 32 | ...component, 33 | render(params, actions) { 34 | return wrap(params.property.shape, component.render(params, actions)) 35 | }, 36 | } 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /demos/examples/InlineNestedShapes/index.ts: -------------------------------------------------------------------------------- 1 | import type { SingleEditorComponent } from '@hydrofoil/shaperone-wc' 2 | import { html } from '@hydrofoil/shaperone-wc' 3 | import { dash } from '@tpluscode/rdf-ns-builders' 4 | import { isResource } from 'is-graph-pointer' 5 | 6 | export const nestedForm: SingleEditorComponent = { 7 | editor: dash.DetailsEditor, 8 | 9 | render({ value, renderer, property: { shape: { node } } }) { 10 | const focusNode = value.object 11 | 12 | if (isResource(focusNode)) { 13 | return renderer.renderFocusNode({ focusNode, shape: node }) 14 | } 15 | 16 | return html`` 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /demos/examples/NestedShapesIndividually/components.ts: -------------------------------------------------------------------------------- 1 | import { dash, rdfs, schema } from '@tpluscode/rdf-ns-builders' 2 | import type { SingleEditorComponent } from '@hydrofoil/shaperone-wc' 3 | import { html } from '@hydrofoil/shaperone-wc' 4 | import type { GraphPointer } from 'clownface' 5 | 6 | function label(object?: GraphPointer) { 7 | return object?.out([rdfs.label, schema.name]).toArray()[0] || object?.value || '' 8 | } 9 | 10 | export const shapeLink: SingleEditorComponent = { 11 | editor: dash.DetailsEditor, 12 | 13 | render({ value }, { focusOnObjectNode }) { 14 | return html`` 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /demos/examples/NestedShapesIndividually/renderer.ts: -------------------------------------------------------------------------------- 1 | import type { FormTemplate } from '@hydrofoil/shaperone-wc/templates.js' 2 | import { decorate } from '@hydrofoil/shaperone-wc/templates.js' 3 | import { html, css } from '@hydrofoil/shaperone-wc' 4 | 5 | export const topmostFocusNodeFormRenderer = decorate((form: FormTemplate): FormTemplate => { 6 | const formTemplate: FormTemplate = (renderer) => { 7 | let backButton = html`` 8 | if (renderer.context.state.focusStack.length > 1) { 9 | backButton = html`` 10 | } 11 | 12 | return html`${backButton}${form(renderer)}` 13 | } 14 | 15 | formTemplate.styles = css`.form-back-button { 16 | display: block; 17 | width: 115px; 18 | height: 25px; 19 | background: #4E9CAF; 20 | padding: 10px; 21 | text-align: center; 22 | border-radius: 5px; 23 | color: white; 24 | font-weight: bold; 25 | line-height: 25px; 26 | }` 27 | 28 | return formTemplate 29 | }) 30 | -------------------------------------------------------------------------------- /demos/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hydrofoil/shaperone-playground-examples", 3 | "version": "0.3.1", 4 | "main": "index.js", 5 | "repository": "https://github.com/hypermedia-app/shaperone", 6 | "author": "Tomasz Pluskiewicz ", 7 | "license": "MIT", 8 | "type": "module", 9 | "private": true, 10 | "dependencies": { 11 | "@tpluscode/rdf-ns-builders": "^4.3.0", 12 | "@fortawesome/fontawesome-svg-core": "^1.2.32", 13 | "@fortawesome/free-solid-svg-icons": "^5.15.1", 14 | "@hydrofoil/shaperone-core": "0.12.1", 15 | "@hydrofoil/shaperone-wc": "0.8.1", 16 | "@zazuko/env": "^2.1.0", 17 | "@rdfjs-elements/lit-helpers": "^0.3.7", 18 | "@zazuko/prefixes": "^2.2.0", 19 | "is-graph-pointer": "^2", 20 | "lit": "^2.0.0", 21 | "multiselect-combo-box": "^3.0.0-alpha.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/lit-html/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 shone-demo-lit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /demos/lit-html/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Open-wc Starter App 6 | 7 | [![Built with open-wc recommendations](https://img.shields.io/badge/built%20with-open--wc-blue.svg)](https://github.com/open-wc) 8 | 9 | ## Quickstart 10 | 11 | To get started: 12 | 13 | ```bash 14 | npm init @open-wc 15 | # requires node 10 & npm 6 or higher 16 | ``` 17 | 18 | ## Scripts 19 | 20 | - `start` runs your app for development, reloading on file changes 21 | - `start:build` runs your app after it has been built using the build command 22 | - `build` builds your app and outputs it in your `dist` directory 23 | - `test` runs your test suite with Karma 24 | - `lint` runs the linter for your project 25 | 26 | ## Tooling configs 27 | 28 | For most of the tools, the configuration is in the `package.json` to reduce the amount of files in your project. 29 | 30 | If you customize the configuration a lot, you can consider moving them to individual files. -------------------------------------------------------------------------------- /demos/lit-html/custom-elements.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "tags": [ 4 | { 5 | "name": "shaperone-playground-lit", 6 | "description": "An application with a title and an action counter", 7 | "properties": [ 8 | { 9 | "name": "title", 10 | "type": "String", 11 | "description": "The title of your application", 12 | "default": "Hey there" 13 | }, 14 | { 15 | "name": "page", 16 | "type": "String", 17 | "description": "Which page to show", 18 | "default": "main" 19 | } 20 | ], 21 | "events": [], 22 | "slots": [], 23 | "cssProperties": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /demos/lit-html/src/env.ts: -------------------------------------------------------------------------------- 1 | import Environment from '@zazuko/env/Environment.js' 2 | import alcaeus from 'alcaeus/Factory.js' 3 | import $rdf from '@rdfine/env' 4 | import FetchFactory from '@rdfjs/fetch-lite/Factory.js' 5 | 6 | export default new Environment([alcaeus(), FetchFactory], { parent: $rdf }) 7 | -------------------------------------------------------------------------------- /demos/lit-html/src/menu.ts: -------------------------------------------------------------------------------- 1 | export interface Menu { 2 | id?: string 3 | type?: 'layout' | 'renderer' | 'format' | 'components' | 'editorChoice' | 'labs' 4 | text?: string 5 | checked?: boolean 6 | children?: Menu[] 7 | component?: string | HTMLElement 8 | } 9 | 10 | export function updateMenu(menu: Menu, type: Menu['type'], text: string | undefined): Menu { 11 | let { checked } = menu 12 | if (menu.type === type) { 13 | checked = menu.text === text 14 | } 15 | 16 | return { 17 | ...menu, 18 | checked, 19 | children: menu.children?.map(child => updateMenu(child, type, text)), 20 | } 21 | } 22 | 23 | export function updateComponent(menu: Menu, id: string, newComponent: string | HTMLElement): Menu { 24 | let { component } = menu 25 | if (menu.id === id) { 26 | component = newComponent 27 | } 28 | 29 | return { 30 | ...menu, 31 | component, 32 | children: menu.children?.map(child => updateComponent(child, id, newComponent)), 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /demos/lit-html/src/state/config.ts: -------------------------------------------------------------------------------- 1 | import * as models from './models/index.js' 2 | 3 | export const config = { 4 | models, 5 | } 6 | -------------------------------------------------------------------------------- /demos/lit-html/src/state/models/components.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from '@captaincodeman/rdx' 2 | 3 | export interface ComponentsState { 4 | components: 'native' | 'material' | 'vaadin' | 'shoelace' 5 | disableEditorChoice: boolean 6 | } 7 | 8 | export const componentsSettings = createModel({ 9 | state: { 10 | components: 'native', 11 | disableEditorChoice: false, 12 | }, 13 | reducers: { 14 | switchComponents(state, components: ComponentsState['components']) { 15 | switch (components) { 16 | case 'material': 17 | case 'native': 18 | case 'vaadin': 19 | case 'shoelace': 20 | return { ...state, components } 21 | default: 22 | return state 23 | } 24 | }, 25 | setEditorChoice(state, disableEditorChoice: boolean) { 26 | return { 27 | ...state, 28 | disableEditorChoice, 29 | } 30 | }, 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /demos/lit-html/src/state/models/index.ts: -------------------------------------------------------------------------------- 1 | export { componentsSettings } from './components.js' 2 | export { resource } from './resource.js' 3 | export { shape } from './shape.js' 4 | export { rendererSettings } from './renderer.js' 5 | export { playground } from './playground.js' 6 | -------------------------------------------------------------------------------- /demos/lit-html/src/state/models/renderer.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from '@captaincodeman/rdx' 2 | 3 | export interface RendererState { 4 | grouping: 'none' | 'material tabs' | 'vaadin accordion' 5 | nesting: 'none' | 'always one' | 'inline' 6 | labs: { 7 | xone?: boolean 8 | errorSummary?: boolean 9 | } 10 | } 11 | 12 | export const rendererSettings = createModel({ 13 | state: { 14 | grouping: 'none', 15 | nesting: 'none', 16 | labs: {}, 17 | }, 18 | reducers: { 19 | switchNesting(state, nesting: RendererState['nesting']) { 20 | return { 21 | ...state, 22 | nesting, 23 | } 24 | }, 25 | switchLayout(state, grouping: RendererState['grouping']) { 26 | return { 27 | ...state, 28 | grouping, 29 | } 30 | }, 31 | toggleLab({ labs = {}, ...state }, { lab, value } : {lab: keyof Required['labs']; value?: boolean}) { 32 | return { 33 | ...state, 34 | labs: { 35 | ...labs, 36 | [lab]: typeof value !== 'undefined' ? value : !labs[lab], 37 | }, 38 | } 39 | }, 40 | }, 41 | }) 42 | -------------------------------------------------------------------------------- /demos/lit-html/src/state/store.ts: -------------------------------------------------------------------------------- 1 | import type { ModelStore, StoreDispatch, StoreState } from '@captaincodeman/rdx' 2 | import { createStore, persist } from '@captaincodeman/rdx' 3 | import { config } from './config.js' 4 | 5 | export type State = StoreState 6 | export type Dispatch = StoreDispatch 7 | export type Store = ModelStore 8 | 9 | export const store = (() => { 10 | const store = persist(createStore(config), { 11 | persist(state: State) { 12 | const { shape, resource } = state 13 | const { serialized, format, options } = shape 14 | 15 | return { 16 | shape: { 17 | serialized, 18 | format, 19 | quads: [], 20 | dataset: undefined, 21 | options, 22 | shapes: [], 23 | }, 24 | resource: { 25 | format: resource.format, 26 | serialized: resource.serialized, 27 | prefixes: resource.prefixes, 28 | selectedResource: resource.selectedResource, 29 | resourcesToSelect: [], 30 | pointer: undefined, 31 | graph: undefined, 32 | quads: [], 33 | }, 34 | rendererSettings: { 35 | ...state.rendererSettings, 36 | }, 37 | componentsSettings: { 38 | ...state.componentsSettings, 39 | }, 40 | playground: { 41 | ...state.playground, 42 | }, 43 | } 44 | }, 45 | }) 46 | 47 | return () => store 48 | })() 49 | -------------------------------------------------------------------------------- /demos/lit-html/vite.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies,import/no-relative-packages */ 2 | import topLevelAwait from 'vite-plugin-top-level-await' 3 | import { defineConfig, mergeConfig } from 'vite' 4 | import config from '../../vite.config.js' 5 | 6 | export default defineConfig(({ command }) => { 7 | if (command === 'build') { 8 | return mergeConfig(config, { 9 | base: '/playground/', 10 | plugins: [topLevelAwait()], 11 | }) 12 | } 13 | 14 | return mergeConfig(config, { 15 | plugins: [topLevelAwait()], 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /demos/storybook/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import { join, dirname } from 'node:path' 2 | import { StorybookConfig } from '@storybook/web-components-vite' 3 | 4 | /** 5 | * This function is used to resolve the absolute path of a package. 6 | * It is needed in projects that use Yarn PnP or are set up within a monorepo. 7 | */ 8 | const getAbsolutePath = (value: string) => { 9 | return dirname(require.resolve(join(value, 'package.json'))) 10 | } 11 | 12 | const config: StorybookConfig = { 13 | stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 14 | 15 | addons: [ 16 | getAbsolutePath('@storybook/addon-links'), 17 | getAbsolutePath('@storybook/addon-essentials'), 18 | ], 19 | 20 | framework: { 21 | name: getAbsolutePath('@storybook/web-components-vite'), 22 | options: {}, 23 | }, 24 | 25 | staticDirs: [ 26 | '../shapes', 27 | '../../../node_modules/@shoelace-style/' 28 | ], 29 | 30 | docs: {}, 31 | } 32 | export default config 33 | -------------------------------------------------------------------------------- /demos/storybook/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /demos/storybook/data/simple/john-doe.ttl: -------------------------------------------------------------------------------- 1 | PREFIX schema: 2 | BASE 3 | 4 | 5 | a schema:Person ; 6 | schema:givenName "John" ; 7 | schema:familyName "Doe" ; 8 | . 9 | -------------------------------------------------------------------------------- /demos/storybook/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*?raw' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /demos/storybook/lib/env.ts: -------------------------------------------------------------------------------- 1 | import Environment from '@zazuko/env/Environment.js' 2 | import FetchFactory from '@rdfjs/fetch-lite/Factory.js' 3 | import rdf from '@zazuko/env' 4 | import formats from '@rdfjs-elements/formats-pretty' 5 | 6 | const env = new Environment([FetchFactory], { parent: rdf }) 7 | 8 | env.formats.import(formats) 9 | export default env 10 | -------------------------------------------------------------------------------- /demos/storybook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shaperone-stories", 3 | "version": "0.0.0", 4 | "private": "true", 5 | "type": "module", 6 | "scripts": { 7 | "start": "storybook dev -p 6006 --no-open", 8 | "build": "storybook build -o ../../dist/storybook" 9 | }, 10 | "dependencies": { 11 | "@esbuild-plugins/node-globals-polyfill": "^0.2.3", 12 | "@hydrofoil/shaperone-wc": "^0.8.0", 13 | "@hydrofoil/shaperone-wc-shoelace": "^0.4.1", 14 | "@rdfjs/fetch-lite": "^3.3.0", 15 | "@rdfjs-elements/rdf-snippet": "^0.4.5", 16 | "@rdfjs-elements/formats-pretty": "^0.6.7", 17 | "@shoelace-style/shoelace": "^2.15.0", 18 | "@storybook/addon-actions": "^8.4.5", 19 | "@storybook/addon-essentials": "^8.4.5", 20 | "@storybook/addon-links": "^8.4.5", 21 | "@storybook/addon-storysource": "^8.4.5", 22 | "@storybook/blocks": "^8.4.5", 23 | "@storybook/components": "^8.4.5", 24 | "@storybook/web-components": "^8.4.5", 25 | "@storybook/web-components-vite": "^8.4.5", 26 | "@vitejs/plugin-react": "^4.3.3", 27 | "@zazuko/env": "^2", 28 | "lit": "^3", 29 | "onetime": "^7.0.0", 30 | "react-syntax-highlighter": "^15.6.1", 31 | "rollup-plugin-polyfill-node": "^0.13.0", 32 | "storybook": "^8.4.5", 33 | "string-to-stream": "^3.0.1" 34 | }, 35 | "devDependencies": { 36 | "@types/react-syntax-highlighter": "^15.5.13" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /demos/storybook/shapes/simple/datatypes.ttl: -------------------------------------------------------------------------------- 1 | PREFIX xsd: 2 | PREFIX rdf: 3 | PREFIX schema: 4 | PREFIX sh: 5 | 6 | <> 7 | a sh:NodeShape ; 8 | sh:property 9 | [ 10 | sh:name "Name" ; 11 | sh:path schema:name ; 12 | sh:datatype rdf:langString ; 13 | sh:minCount 1 ; 14 | sh:maxCount 1 ; 15 | ], 16 | [ 17 | sh:name "Base Salary" ; 18 | sh:path schema:baseSalary ; 19 | sh:datatype xsd:decimal ; 20 | sh:minCount 1 ; 21 | sh:maxCount 1 ; 22 | ], 23 | [ 24 | sh:name "Age" ; 25 | sh:path schema:age ; 26 | sh:datatype xsd:integer ; 27 | sh:minCount 1 ; 28 | sh:maxCount 1 ; 29 | ] ; 30 | . 31 | -------------------------------------------------------------------------------- /demos/storybook/shapes/simple/first-last.ttl: -------------------------------------------------------------------------------- 1 | PREFIX ex: 2 | PREFIX dash: 3 | PREFIX schema: 4 | PREFIX sh: 5 | 6 | ex:PersonShape 7 | a sh:NodeShape ; 8 | sh:targetClass schema:Person ; 9 | sh:property 10 | [ 11 | sh:name "First Name" ; 12 | sh:path schema:givenName ; 13 | dash:editor dash:TextFieldEditor ; 14 | sh:minCount 1 ; 15 | sh:maxCount 1 ; 16 | ], 17 | [ 18 | sh:name "Last Name" ; 19 | sh:path schema:familyName ; 20 | dash:editor dash:TextFieldEditor ; 21 | sh:minCount 1 ; 22 | sh:maxCount 1 ; 23 | ] ; 24 | . 25 | -------------------------------------------------------------------------------- /demos/storybook/shapes/simple/unrestricted.ttl: -------------------------------------------------------------------------------- 1 | PREFIX ex: 2 | PREFIX dash: 3 | PREFIX schema: 4 | PREFIX sh: 5 | 6 | # Like first-last.ttl, but without the minCount and maxCount constraints 7 | ex:TestShape 8 | a sh:NodeShape ; 9 | sh:targetClass schema:Person ; 10 | sh:property 11 | [ 12 | sh:name "First Name" ; 13 | sh:path schema:givenName ; 14 | dash:editor dash:TextFieldEditor ; 15 | ], 16 | [ 17 | sh:name "Last Name" ; 18 | sh:path schema:familyName ; 19 | dash:editor dash:TextFieldEditor ; 20 | ], 21 | [ 22 | sh:name "Occupation" ; 23 | sh:path schema:jobTitle ; 24 | dash:editor dash:TextFieldEditor ; 25 | ] ; 26 | . 27 | -------------------------------------------------------------------------------- /demos/storybook/stories/Shoelace.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj as Story } from '@storybook/web-components' 2 | import * as shoelace from '@hydrofoil/shaperone-wc-shoelace/components.js' 3 | import * as templates from '@hydrofoil/shaperone-wc-shoelace/templates.js' 4 | import type { ConfigCallback } from '@hydrofoil/shaperone-wc/configure.js' 5 | import { render } from './render.js' 6 | import firstLast from '../shapes/simple/first-last.ttl?raw' 7 | 8 | const configure: ConfigCallback = ({ components, renderer }) => { 9 | components.pushComponents(shoelace) 10 | renderer.setTemplates(templates) 11 | } 12 | 13 | const meta: Meta = { 14 | component: 'shaperone-form', 15 | argTypes: { 16 | focusNode: { 17 | control: 'text', 18 | }, 19 | shape: { 20 | control: 'text', 21 | }, 22 | prefixes: { 23 | control: 'text', 24 | }, 25 | }, 26 | args: { 27 | prefixes: 'schema', 28 | }, 29 | } 30 | 31 | export default meta 32 | 33 | /** 34 | * Without setting the data graph, the form will be empty and a `<>` IRI used for focus node. 35 | */ 36 | export const EmptyDataGraph: Story = { 37 | name: 'Empty data graph', 38 | args: { 39 | shapes: firstLast, 40 | shape: 'http://example.org/PersonShape', 41 | }, 42 | loaders: [ 43 | async () => ({ 44 | configure, 45 | }), 46 | ], 47 | render, 48 | } 49 | -------------------------------------------------------------------------------- /demos/storybook/stories/render.ts: -------------------------------------------------------------------------------- 1 | import type { GraphPointer } from 'clownface' 2 | import type { WebComponentsRenderer } from '@storybook/web-components' 3 | import type { ArgsStoryFn } from '@storybook/csf' 4 | import { html } from 'lit' 5 | 6 | import '../lib/shaperone-demo.js' 7 | 8 | interface Args { 9 | focusNode?: string 10 | prefixes?: string 11 | shapes?: string 12 | data?: string 13 | debug?: boolean 14 | } 15 | 16 | const render: ArgsStoryFn = function ({ focusNode, prefixes, debug, ...raw }: Args, { loaded: { shapes, data, configure } }) { 17 | let resource: GraphPointer | undefined 18 | if (focusNode) { 19 | resource = data.namedNode(focusNode) 20 | } 21 | return html` 22 | 23 | 24 | ` 25 | } 26 | 27 | export { render } 28 | -------------------------------------------------------------------------------- /demos/storybook/vite.config.js: -------------------------------------------------------------------------------- 1 | import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill' 2 | import rollupNodePolyFill from 'rollup-plugin-polyfill-node' 3 | import react from '@vitejs/plugin-react' 4 | 5 | export default { 6 | define: { 7 | global: 'window', 8 | }, 9 | resolve: { 10 | alias: { 11 | stream: 'readable-stream', 12 | zlib: 'browserify-zlib', 13 | util: 'util', 14 | }, 15 | }, 16 | optimizeDeps: { 17 | esbuildOptions: { 18 | plugins: [ 19 | NodeGlobalsPolyfillPlugin({ 20 | process: true, 21 | buffer: true, 22 | }), 23 | ], 24 | }, 25 | }, 26 | build: { 27 | rollupOptions: { 28 | plugins: [rollupNodePolyFill()], 29 | }, 30 | }, 31 | plugins: [ 32 | react({ 33 | jsxRuntime: 'automatic', 34 | }), 35 | ], 36 | } 37 | -------------------------------------------------------------------------------- /dist/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/.nojekyll -------------------------------------------------------------------------------- /dist/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](_media/logo.png) 2 | 3 | # @hydrofoil/shaperone 4 | 5 | > SHACL Form generator 6 | 7 | - Compatible with standard RDF/JS tooling 8 | - Highly customizable 9 | - Using modern web technologies 10 | 11 | [Get started](overview) 12 | [GitHub](https://github.com/hypermedia-app/shaperone) 13 | [JS API](/api) 14 | [`shaperone-form`](/shaperone-form) 15 | [Playground](/playground) 16 | -------------------------------------------------------------------------------- /dist/_media/GitHub-Mark-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/_media/GitHub-Mark-32px.png -------------------------------------------------------------------------------- /dist/_media/anatomy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/_media/anatomy.png -------------------------------------------------------------------------------- /dist/_media/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/_media/configuration.png -------------------------------------------------------------------------------- /dist/_media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/_media/logo.png -------------------------------------------------------------------------------- /dist/_media/multi-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/_media/multi-select.png -------------------------------------------------------------------------------- /dist/_media/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/_media/stack.png -------------------------------------------------------------------------------- /dist/_media/star-rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypermedia-app/shaperone/824456560ac22410f577bcde09b7dfde79211ca7/dist/_media/star-rating.png -------------------------------------------------------------------------------- /dist/_sidebar.md: -------------------------------------------------------------------------------- 1 | * Getting started 2 | * [Intro](overview.md "Shaperone | Intro") 3 | * [Getting Started](getting-started.md "Shaperone | Getting Started") 4 | * [Configuration](configuration.md "Shaperone | Configuration") 5 | * [Anatomy of shaperone](anatomy.md "Shaperone | Anatomy") 6 | * Editors 7 | * [Overview](editors.md "Shaperone | Editors") 8 | * [DASH](editors/dash.md "Shaperone | DASH") 9 | * [Matchers](editors/matchers.md "Shaperone | Editor matchers") 10 | * [Metadata](editors/metadata.md "Shaperone | Editor metadata") 11 | * [Core library](core.md "Shaperone | Core") 12 | * [Advanced shapes](advanced.md "Shaperone | Advanced") 13 | * [Validation](validation.md "Shaperone | Validation") 14 | * Renderer 15 | * [Overview](renderer/core.md) 16 | * [Web Components](renderer/web-components.md) 17 | * Web Components 18 | * [DASH](components/dash.md "Shaperone | Component | DASH") 19 | * [Design systems](components/design-systems/ "Shaperone | Components | Design system") 20 | * [Material Design](components/design-systems/material.md "Shaperone | Components | Material Design") 21 | * [Vaadin](components/design-systems/vaddin.md "Shaperone | Components | Vaadin") 22 | * [Create your own](components/implement.md "Shaperone | Implementing components") 23 | * Extensions 24 | * [Hydra](extensions/hydra.md "Shaperone | Hydra") 25 | * [Plugins](extensions/plugins.md "Shaperone | Plugins") 26 | * [API](/api) 27 | -------------------------------------------------------------------------------- /dist/extensions/plugins.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | A low-level feature allows adding custom functionality directly to the state store. This attaches directly to the 4 | [rdx](https://github.com/CaptainCodeman/rdx) library and allows full access to action invoked on the store. 5 | 6 | ## Setup 7 | 8 | Use `addPlugin` to have it added to the store. Make sure to call this before a form is initialised on a page. 9 | 10 | ```js 11 | import { addPlugin } from '@hydrofoil/shaperone-core/store' 12 | 13 | const loggerPlugin = { 14 | // a plugin can have its own state 15 | model: { 16 | state: { enabled: false }, 17 | reducers: { 18 | toggle(state) { 19 | return { ...state, enabled: !state.enabled } 20 | } 21 | }, 22 | // and (async) effects which listen and call other models, or do fancy stuff 23 | effects(store) { 24 | const dispatch = store.getDispatch() 25 | 26 | return { 27 | 'forms/initObjectValue'(arg) { 28 | console.log('Initialized property', arg) 29 | 30 | dispatch.forms.updateComponentState({ 31 | ...arg, 32 | newState: { 33 | observed: true 34 | } 35 | }) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | addPlugins({ 43 | loggerPlugin 44 | }) 45 | ``` 46 | 47 | ## See more 48 | 49 | Unfortunately, the plugin feature is not well documented. See the built-in plugins for inspiration: [effects](https://github.com/CaptainCodeman/rdx/blob/master/src/effectsPlugin.ts), 50 | [routing](https://github.com/CaptainCodeman/rdx/blob/master/src/routingPlugin.ts) 51 | -------------------------------------------------------------------------------- /dist/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | To add a most basic form to you app create a `` element in your HTML and set its `shapes` property with a [RDF/JS Dataset](https://rdf.js.org/dataset-spec/#datasetcore-interface) or [clownface](https://zazuko.github.io/clownface) graph pointer. 4 | 5 | ```js 6 | // 1. Import the Web Component's package 7 | import '@hydrofoil/shaperone-wc/shaperone-form.js' 8 | 9 | // 2. Load or create shape's RDF representation 10 | const shape = fetchShape() 11 | 12 | // 3. Set the shape to the element 13 | document.querySelector('shaperone-form').shapes = shape 14 | ``` 15 | 16 | To access the contents of a form get the `.resource` property which again returns a [clownface](https://zazuko.github.io/clownface) graph pointer object. 17 | 18 | > [!TIP] 19 | > Naturally, the `.resource` property also has a getter. If not set, the form will be initialized with an empty string `<>` named node. 20 | 21 | Below you'll find a running example rendering a very simple form. It also adds a simple button which will open the resulting RDF in a new tab, serialized as turtle. 22 | 23 | 29 | -------------------------------------------------------------------------------- /dist/renderer/core.md: -------------------------------------------------------------------------------- 1 | # Renderer 2 | 3 | While the core library does not provide any rendering code, it defines a set of base interfaces for renderers. At the highest level a renderer is a simple interface, with but a single method to render the entire form. 4 | 5 | ```typescript 6 | /** 7 | * @module @hydrofoil/shaperone-core/renderer 8 | */ 9 | 10 | export interface RenderContext { 11 | form: symbol 12 | editors: EditorsState 13 | state: FormState 14 | components: ComponentsState 15 | dispatch: Dispatch 16 | shapes: NodeShape[] 17 | } 18 | 19 | 20 | export interface Renderer { 21 | render(params: RenderContext): TRenderResult 22 | } 23 | ``` 24 | 25 | However, the assumption is that the implementation breaks up the rendering in at least the following parts: 26 | 27 | 1. [The form](/api/interfaces/_hydrofoil_shaperone_core_renderer.formrenderer.html) itself at the top level 28 | 2. [Focus Node](/api/interfaces/_hydrofoil_shaperone_core_renderer.focusnoderenderer.html) 29 | 3. (Optionally) [Group Shapes](/api/interfaces/_hydrofoil_shaperone_core_renderer.grouprenderer.html) 30 | 4. [Property Shapes](/api/interfaces/_hydrofoil_shaperone_core_renderer.propertyrenderer.html) 31 | 5. [Objects](/api/interfaces/_hydrofoil_shaperone_core_renderer.objectrenderer.html) 32 | 33 | Each subsequent renderer level expands the one above by adding the context objects and mutation callbacks, called `actions`. For example, a `PropertyRenderer` combines its parents and adds the property state object and functions. 34 | 35 | Although it is up to the implementors to follow this pattern exactly or not, ultimately the components' render method require an instance of `ObjectRenderer`, which represents the object's position in the graph. 36 | -------------------------------------------------------------------------------- /dist/renderer/web-components.md: -------------------------------------------------------------------------------- 1 | # Rendering Web Components 2 | 3 | TBD 4 | -------------------------------------------------------------------------------- /dist/variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "playground": "http://localhost:8080" 3 | } 4 | -------------------------------------------------------------------------------- /packages/core-tests/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @hydrofoil/shaperone-core-tests 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - 40dd516: Build: fixes some incorrect imports in generated d.ts files 8 | 9 | ## 0.1.0 10 | 11 | ### Minor Changes 12 | 13 | - e76afd5: Use a centralised RDF/JS Environment 14 | 15 | ### Patch Changes 16 | 17 | - e76afd5: Update RDF/JS-related dependencies (closes #300) 18 | 19 | ## 0.0.1 20 | 21 | ### Patch Changes 22 | 23 | - 61ac785: Update `@tpluscode/rdf-ns-builders` to v2 24 | -------------------------------------------------------------------------------- /packages/core-tests/lib/property.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { sh, xsd } from '@tpluscode/rdf-ns-builders' 5 | import { createTerm } from '@hydrofoil/shaperone-core/lib/property.js' 6 | import { propertyShape } from '@shaperone/testing/util.js' 7 | 8 | describe('core/lib/property', () => { 9 | describe('createTerm', () => { 10 | it('creates named node when property has sh:IRI kind', () => { 11 | // given 12 | const pointer = $rdf.clownface().blankNode() 13 | const property = { 14 | shape: propertyShape(pointer, { 15 | [sh.nodeKind.value]: sh.IRI, 16 | }), 17 | } 18 | 19 | // when 20 | const term = createTerm($rdf, property, 'http://foo/bar') 21 | 22 | // then 23 | expect(term.value).to.equal('http://foo/bar') 24 | }) 25 | 26 | it('creates typed literal when property has sh:datatype', () => { 27 | // given 28 | const pointer = $rdf.clownface().blankNode() 29 | const property = { 30 | shape: propertyShape(pointer), 31 | datatype: xsd.int, 32 | } 33 | 34 | // when 35 | const term = createTerm($rdf, property, '41') 36 | 37 | // then 38 | expect(term).to.deep.equal($rdf.literal('41', xsd.int)) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/core-tests/models/editors/effects/loadDash.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import { testStore } from '@shaperone/testing/models/form.js' 4 | import type { Store } from '@hydrofoil/shaperone-core/state' 5 | import { loadDash } from '@hydrofoil/shaperone-core/models/editors/effects/index.js' 6 | 7 | describe('models/editors/effects/loadDash', () => { 8 | let store: Store 9 | 10 | beforeEach(() => { 11 | store = testStore() 12 | }) 13 | 14 | it('does not add metadata twice', async () => { 15 | // given 16 | const effect = loadDash(store) 17 | await effect() 18 | 19 | // when 20 | await effect() 21 | 22 | // expect 23 | const dispatch = store.getDispatch() 24 | expect(dispatch.editors.addMatchers).to.have.been.calledOnce 25 | expect(dispatch.editors.addMetadata).to.have.been.calledOnce 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/effects/removeObject.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { dash, schema, sh } from '@tpluscode/rdf-ns-builders' 5 | import { testFocusNodeState, testPropertyState, testStore } from '@shaperone/testing/models/form.js' 6 | import type { Store } from '@hydrofoil/shaperone-core/state' 7 | import { removeObject } from '@hydrofoil/shaperone-core/models/forms/effects/removeObject.js' 8 | import { nodeShape, propertyShape } from '@shaperone/testing/util.js' 9 | 10 | describe('models/forms/effects/removeObject', () => { 11 | let store: Store 12 | 13 | beforeEach(() => { 14 | store = testStore() 15 | }) 16 | 17 | it('dispatches update of property linked by sh:equals', () => { 18 | // given 19 | const focusNode = $rdf.clownface().namedNode('foo') 20 | const shapesGraph = $rdf.clownface() 21 | const property = propertyShape(shapesGraph.blankNode(), { 22 | path: schema.name, 23 | }) 24 | const synced = propertyShape(shapesGraph.blankNode(), { 25 | [dash.hidden.value]: true, 26 | path: schema.givenName, 27 | [sh.equals.value]: schema.name, 28 | }) 29 | store.getState().form.focusNodes = testFocusNodeState(focusNode, { 30 | shape: nodeShape(shapesGraph.blankNode()), 31 | properties: [testPropertyState(property.pointer), testPropertyState(synced.pointer)], 32 | }) 33 | 34 | // when 35 | removeObject(store)({ 36 | focusNode, 37 | property, 38 | }) 39 | 40 | // then 41 | const dispatch = store.getDispatch() 42 | expect(dispatch.form.setPropertyObjects).to.have.been.called 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/effects/replaceObjects.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import $rdf from '@shaperone/testing/env.js' 3 | import { expect } from 'chai' 4 | import type { sinon } from '@shaperone/testing' 5 | import { testStore } from '@shaperone/testing/models/form.js' 6 | import { replaceObjects } from '@hydrofoil/shaperone-core/models/forms/effects/replaceObjects.js' 7 | import type { Store } from '@hydrofoil/shaperone-core/state' 8 | import { propertyShape } from '@shaperone/testing/util.js' 9 | 10 | describe('models/forms/effects/replaceObjects', () => { 11 | let store: Store 12 | 13 | beforeEach(() => { 14 | store = testStore() 15 | }) 16 | 17 | it('dispatches setPropertyObjects with pointers from resource graph', () => { 18 | // given 19 | const { dataset } = store.getState().resources.graph! 20 | const property = propertyShape() 21 | const focusNode = $rdf.clownface().blankNode() 22 | const terms = [ 23 | $rdf.literal('a'), 24 | $rdf.literal('b'), 25 | $rdf.literal('c'), 26 | ] 27 | 28 | // when 29 | replaceObjects(store)({ 30 | property, 31 | focusNode, 32 | terms, 33 | }) 34 | 35 | // then 36 | const dispatch = store.getDispatch() 37 | const [call] = (dispatch.form.setPropertyObjects as sinon.SinonStub).getCalls() 38 | expect(call.lastArg.objects.dataset === dataset) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/effects/selectShape.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import $rdf from '@shaperone/testing/env.js' 3 | import { expect } from 'chai' 4 | import type { sinon } from '@shaperone/testing' 5 | import type { NodeShape } from '@rdfine/shacl' 6 | import { testStore } from '@shaperone/testing/models/form.js' 7 | import { selectShape } from '@hydrofoil/shaperone-core/models/forms/effects/selectShape.js' 8 | import type { Store } from '@hydrofoil/shaperone-core/state' 9 | 10 | describe('models/forms/effects/selectShape', () => { 11 | let store: Store 12 | 13 | beforeEach(() => { 14 | store = testStore() 15 | }) 16 | 17 | it('creates state with selected shape', () => { 18 | // given 19 | const shape = {} as NodeShape 20 | const focusNode = $rdf.clownface().blankNode() 21 | 22 | // when 23 | selectShape(store)({ 24 | focusNode, 25 | shape, 26 | }) 27 | 28 | // then 29 | const dispatch = store.getDispatch() 30 | const [call] = (dispatch.form.createFocusNodeState as sinon.SinonStub).getCalls() 31 | expect(call.lastArg.shape === shape) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/effects/setObjectValue.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { dash, schema, sh } from '@tpluscode/rdf-ns-builders' 5 | import { testStore, testFocusNodeState, testPropertyState } from '@shaperone/testing/models/form.js' 6 | import type { Store } from '@hydrofoil/shaperone-core/state' 7 | import { setObjectValue } from '@hydrofoil/shaperone-core/models/forms/effects/setObjectValue.js' 8 | import { nodeShape, propertyShape } from '@shaperone/testing/util.js' 9 | 10 | describe('models/forms/effects/setObjectValue', () => { 11 | let store: Store 12 | 13 | beforeEach(() => { 14 | store = testStore() 15 | }) 16 | 17 | it('dispatches update of property linked by sh:equals', () => { 18 | // given 19 | const focusNode = $rdf.clownface().namedNode('foo') 20 | const shapesGraph = $rdf.clownface() 21 | const property = propertyShape(shapesGraph.blankNode(), { 22 | path: schema.name, 23 | }) 24 | const synced = propertyShape(shapesGraph.blankNode(), { 25 | [dash.hidden.value]: true, 26 | path: schema.givenName, 27 | [sh.equals.value]: schema.name, 28 | }) 29 | store.getState().form.focusNodes = testFocusNodeState(focusNode, { 30 | shape: nodeShape(shapesGraph.blankNode()), 31 | properties: [testPropertyState(property.pointer), testPropertyState(synced.pointer)], 32 | }) 33 | 34 | // when 35 | setObjectValue(store)({ 36 | focusNode, 37 | property, 38 | }) 39 | 40 | // then 41 | const dispatch = store.getDispatch() 42 | expect(dispatch.form.setPropertyObjects).to.have.been.called 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/effects/setPropertyObjects.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { dash, schema, sh } from '@tpluscode/rdf-ns-builders' 5 | import { testFocusNodeState, testPropertyState, testStore } from '@shaperone/testing/models/form.js' 6 | import type { Store } from '@hydrofoil/shaperone-core/state' 7 | import { setPropertyObjects } from '@hydrofoil/shaperone-core/models/forms/effects/setPropertyObjects.js' 8 | import { nodeShape, propertyShape } from '@shaperone/testing/util.js' 9 | 10 | describe('models/forms/effects/setPropertyObjects', () => { 11 | let store: Store 12 | 13 | beforeEach(() => { 14 | store = testStore() 15 | }) 16 | 17 | it('dispatches update of property linked by sh:equals', () => { 18 | // given 19 | const focusNode = $rdf.clownface().namedNode('foo') 20 | const shapesGraph = $rdf.clownface() 21 | const property = propertyShape(shapesGraph.blankNode(), { 22 | path: schema.name, 23 | }) 24 | const synced = propertyShape(shapesGraph.blankNode(), { 25 | [dash.hidden.value]: true, 26 | path: schema.givenName, 27 | [sh.equals.value]: schema.name, 28 | }) 29 | store.getState().form.focusNodes = testFocusNodeState(focusNode, { 30 | shape: nodeShape(shapesGraph.blankNode()), 31 | properties: [testPropertyState(property.pointer), testPropertyState(synced.pointer)], 32 | }) 33 | 34 | // when 35 | setPropertyObjects(store)({ 36 | focusNode, 37 | property, 38 | }) 39 | 40 | // then 41 | const dispatch = store.getDispatch() 42 | expect(dispatch.form.setPropertyObjects).to.have.been.called 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/effects/shapes/setGraph.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import $rdf from '@shaperone/testing/env.js' 3 | import { rdf, sh } from '@tpluscode/rdf-ns-builders' 4 | import { expect } from 'chai' 5 | import { testStore } from '@shaperone/testing/models/form.js' 6 | import setGraph from '@hydrofoil/shaperone-core/models/forms/effects/shapes/setGraph.js' 7 | import type { Store } from '@hydrofoil/shaperone-core/state' 8 | 9 | const ex = $rdf.namespace('http://example.com/') 10 | 11 | describe('models/forms/effects/shapes/setGraph', () => { 12 | let store: Store 13 | 14 | beforeEach(() => { 15 | store = testStore() 16 | }) 17 | 18 | it('creates focus nodes state for focus stack', () => { 19 | // given 20 | const resourceGraph = $rdf.clownface() 21 | const shapesGraph = $rdf.clownface() 22 | shapesGraph.node(ex.Shape).addOut(rdf.type, sh.Shape).addOut(sh.targetNode, [ex.Foo, ex.Bar]) 23 | const formState = store.getState().form 24 | formState.focusStack = [ 25 | resourceGraph.node(ex.Foo), 26 | resourceGraph.node(ex.Bar), 27 | ] 28 | 29 | // when 30 | setGraph(store)() 31 | 32 | // then 33 | const spy = store.getDispatch().form.createFocusNodeState as sinon.SinonSpy 34 | expect(spy.getCalls().map(c => c.firstArg)).to.containSubset([ 35 | { focusNode: resourceGraph.node(ex.Foo) }, 36 | { focusNode: resourceGraph.node(ex.Bar) }, 37 | ]) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/effects/validate.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { testStore, testFocusNodeState } from '@shaperone/testing/models/form.js' 5 | import { sinon } from '@shaperone/testing' 6 | import type { Store } from '@hydrofoil/shaperone-core/state' 7 | import { validate } from '@hydrofoil/shaperone-core/models/forms/effects/validate.js' 8 | 9 | describe('@hydrofoil/shaperone-core/models/forms/effects/validate', () => { 10 | let store: Store 11 | 12 | beforeEach(() => { 13 | store = testStore() 14 | }) 15 | 16 | it('calls validator and dispatches report update', async () => { 17 | // given 18 | const focusNode = $rdf.clownface().namedNode('foo') 19 | store.getState().form.focusNodes = testFocusNodeState(focusNode) 20 | const validationResult = { 21 | term: $rdf.blankNode(), 22 | dataset: $rdf.dataset(), 23 | } 24 | store.getState().validation.validator = sinon.stub().resolves(validationResult) 25 | 26 | // when 27 | await validate(store)() 28 | 29 | // then 30 | const dispatch = store.getDispatch() 31 | expect(store.getState().validation.validator).to.have.been.called 32 | expect(dispatch.form.validationReport).to.have.been.called 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/reducers/pushFocusNode.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { sinon } from '@shaperone/testing' 5 | import { testStore } from '@shaperone/testing/models/form.js' 6 | import { pushFocusNode } from '@hydrofoil/shaperone-core/models/forms/effects/pushFocusNode.js' 7 | import type { Store } from '@hydrofoil/shaperone-core/state' 8 | import { propertyShape } from '@shaperone/testing/util.js' 9 | 10 | const ex = $rdf.namespace('http://example.com/') 11 | 12 | describe('core/models/forms/reducers/pushFocusNode', () => { 13 | let store: Store 14 | beforeEach(() => { 15 | store = testStore() 16 | }) 17 | 18 | it('dispatches initialization of new node state', () => { 19 | // given 20 | const graph = $rdf.clownface() 21 | const property = propertyShape(graph.namedNode(ex.propertyShape)) 22 | 23 | // when 24 | pushFocusNode(store)({ 25 | focusNode: graph.node(ex.FocusNode), 26 | property, 27 | }) 28 | 29 | // then 30 | expect(store.getDispatch().form.createFocusNodeState).to.have.been.calledWith(sinon.match({ 31 | appendToStack: true, 32 | })) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/core-tests/models/forms/reducers/selectGroup.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { testFocusNodeState, testFormState as testState } from '@shaperone/testing/models/form.js' 5 | import { selectGroup } from '@hydrofoil/shaperone-core/models/forms/reducers/selectGroup.js' 6 | 7 | const ex = $rdf.namespace('http://example.com/') 8 | 9 | describe('core/models/forms/reducers/selectGroup', () => { 10 | it('sets flag on selected group and unsets others', () => { 11 | // given 12 | const graph = $rdf.clownface() 13 | const focusNode = graph.node(ex.FocusNode) 14 | const state = testState({ 15 | focusNodes: { 16 | ...testFocusNodeState(focusNode, { 17 | groups: [{ 18 | selected: true, 19 | group: $rdf.rdfine.sh.PropertyGroup(graph.node(ex.Group0)), 20 | order: 0, 21 | }, { 22 | selected: true, 23 | group: $rdf.rdfine.sh.PropertyGroup(graph.node(ex.Group1)), 24 | order: 1, 25 | }], 26 | }), 27 | }, 28 | }) 29 | 30 | // when 31 | const next = selectGroup(state, { focusNode, group: $rdf.rdfine.sh.PropertyGroup(graph.node(ex.Group1)) }) 32 | 33 | // then 34 | const focusNodeState = next.focusNodes[ex.FocusNode.value] 35 | expect(focusNodeState.groups[0].selected).to.be.false 36 | expect(focusNodeState.groups[1].selected).to.be.true 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/core-tests/models/resources/reducers/setRoot.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import $rdf from '@shaperone/testing/env.js' 4 | import { testStore } from '@shaperone/testing/models/form.js' 5 | import { setRoot } from '@hydrofoil/shaperone-core/models/resources/reducers/setRoot.js' 6 | 7 | const ex = $rdf.namespace('http://example.com/') 8 | 9 | describe('core/models/resources/reducers/setRoot', () => { 10 | it('sets dataset to state', () => { 11 | // given 12 | const dataset = $rdf.dataset() 13 | const graph = $rdf.clownface({ dataset }) 14 | const store = testStore() 15 | 16 | // when 17 | const after = setRoot(store.getState().resources, { 18 | rootPointer: graph.node(ex.Foo), 19 | }) 20 | 21 | // then 22 | expect(after.graph === graph) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /packages/core-tests/models/validation/reducers/setValidator.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha' 2 | import { expect } from 'chai' 3 | import type { ValidatorState } from '@hydrofoil/shaperone-core/models/validation' 4 | import { setValidator } from '@hydrofoil/shaperone-core/models/validation/reducers/setValidator.js' 5 | 6 | describe('@hydrofoil/shaperone-core/models/validation/reducers/setValidator', () => { 7 | it('gets replaced in state', () => { 8 | // given 9 | const before: ValidatorState = { 10 | validator: async () => ({} as any), 11 | } 12 | 13 | // when 14 | const newValidator = async () => ({} as any) 15 | const after = setValidator(before, newValidator) 16 | 17 | // then 18 | expect(after.validator).to.eq(newValidator) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/core-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hydrofoil/shaperone-core-tests", 3 | "private": true, 4 | "version": "0.1.1", 5 | "type": "module", 6 | "scripts": { 7 | "test": "mocha \"**/*.ts\" --exclude \"node_modules/**/*.ts\"" 8 | }, 9 | "devDependencies": { 10 | "@hydrofoil/shaperone-core": "*", 11 | "@rdfine/shacl": "^0.10.5", 12 | "@shaperone/testing": "*", 13 | "@tpluscode/rdf-ns-builders": "^4.3.0", 14 | "@zazuko/env": "^2.1.0", 15 | "chai": "^5", 16 | "mocha": "^11.0.0", 17 | "@types/chai": "^5", 18 | "@types/chai-subset": "^1.3.5", 19 | "@types/mocha": "^10.0.10", 20 | "@types/sinon-chai": "^3.2.4", 21 | "autoesm": "^1.0.5", 22 | "chai-snapshot-matcher": "^2.0.3", 23 | "chai-subset": "^1.6.0", 24 | "chai-quantifiers": "^1.0.18", 25 | "deepmerge": "^4.2.2", 26 | "promise-the-world": "^1.0.1", 27 | "rdf-dataset-ext": "^1.1.0" 28 | }, 29 | "mocha": { 30 | "require": [ 31 | "./test-setup.ts" 32 | ], 33 | "loader": "ts-node/esm/transpile-only" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core-tests/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'chai-snapshot-matcher' 2 | import chaiQuantifiers from 'chai-quantifiers' 3 | import * as chai from 'chai' 4 | import sinonChai from 'sinon-chai' 5 | import chaiSubset from 'chai-subset' 6 | import type { RequiredEnvironment } from '@hydrofoil/shaperone-core/env.js' 7 | import { setEnv } from '@hydrofoil/shaperone-core/env.js' 8 | import rdf from '@zazuko/env' 9 | 10 | chai.use(sinonChai) 11 | chai.use(chaiQuantifiers) 12 | chai.use(chaiSubset) 13 | 14 | export const mochaHooks = { 15 | beforeAll() { 16 | setEnv(rdf as unknown as RequiredEnvironment) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/esbuild.config.js: -------------------------------------------------------------------------------- 1 | import p from './package.json' with { type: 'json' } 2 | 3 | export default { 4 | entryPoints: [ 5 | '*.ts', 6 | 'lib/**/*.ts', 7 | 'models/**/*.ts', 8 | ], 9 | format: 'esm', 10 | platform: 'browser', 11 | outdir: '.', 12 | bundle: true, 13 | splitting: true, 14 | sourcemap: true, 15 | external: [ 16 | 'crypto', 17 | ...Object.keys(p.dependencies), 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @module @hydrofoil/shaperone-core 4 | */ 5 | 6 | import type { BlankNode, NamedNode } from '@rdfjs/types' 7 | import type { GraphPointer } from 'clownface' 8 | import '@rdfine/dash/extensions/sh/PropertyShape' 9 | 10 | /** 11 | * A [focus node](https://www.w3.org/TR/shacl/#focusNodes) is a graph pointer to a Named Node or a Blank Node 12 | */ 13 | export type FocusNode = GraphPointer 14 | export type { Component, SingleEditorComponent, MultiEditorComponent, RenderComponent, Lazy } from './models/components/index.js' 15 | export type { Editor, SingleEditor, MultiEditor } from './models/editors/index.js' 16 | -------------------------------------------------------------------------------- /packages/core/lib/RecursivePartial.ts: -------------------------------------------------------------------------------- 1 | export type RecursivePartial = { 2 | [P in keyof T]?: 3 | T[P] extends (infer U)[] ? RecursivePartial[] : 4 | // eslint-disable-next-line @typescript-eslint/ban-types 5 | T[P] extends object ? RecursivePartial : 6 | T[P]; 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/lib/components.ts: -------------------------------------------------------------------------------- 1 | import type { GraphPointer } from 'clownface' 2 | import { getLocalizedLabel } from '@rdfjs-elements/lit-helpers' 3 | import { rdfs } from '@tpluscode/rdf-ns-builders' 4 | import type { PropertyShape } from '@rdfine/shacl' 5 | import type { SingleEditorComponent } from '../models/components/index.js' 6 | import env from '../env.js' 7 | 8 | export type CoreComponent> = Omit 9 | 10 | export function sort(shape: PropertyShape) { 11 | const orderByList = [...shape.pointer.out(env().ns.sh1.orderBy).list() || []] 12 | const orderByPredicates = orderByList.length ? orderByList.map(i => i.term) : [rdfs.label] 13 | 14 | return (left: GraphPointer, right: GraphPointer) => orderByPredicates.reduce((result, predicate) => { 15 | if (result) { 16 | return result 17 | } 18 | 19 | const leftLabel = getLocalizedLabel(left.out(predicate)) || left.value 20 | const rightLabel = getLocalizedLabel(right.out(predicate)) || right.value 21 | return leftLabel.localeCompare(rightLabel) 22 | }, 0) 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/lib/components/autoComplete.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { CoreComponent } from '../components.js' 3 | import { sort } from '../components.js' 4 | import * as select from './base/instancesSelect.js' 5 | import type { State, Editor } from './base/instancesSelect.js' 6 | 7 | /** 8 | * Represents the state of an instances select component 9 | */ 10 | export interface AutoComplete extends State { 11 | freetextQuery?: string 12 | } 13 | 14 | export interface AutoCompleteEditor extends Editor { 15 | } 16 | 17 | /** 18 | * A base implementation of Instances Select component which sets {@link InstancesSelect.ready} state flag the instances are first loaded. 19 | * 20 | * The instance data will be loaded from the shapes graph 21 | */ 22 | export const autoComplete: CoreComponent = { 23 | editor: dash.AutoCompleteEditor, 24 | ...select, 25 | sort, 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/lib/components/blankNode.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders/loose' 2 | import type { ComponentInstance, SingleEditorComponent } from '../../models/components/index.js' 3 | import type { CoreComponent } from '../components.js' 4 | 5 | /** 6 | * Instance state of blank node component 7 | */ 8 | export interface BlankNode extends ComponentInstance { 9 | } 10 | 11 | export interface BlankNodeEditor extends SingleEditorComponent { 12 | } 13 | 14 | /** 15 | * Extend to implement [DASH blank node editor](http://datashapes.org/forms.html#BlankNodeEditor) 16 | */ 17 | export const blankNode: CoreComponent = { 18 | editor: dash.BlankNodeEditor, 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/lib/components/booleanSelect.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { ComponentInstance, SingleEditorComponent } from '../../models/components/index.js' 3 | import type { CoreComponent } from '../components.js' 4 | 5 | /** 6 | * Instance state of boolean select component 7 | */ 8 | export interface BooleanSelect extends ComponentInstance { 9 | } 10 | 11 | export interface BooleanSelectEditor extends SingleEditorComponent { 12 | } 13 | 14 | /** 15 | * Extend to implement [DASH boolean select editor](http://datashapes.org/forms.html#BooleanSelectEditor) 16 | */ 17 | export const booleanSelect: CoreComponent = { 18 | editor: dash.BooleanSelectEditor, 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/lib/components/datePicker.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { ComponentInstance, SingleEditorComponent } from '../../models/components/index.js' 3 | import type { CoreComponent } from '../components.js' 4 | 5 | /** 6 | * Instance state of date picker component 7 | */ 8 | export interface DatePicker extends ComponentInstance { 9 | } 10 | 11 | export interface DatePickerEditor extends SingleEditorComponent { 12 | } 13 | 14 | /** 15 | * Extend to implement [DASH date picker editor](http://datashapes.org/forms.html#DatePickerEditor) 16 | */ 17 | export const datePicker: CoreComponent = { 18 | editor: dash.DatePickerEditor, 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/lib/components/dateTimePicker.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { ComponentInstance, SingleEditorComponent } from '../../models/components/index.js' 3 | import type { CoreComponent } from '../components.js' 4 | 5 | /** 6 | * Instance state of date-time picker component 7 | */ 8 | export interface DateTimePicker extends ComponentInstance { 9 | } 10 | 11 | export interface DateTimePickerEditor extends SingleEditorComponent { 12 | } 13 | 14 | /** 15 | * Extend to implement [DASH date-time picker editor](http://datashapes.org/forms.html#DateTimePickerEditor) 16 | */ 17 | export const dateTimePicker: CoreComponent = { 18 | editor: dash.DateTimePickerEditor, 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/lib/components/details.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { ComponentInstance, SingleEditorComponent } from '../../models/components/index.js' 3 | import type { CoreComponent } from '../components.js' 4 | 5 | /** 6 | * Instance state of details component 7 | */ 8 | export interface Details extends ComponentInstance { 9 | } 10 | 11 | export interface DetailsEditor extends SingleEditorComponent
{ 12 | } 13 | 14 | /** 15 | * Extend to implement [DASH details editor](http://datashapes.org/forms.html#DetailsEditor) 16 | */ 17 | export const details: CoreComponent = { 18 | editor: dash.DetailsEditor, 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/lib/components/instancesSelect.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { CoreComponent } from '../components.js' 3 | import { sort } from '../components.js' 4 | import * as select from './base/instancesSelect.js' 5 | import type { State, Editor } from './base/instancesSelect.js' 6 | 7 | /** 8 | * Represents the state of an instances select component 9 | */ 10 | export interface InstancesSelect extends State { 11 | } 12 | 13 | export interface InstancesSelectEditor extends Editor { 14 | } 15 | 16 | /** 17 | * A base implementation of Instances Select component which sets {@link InstancesSelect.ready} state flag the instances are first loaded. 18 | * 19 | * The instance data will be loaded from the shapes graph 20 | */ 21 | export const instancesSelect: CoreComponent = { 22 | editor: dash.InstancesSelectEditor, 23 | ...select, 24 | sort, 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/lib/components/richText.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { ComponentInstance, SingleEditorComponent } from '../../models/components/index.js' 3 | import type { CoreComponent } from '../components.js' 4 | 5 | /** 6 | * Instance state of rich text component 7 | */ 8 | export interface RichText extends ComponentInstance { 9 | } 10 | 11 | export interface RichTextEditor extends SingleEditorComponent { 12 | } 13 | 14 | /** 15 | * Extend to implement [DASH rich text editor](http://datashapes.org/forms.html#RichTextEditor) 16 | */ 17 | export const richText: CoreComponent = { 18 | editor: dash.RichTextEditor, 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/lib/components/textArea.ts: -------------------------------------------------------------------------------- 1 | import { dash } from '@tpluscode/rdf-ns-builders' 2 | import type { ComponentInstance, SingleEditorComponent } from '../../models/components/index.js' 3 | import type { CoreComponent } from '../components.js' 4 | 5 | /** 6 | * Instance state of text area component 7 | */ 8 | export interface TextArea extends ComponentInstance { 9 | } 10 | 11 | export interface TextAreaEditor extends SingleEditorComponent