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", "name": "web-components",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"workspaces": [
"packages/*"
],
"devDependencies": { "devDependencies": {
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"jest": "^30.2.0", "jest": "^30.2.0",
@@ -1530,6 +1533,37 @@
"node": ">= 14" "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": { "node_modules/ansi-escapes": {
"version": "4.3.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -2049,6 +2083,10 @@
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"dev": true "dev": true
}, },
"node_modules/declaform": {
"resolved": "packages/declaform",
"link": true
},
"node_modules/dedent": { "node_modules/dedent": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", "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": "^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": { "node_modules/fast-json-stable-stringify": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true "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": { "node_modules/fb-watchman": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -3326,6 +3384,15 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true "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": { "node_modules/json5": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -3820,6 +3887,14 @@
"node": ">=0.10.0" "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": { "node_modules/resolve-cwd": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
@@ -4799,6 +4874,31 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "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", "author": "Austin Smith",
"license": "ISC", "license": "ISC",
"workspaces": [
"packages/*"
],
"devDependencies": { "devDependencies": {
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"jest": "^30.2.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) { if (src) {
this.hydrateFromEndpoint(src); this.hydrateFromEndpoint(src);
} }
this._form.addEventListener('submit', async (e) => {
e.preventDefault();
await this.submit();
})
} }
get form() { get form() {
@@ -202,6 +207,20 @@ export class Declaform extends HTMLElement {
const obj = await response.json(); const obj = await response.json();
this.hydrate(obj); 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); customElements.define('decla-form', Declaform);

View File

@@ -1,4 +1,4 @@
import { Declaform } from '../declaform'; import { Declaform } from '../src';
const MOVIE_FORM_NO_SRC = ` const MOVIE_FORM_NO_SRC = `
<decla-form id="form"> <decla-form id="form">
@@ -32,7 +32,7 @@ const MOVIE_FORM_NO_SRC = `
`; `;
const MOVIE_FORM_HTTP = ` 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"> <form slot="form">
<div class="input-group"> <div class="input-group">
<label for="name">Name</label> <label for="name">Name</label>
@@ -108,7 +108,7 @@ describe('Declaform', () => {
}); });
it('Supplying "src" attribute should hydrate form with the supplied endpoint.', async () => { it('Supplying "src" attribute should hydrate form with the supplied endpoint.', async () => {
global.fetch = jest.fn().mockResolvedValue({ globalThis.fetch = jest.fn().mockResolvedValue({
status: 200, status: 200,
ok: true, ok: true,
json: () => Promise.resolve({ 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": { "compilerOptions": {
// File Layout
"rootDir": "./src",
"outDir": "./dist",
// Environment Settings // Environment Settings
// See also https://aka.ms/tsconfig/module // See also https://aka.ms/tsconfig/module
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"target": "es6", "target": "es6",
"types": [], "types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs // Other Outputs
"sourceMap": true, "sourceMap": true,
@@ -24,6 +15,7 @@
// Stricter Typechecking Options // Stricter Typechecking Options
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true, "exactOptionalPropertyTypes": true,
"moduleResolution": "node",
// Style Options // Style Options
// "noImplicitReturns": true, // "noImplicitReturns": true,
@@ -41,6 +33,5 @@
"noUncheckedSideEffectImports": true, "noUncheckedSideEffectImports": true,
"moduleDetection": "force", "moduleDetection": "force",
"skipLibCheck": true, "skipLibCheck": true,
}, }
"exclude": ["./src/**/*.test.ts"]
} }