Repo restructure.

This commit is contained in:
Austin Smith
2025-11-13 09:13:00 -05:00
parent 234fa6e0ef
commit 0d4adef274
11 changed files with 219 additions and 14 deletions

100
package-lock.json generated
View File

@@ -8,6 +8,9 @@
"name": "web-components",
"version": "1.0.0",
"license": "ISC",
"workspaces": [
"packages/*"
],
"devDependencies": {
"@types/jest": "^30.0.0",
"jest": "^30.2.0",
@@ -1530,6 +1533,37 @@
"node": ">= 14"
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -2049,6 +2083,10 @@
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"dev": true
},
"node_modules/declaform": {
"resolved": "packages/declaform",
"link": true
},
"node_modules/dedent": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz",
@@ -2218,12 +2256,32 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
]
},
"node_modules/fb-watchman": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -3326,6 +3384,15 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
"node_modules/json-schema": {
"resolved": "packages/json-schema",
"link": true
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -3820,6 +3887,14 @@
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
@@ -4799,6 +4874,31 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"packages/declaform": {
"version": "1.0.0",
"license": "ISC"
},
"packages/json-schema": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1"
}
},
"src/declaform": {
"version": "1.0.0",
"extraneous": true,
"license": "ISC"
},
"src/json-schema": {
"version": "1.0.0",
"extraneous": true,
"license": "ISC",
"dependencies": {
"ajv-formats": "^3.0.1"
}
}
}
}

View File

@@ -8,6 +8,9 @@
},
"author": "Austin Smith",
"license": "ISC",
"workspaces": [
"packages/*"
],
"devDependencies": {
"@types/jest": "^30.0.0",
"jest": "^30.2.0",

View File

@@ -0,0 +1,14 @@
{
"name": "declaform",
"version": "1.0.0",
"description": "Web component which maps a form to a JSON object",
"main": "index.js",
"directories": {
"test": "tests"
},
"scripts": {
"test": "npx jest"
},
"author": "Austin Smith",
"license": "ISC"
}

View File

@@ -150,6 +150,11 @@ export class Declaform extends HTMLElement {
if (src) {
this.hydrateFromEndpoint(src);
}
this._form.addEventListener('submit', async (e) => {
e.preventDefault();
await this.submit();
})
}
get form() {
@@ -202,6 +207,20 @@ export class Declaform extends HTMLElement {
const obj = await response.json();
this.hydrate(obj);
}
private async submit(): Promise<void> {
const method = this.getAttribute('method') ?? 'POST';
const endpoint = new URL(this.getAttribute('action') ?? '');
const obj = this.toObject();
const response = await fetch(endpoint, {
method,
body: JSON.stringify(obj),
headers: {
'Content-Type': 'application/json',
}
})
}
}
customElements.define('decla-form', Declaform);

View File

@@ -1,4 +1,4 @@
import { Declaform } from '../declaform';
import { Declaform } from '../src';
const MOVIE_FORM_NO_SRC = `
<decla-form id="form">
@@ -32,7 +32,7 @@ const MOVIE_FORM_NO_SRC = `
`;
const MOVIE_FORM_HTTP = `
<decla-form id="form" src="get-movie/jaws">
<decla-form id="form" src="movie/jaws" action="movie/jaws" method="PUT">
<form slot="form">
<div class="input-group">
<label for="name">Name</label>
@@ -108,7 +108,7 @@ describe('Declaform', () => {
});
it('Supplying "src" attribute should hydrate form with the supplied endpoint.', async () => {
global.fetch = jest.fn().mockResolvedValue({
globalThis.fetch = jest.fn().mockResolvedValue({
status: 200,
ok: true,
json: () => Promise.resolve({

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./",
"outDir": "../../dist/declaform",
"types": ["jest"]
},
"include": ["src", "tests"]
}

View File

@@ -0,0 +1,15 @@
{
"name": "json-schema",
"version": "1.0.0",
"description": "Web component which validates a form against a JSON Schema.",
"main": "index.js",
"scripts": {
"test": "npx jest"
},
"author": "Austin Smith",
"license": "ISC",
"dependencies": {
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1"
}
}

View File

@@ -0,0 +1,46 @@
/**
* Validates a form against a JSON Schema validated by Ajv.
*
* Emits CustomEvents:
* - validationSuccess
* - validationFailure
*/
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv();
addFormats(ajv);
export class JsonSchema extends HTMLElement {
static observedAttributes = [
'src', // url of remote schema
'for', // id of form the schema is applied to`
]
private form: HTMLFormElement | null;
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot!.innerHTML = '<div><slot name="schema"></slot></div>';
this.form = null;
}
async connectedCallback() {
const for_attr = this.getAttribute('for');
if (!for_attr) {
throw new Error('[Json-Schema] Error: "for" attribute must be defined');
}
this.form = document.getElementById(for_attr) as HTMLFormElement;
this.form.addEventListener('submit', () => {
});
}
validate() {
}
}
customElements.define('json-schema', JsonSchema);

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "../../dist/json-schema"
},
"include": ["src"]
}

View File

@@ -1,20 +1,11 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
"rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"esModuleInterop": true,
"module": "esnext",
"target": "es6",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
@@ -24,6 +15,7 @@
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"moduleResolution": "node",
// Style Options
// "noImplicitReturns": true,
@@ -41,6 +33,5 @@
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
},
"exclude": ["./src/**/*.test.ts"]
}
}