switch to typescript

This commit is contained in:
2025-12-28 18:08:57 -08:00
parent a032019f33
commit 98c2dac434
11 changed files with 230 additions and 186 deletions
-169
View File
@@ -1,169 +0,0 @@
/** @module FormDataDeep */
/**
* Usage: `Array<HTMLElement>.filter(removeChildren)`
*
* Removes elements from the array which are children of other elements in the array.
*/
export function removeChildren(element,index,array){
for(const sibling of array){
if(!sibling.isSameNode(element) && sibling.contains(element)){
return false
}
}
return true
}
/**
* Appends a new value onto an existing key inside an object, or adds the key if it does not already exist.
* @param {Object} object
* @param {String} name
* @param {*} value
*/
export function append(object,name,value){
if(name in object){
if(object[name] instanceof Array){
object[name].push(value)
}else{
object[name]=[object[name],value]
}
}else{
object[name]=value
}
}
/**
* Get `form` data as an object, including nested forms.
* @param {HTMLFormElement} form
* @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
*/
export function getFormDataDeep(form,primitives=false){
const formDataDeep={}
const formData=new FormData(form)
for(const name of formData.keys()){
const value=formData.getAll(name)
if(value.length==0){
continue
}
if(primitives){
for(let i=0;i<value.length;i++){
if(!isNaN(+value[i])){
value[i]=+value[i]
}
if(value[i]=='on'){
value[i]=true
}
}
}
if(value.length==1){
formDataDeep[name]=value[0]
}else{
formDataDeep[name]=value
}
}
for(const child of [...form.querySelectorAll('form')].filter(removeChildren)){
const value=getFormDataDeep(child,primitives)
if(Object.keys(value)==0){
continue
}
const name=child.name
append(formDataDeep,name,value)
}
return formDataDeep
}
/**
* Convenience class to have a similar interface to FormData
*/
export class FormDataDeep extends FormData {
/**
* Get `form` data as an object, including nested forms.
* @param {HTMLFormElement | Object} form the root form element, or a JSON Object containing form data.
* @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
*/
constructor(form,primitives=false){
super()
if(form instanceof HTMLFormElement){
Object.assign(this,getFormDataDeep(form,primitives))
}else{
Object.assign(this,form)
}
}
append(name,value,filename=undefined){
if(filename!==undefined){
value=new File([value],filename)
}
append(this,name,value)
}
delete(name){
delete this[name]
}
entries(){
return Object.entries(this).filter((value)=>!value instanceof Function)
}
get(name){
if(this[name] instanceof Array){
return this[name].at(-1)
}else{
return this[name]
}
}
getAll(name){
if(this[name] instanceof Array){
return this[name]
}else{
return [this[name]]
}
}
has(name){
return name in this
}
keys(){
return this.entries().keys()
}
set(name,value,filename=undefined){
if(filename!==undefined){
value=new File([value],filename)
}
this[name]=value
}
values(){
return this.entries().values()
}
/**
* Applies the values in this object to the target form.
* @param {HTMLFormElement} form
*/
apply(form){
for(const element of form.elements){
const name=element.name
if(! name in this){
continue
}
let value
if(this[name] instanceof Array){
value=this[name].shift()
}else{
value=this[name]
delete this[name]
}
switch(element.type){
case 'radio':
case 'checkbox':{
element.checked=true
break
}
default:{
element.value=value
break
}
}
}
for(const child of [...form.querySelectorAll('form')].filter(removeChildren)){
const name=child.name
if(! name in this){
continue
}
new FormDataDeep(this[name]).apply(form)
}
}
}
export default FormDataDeep
+18 -11
View File
@@ -1,15 +1,15 @@
{ {
"name": "js-util", "name": "@sugoidogo/js-util",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "js-util", "name": "@sugoidogo/js-util",
"version": "0.1.0", "version": "0.1.0",
"devDependencies": { "devDependencies": {
"@types/web": "^0.0.301", "jsdoc": "^4.0.5",
"jsdoc": "^4.0.5" "typescript": "^5.9.3"
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
@@ -101,13 +101,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/web": {
"version": "0.0.301",
"resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.301.tgz",
"integrity": "sha512-GAoFovImstIY39EAwGazoeal9+uy+zZz0vLhz/MDbqaHAbK0d8yzF8EKtlSkwNzZu9WEIyYdfSGwVTUSnJMtpw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -328,6 +321,20 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uc.micro": { "node_modules/uc.micro": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+9 -3
View File
@@ -2,10 +2,16 @@
"name": "@sugoidogo/js-util", "name": "@sugoidogo/js-util",
"version": "0.1.0", "version": "0.1.0",
"main": "dist/index.js", "main": "dist/index.js",
"files": ["dist/"], "scripts": {
"prepack": "tsc"
},
"files": [
"dist/",
"src/"
],
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@types/web": "^0.0.301", "jsdoc": "^4.0.5",
"jsdoc": "^4.0.5" "typescript": "^5.9.3"
} }
} }
+1 -1
View File
@@ -77,7 +77,7 @@ export async function loadAsset(dataURL,id=undefined,unsafe=false){
} }
default:{ default:{
if(unsafe){ if(unsafe){
window[id]=await fetch(dataURL) Object.defineProperty(window,id,await fetch(dataURL))
} }
return await fetch(dataURL) return await fetch(dataURL)
} }
+4 -2
View File
@@ -3,6 +3,8 @@
* @module CSSRandom * @module CSSRandom
*/ */
(function CSSRandom() { (function CSSRandom() {
document.documentElement.style.setProperty('--random', Math.random()) document.documentElement.style.setProperty('--random', Math.random().toString())
requestAnimationFrame(CSSRandom) requestAnimationFrame(CSSRandom)
})() })()
export {}
+177
View File
@@ -0,0 +1,177 @@
/** @module FormDataDeep */
/**
* Usage: `Array<HTMLElement>.filter(removeChildren)`
*
* Removes elements from the array which are children of other elements in the array.
*/
export function removeChildren(element, index, array) {
for (const sibling of array) {
if (!sibling.isSameNode(element) && sibling.contains(element)) {
return false
}
}
return true
}
/**
* Appends a new value onto an existing key inside an object, or adds the key if it does not already exist.
* @param {Object} object
* @param {String} name
* @param {*} value
*/
export function append(object, name, value) {
if (name in object) {
if (object[name] instanceof Array) {
object[name].push(value)
} else {
object[name] = [object[name], value]
}
} else {
object[name] = value
}
}
/**
* Get `form` data as an object, including nested forms.
* @param {HTMLFormElement} form
* @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
*/
export function getFormDataDeep(form, primitives = false) {
const formDataDeep = {}
const formData = new FormData(form)
for (const name of formData.keys()) {
const value: any = formData.getAll(name)
if (value.length == 0) {
continue
}
if (primitives) {
for (let i = 0; i < value.length; i++) {
if (!isNaN(+value[i])) {
value[i] = +value[i]
}
if (value[i] == 'on') {
value[i] = true
}
}
}
if (value.length == 1) {
formDataDeep[name] = value[0]
} else {
formDataDeep[name] = value
}
}
for (const child of [...form.querySelectorAll('form')].filter(removeChildren)) {
const value = getFormDataDeep(child, primitives)
if (Object.keys(value).length === 0) {
continue
}
const name = child.name
append(formDataDeep, name, value)
}
return formDataDeep
}
/**
* Convenience class to have a similar interface to FormData
*/
export class FormDataDeep implements FormData {
#formdatadeep:{[key:string]: any} = {}
/**
* Get `form` data as an object, including nested forms.
* @param {HTMLFormElement | Object} form the root form element, or a JSON Object containing form data.
* @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
*/
constructor(form, primitives = false) {
if (form instanceof HTMLFormElement) {
Object.assign(this.#formdatadeep, getFormDataDeep(form, primitives))
} else {
Object.assign(this.#formdatadeep, form)
}
}
forEach(callbackfn: (value: FormDataEntryValue, key: string, parent: FormData) => void, thisArg: any=this): void {
for (const [key,value] of thisArg.entries()) {
callbackfn(value,key,thisArg)
}
}
[Symbol.iterator](): FormDataIterator<[string, FormDataEntryValue]> {
return this.entries()
}
append(name, value, filename = undefined) {
if (filename !== undefined) {
value = new File([value], filename)
}
append(this.#formdatadeep, name, value)
}
delete(name) {
delete this.#formdatadeep[name]
}
entries() {
return Object.entries(this.#formdatadeep)[Symbol.iterator]()
}
get(name) {
if (this.#formdatadeep[name] instanceof Array) {
return this.#formdatadeep[name].at(-1)
} else {
return this.#formdatadeep[name]
}
}
getAll(name) {
if (this.#formdatadeep[name] instanceof Array) {
return this.#formdatadeep[name]
} else {
return [this.#formdatadeep[name]]
}
}
has(name) {
return name in this.#formdatadeep
}
keys() {
return Object.keys(this.#formdatadeep)[Symbol.iterator]()
}
set(name, value, filename = undefined) {
if (filename !== undefined) {
value = new File([value], filename)
}
this.#formdatadeep[name] = value
}
values() {
return Object.values(this.#formdatadeep)[Symbol.iterator]()
}
/**
* Applies the values in this object to the target form.
* @param {HTMLFormElement} form
*/
apply(form) {
for (const element of form.elements) {
const name = element.name
if (!(name in this.#formdatadeep)) {
continue
}
let value
if (this.#formdatadeep[name] instanceof Array) {
value = this.#formdatadeep[name].shift()
} else {
value = this.#formdatadeep[name]
delete this.#formdatadeep[name]
}
switch (element.type) {
case 'radio':
case 'checkbox': {
element.checked = true
break
}
default: {
element.value = value
break
}
}
}
for (const child of [...form.querySelectorAll('form')].filter(removeChildren)) {
const name = child.name
if (!(name in this.#formdatadeep)) {
continue
}
new FormDataDeep(this.#formdatadeep[name]).apply(form)
}
}
}
export default FormDataDeep
+8
View File
@@ -0,0 +1,8 @@
export async function forAsync<T, U>(iterable: Iterable<T> | AsyncIterable<T>, callback: (value: T) => Promise<U> | U): Promise<Array<U>> {
const promises: Array<Promise<U> | U> = []
for await (const iterableValue of iterable) {
promises.push(callback(iterableValue))
}
return Promise.all(promises)
}
+1
View File
@@ -3,3 +3,4 @@ export * as CSSRandom from './CSSRandom'
export { FileReaderAsync } from './FileReaderAsync' export { FileReaderAsync } from './FileReaderAsync'
export { fontScale } from './fontScale' export { fontScale } from './fontScale'
export { FormDataDeep, getFormDataDeep } from './FormDataDeep' export { FormDataDeep, getFormDataDeep } from './FormDataDeep'
export { forAsync } from './forAsync'
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es2022",
"module": "es2022",
"moduleResolution": "node",
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"outDir": "dist"
},
"include": ["src/index.ts"]
}