From 0d4adef27469a7f77313aecea911a193a31f8ee5 Mon Sep 17 00:00:00 2001 From: Austin Smith Date: Thu, 13 Nov 2025 09:13:00 -0500 Subject: [PATCH] Repo restructure. --- package-lock.json | 100 ++++++++++++++++++ package.json | 3 + {src => packages}/declaform/README.md | 0 packages/declaform/package.json | 14 +++ .../declaform/src/index.ts | 19 ++++ .../declaform/tests/declaform.test.ts | 6 +- packages/declaform/tsconfig.json | 9 ++ packages/json-schema/package.json | 15 +++ packages/json-schema/src/index.ts | 46 ++++++++ packages/json-schema/tsconfig.json | 8 ++ tsconfig.json | 13 +-- 11 files changed, 219 insertions(+), 14 deletions(-) rename {src => packages}/declaform/README.md (100%) create mode 100644 packages/declaform/package.json rename src/declaform/declaform.ts => packages/declaform/src/index.ts (92%) rename {src => packages}/declaform/tests/declaform.test.ts (97%) create mode 100644 packages/declaform/tsconfig.json create mode 100644 packages/json-schema/package.json create mode 100644 packages/json-schema/src/index.ts create mode 100644 packages/json-schema/tsconfig.json diff --git a/package-lock.json b/package-lock.json index b8adb68..14264e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" + } } } } diff --git a/package.json b/package.json index 4ed7838..69ebff8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,9 @@ }, "author": "Austin Smith", "license": "ISC", + "workspaces": [ + "packages/*" + ], "devDependencies": { "@types/jest": "^30.0.0", "jest": "^30.2.0", diff --git a/src/declaform/README.md b/packages/declaform/README.md similarity index 100% rename from src/declaform/README.md rename to packages/declaform/README.md diff --git a/packages/declaform/package.json b/packages/declaform/package.json new file mode 100644 index 0000000..c4199f5 --- /dev/null +++ b/packages/declaform/package.json @@ -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" +} diff --git a/src/declaform/declaform.ts b/packages/declaform/src/index.ts similarity index 92% rename from src/declaform/declaform.ts rename to packages/declaform/src/index.ts index 50abff8..d7daae8 100644 --- a/src/declaform/declaform.ts +++ b/packages/declaform/src/index.ts @@ -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 { + 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); diff --git a/src/declaform/tests/declaform.test.ts b/packages/declaform/tests/declaform.test.ts similarity index 97% rename from src/declaform/tests/declaform.test.ts rename to packages/declaform/tests/declaform.test.ts index 830327f..20db120 100644 --- a/src/declaform/tests/declaform.test.ts +++ b/packages/declaform/tests/declaform.test.ts @@ -1,4 +1,4 @@ -import { Declaform } from '../declaform'; +import { Declaform } from '../src'; const MOVIE_FORM_NO_SRC = ` @@ -32,7 +32,7 @@ const MOVIE_FORM_NO_SRC = ` `; const MOVIE_FORM_HTTP = ` - +
@@ -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({ diff --git a/packages/declaform/tsconfig.json b/packages/declaform/tsconfig.json new file mode 100644 index 0000000..fed077b --- /dev/null +++ b/packages/declaform/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./", + "outDir": "../../dist/declaform", + "types": ["jest"] + }, + "include": ["src", "tests"] +} \ No newline at end of file diff --git a/packages/json-schema/package.json b/packages/json-schema/package.json new file mode 100644 index 0000000..1099c71 --- /dev/null +++ b/packages/json-schema/package.json @@ -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" + } +} diff --git a/packages/json-schema/src/index.ts b/packages/json-schema/src/index.ts new file mode 100644 index 0000000..3a0dc6e --- /dev/null +++ b/packages/json-schema/src/index.ts @@ -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 = '
'; + 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); \ No newline at end of file diff --git a/packages/json-schema/tsconfig.json b/packages/json-schema/tsconfig.json new file mode 100644 index 0000000..5e426b4 --- /dev/null +++ b/packages/json-schema/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "../../dist/json-schema" + }, + "include": ["src"] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 11a9b67..e33540c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] + } }