diff --git a/package-lock.json b/package-lock.json index 7607f4e94..06083b078 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "semver": "^7.3.5", "turbo": "^1.11.3", "typescript": "^5.0.4", - "vitest": "^2.0.5" + "vitest": "^2.1.4" }, "engines": { "node": ">=18.0.0" @@ -723,6 +723,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -2541,208 +2542,252 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.3.tgz", + "integrity": "sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.3.tgz", + "integrity": "sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.3.tgz", + "integrity": "sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.3.tgz", + "integrity": "sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.3.tgz", + "integrity": "sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.3.tgz", + "integrity": "sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.3.tgz", + "integrity": "sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.3.tgz", + "integrity": "sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.3.tgz", + "integrity": "sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.3.tgz", + "integrity": "sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.3.tgz", + "integrity": "sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.3.tgz", + "integrity": "sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.3.tgz", + "integrity": "sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.3.tgz", + "integrity": "sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.3.tgz", + "integrity": "sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.3.tgz", + "integrity": "sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.3.tgz", + "integrity": "sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", - "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.3.tgz", + "integrity": "sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -2878,9 +2923,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, @@ -3260,15 +3305,15 @@ "license": "ISC" }, "node_modules/@vitest/expect": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", - "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", + "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -3276,46 +3321,70 @@ } }, "node_modules/@vitest/expect/node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/expect/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/@vitest/expect/node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/mocker": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz", + "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "@vitest/spy": "2.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@vitest/expect/node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { - "get-func-name": "^2.0.1" + "@types/estree": "^1.0.0" } }, "node_modules/@vitest/pretty-format": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", - "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz", + "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==", "dev": true, + "license": "MIT", "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -3324,13 +3393,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", - "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz", + "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.0.5", + "@vitest/utils": "2.1.4", "pathe": "^1.1.2" }, "funding": { @@ -3338,50 +3407,36 @@ } }, "node_modules/@vitest/runner/node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/@vitest/runner/node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/@vitest/snapshot": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", - "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz", + "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "magic-string": "^0.30.10", + "@vitest/pretty-format": "2.1.4", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -3389,13 +3444,13 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", - "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" @@ -4297,9 +4352,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -4314,13 +4369,11 @@ } }, "node_modules/chai/node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/chalk": { "version": "4.1.2", @@ -4354,9 +4407,6 @@ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, "engines": { "node": ">= 16" } @@ -4618,13 +4668,13 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5713,6 +5763,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -8463,9 +8523,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "license": "MIT", "dependencies": { @@ -8655,9 +8715,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, @@ -9126,9 +9186,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { @@ -9233,9 +9293,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -9254,8 +9314,8 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9686,11 +9746,14 @@ } }, "node_modules/rollup": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", - "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", + "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -9699,22 +9762,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.20.0", - "@rollup/rollup-android-arm64": "4.20.0", - "@rollup/rollup-darwin-arm64": "4.20.0", - "@rollup/rollup-darwin-x64": "4.20.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", - "@rollup/rollup-linux-arm-musleabihf": "4.20.0", - "@rollup/rollup-linux-arm64-gnu": "4.20.0", - "@rollup/rollup-linux-arm64-musl": "4.20.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", - "@rollup/rollup-linux-riscv64-gnu": "4.20.0", - "@rollup/rollup-linux-s390x-gnu": "4.20.0", - "@rollup/rollup-linux-x64-gnu": "4.20.0", - "@rollup/rollup-linux-x64-musl": "4.20.0", - "@rollup/rollup-win32-arm64-msvc": "4.20.0", - "@rollup/rollup-win32-ia32-msvc": "4.20.0", - "@rollup/rollup-win32-x64-msvc": "4.20.0", + "@rollup/rollup-android-arm-eabi": "4.24.3", + "@rollup/rollup-android-arm64": "4.24.3", + "@rollup/rollup-darwin-arm64": "4.24.3", + "@rollup/rollup-darwin-x64": "4.24.3", + "@rollup/rollup-freebsd-arm64": "4.24.3", + "@rollup/rollup-freebsd-x64": "4.24.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.3", + "@rollup/rollup-linux-arm-musleabihf": "4.24.3", + "@rollup/rollup-linux-arm64-gnu": "4.24.3", + "@rollup/rollup-linux-arm64-musl": "4.24.3", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.3", + "@rollup/rollup-linux-riscv64-gnu": "4.24.3", + "@rollup/rollup-linux-s390x-gnu": "4.24.3", + "@rollup/rollup-linux-x64-gnu": "4.24.3", + "@rollup/rollup-linux-x64-musl": "4.24.3", + "@rollup/rollup-win32-arm64-msvc": "4.24.3", + "@rollup/rollup-win32-ia32-msvc": "4.24.3", + "@rollup/rollup-win32-x64-msvc": "4.24.3", "fsevents": "~2.3.2" } }, @@ -9892,13 +9957,6 @@ "dev": true, "license": "MIT" }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -10061,9 +10119,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -10423,11 +10481,19 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/tinypool": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", - "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -10443,9 +10509,9 @@ } }, "node_modules/tinyspy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", - "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { @@ -11035,14 +11101,14 @@ } }, "node_modules/vite": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", - "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.41", + "postcss": "^8.4.43", "rollup": "^4.20.0" }, "bin": { @@ -11095,16 +11161,15 @@ } }, "node_modules/vite-node": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", - "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz", + "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.5", + "debug": "^4.3.7", "pathe": "^1.1.2", - "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -11118,30 +11183,31 @@ } }, "node_modules/vitest": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", - "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.5", - "@vitest/pretty-format": "^2.0.5", - "@vitest/runner": "2.0.5", - "@vitest/snapshot": "2.0.5", - "@vitest/spy": "2.0.5", - "@vitest/utils": "2.0.5", - "chai": "^5.1.1", - "debug": "^4.3.5", - "execa": "^8.0.1", - "magic-string": "^0.30.10", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz", + "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.4", + "@vitest/mocker": "2.1.4", + "@vitest/pretty-format": "^2.1.4", + "@vitest/runner": "2.1.4", + "@vitest/snapshot": "2.1.4", + "@vitest/spy": "2.1.4", + "@vitest/utils": "2.1.4", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.7.0", - "tinybench": "^2.8.0", - "tinypool": "^1.0.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.5", + "vite-node": "2.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -11156,8 +11222,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.5", - "@vitest/ui": "2.0.5", + "@vitest/browser": "2.1.4", + "@vitest/ui": "2.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -11183,184 +11249,26 @@ } }, "node_modules/vitest/node_modules/@vitest/utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", - "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz", + "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.0.5", - "estree-walker": "^3.0.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.4", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/vitest/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/vitest/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/vitest/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, - "node_modules/vitest/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/vitest/node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/vitest/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/vitest/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/vitest/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/vscode-json-languageservice": { "version": "4.2.1", diff --git a/package.json b/package.json index ce48cc40d..23f280e11 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "semver": "^7.3.5", "turbo": "^1.11.3", "typescript": "^5.0.4", - "vitest": "^2.0.5" + "vitest": "^2.1.4" }, "happyLintChanged": { "rules": [ diff --git a/packages/happy-dom/package.json b/packages/happy-dom/package.json index 7cc1a131d..2cf5b7d86 100644 --- a/packages/happy-dom/package.json +++ b/packages/happy-dom/package.json @@ -74,7 +74,7 @@ "test": "vitest run", "test:ui": "vitest --ui", "test:watch": "vitest", - "test:debug": "vitest run --inspect-brk --threads=false" + "test:debug": "vitest run --inspect-brk --no-file-parallelism" }, "dependencies": { "entities": "^4.5.0", diff --git a/packages/happy-dom/src/ClassMethodBinder.ts b/packages/happy-dom/src/ClassMethodBinder.ts index 8bbc3ac40..32f636333 100644 --- a/packages/happy-dom/src/ClassMethodBinder.ts +++ b/packages/happy-dom/src/ClassMethodBinder.ts @@ -2,79 +2,65 @@ * Node utility. */ export default class ClassMethodBinder { + private target: Object; + private classes: any[]; + private cache = new Map(); + /** - * Binds methods to a target. + * Constructor. * * @param target Target. * @param classes Classes. - * @param [options] Options. - * @param [options.bindSymbols] Bind symbol methods. - * @param [options.forwardToPrototype] Forwards the method calls to the prototype. This makes it possible for test tools to override methods on the prototype (e.g. Object.defineProperty(HTMLCollection.prototype, 'item', {})). - * @param [options.proxy] Bind methods using a proxy. */ - public static bindMethods( - target: Object, - classes: any[], - options?: { bindSymbols?: boolean; forwardToPrototype?: boolean; proxy?: any } - ): void { - for (const _class of classes) { - const propertyDescriptors = Object.getOwnPropertyDescriptors(_class.prototype); - const keys: Array = Object.keys(propertyDescriptors); + constructor(target: Object, classes: any[]) { + this.target = target; + this.classes = classes; + } - if (options?.bindSymbols) { - for (const symbol of Object.getOwnPropertySymbols(propertyDescriptors)) { - keys.push(symbol); - } - } + /** + * Binds method, getters and setters to a target. + * + * @param name Method name. + */ + public bind(name: string | symbol): void { + if (this.cache.has(name)) { + return; + } - const scope = options?.proxy ? options.proxy : target; + this.cache.set(name, true); - if (options?.forwardToPrototype) { - for (const key of keys) { - const descriptor = propertyDescriptors[key]; - if (descriptor.get || descriptor.set) { - Object.defineProperty(target, key, { - ...descriptor, - get: - descriptor.get && - (() => Object.getOwnPropertyDescriptor(_class.prototype, key).get.call(scope)), - set: - descriptor.set && - ((newValue) => - Object.getOwnPropertyDescriptor(_class.prototype, key).set.call(scope, newValue)) - }); - } else if ( - key !== 'constructor' && - typeof descriptor.value === 'function' && - !descriptor.value.toString().startsWith('class ') - ) { - Object.defineProperty(target, key, { - ...descriptor, - value: (...args) => _class.prototype[key].apply(scope, args) - }); - } - } - } else { - for (const key of keys) { - const descriptor = propertyDescriptors[key]; - if (descriptor.get || descriptor.set) { - Object.defineProperty(target, key, { - ...descriptor, - get: descriptor.get?.bind(scope), - set: descriptor.set?.bind(scope) - }); - } else if ( - key !== 'constructor' && - typeof descriptor.value === 'function' && - !descriptor.value.toString().startsWith('class ') - ) { - Object.defineProperty(target, key, { - ...descriptor, - value: descriptor.value.bind(scope) - }); - } + const target = this.target; + + if (!(name in target)) { + return; + } + + for (const _class of this.classes) { + const descriptor = Object.getOwnPropertyDescriptor(_class.prototype, name); + if (descriptor) { + if (typeof descriptor.value === 'function') { + Object.defineProperty(target, name, { + ...descriptor, + value: descriptor.value.bind(target) + }); + } else if (descriptor.get !== undefined) { + Object.defineProperty(target, name, { + ...descriptor, + get: descriptor.get?.bind(target), + set: descriptor.set?.bind(target) + }); } + return; } } } + + /** + * Prevents a method, getter or setter from being bound. + * + * @param name Method name. + */ + public preventBinding(name: string | symbol): void { + this.cache.set(name, true); + } } diff --git a/packages/happy-dom/src/PropertySymbol.ts b/packages/happy-dom/src/PropertySymbol.ts index f9a91eee5..bb5b5e214 100644 --- a/packages/happy-dom/src/PropertySymbol.ts +++ b/packages/happy-dom/src/PropertySymbol.ts @@ -141,7 +141,7 @@ export const mode = Symbol('mode'); export const host = Symbol('host'); export const setURL = Symbol('setURL'); export const localName = Symbol('localName'); -export const registedClass = Symbol('registedClass'); +export const classRegistry = Symbol('classRegistry'); export const nodeStream = Symbol('nodeStream'); export const location = Symbol('location'); export const history = Symbol('history'); @@ -232,3 +232,146 @@ export const destroyed = Symbol('destroyed'); export const aborted = Symbol('aborted'); export const browserFrames = Symbol('browserFrames'); export const windowInternalId = Symbol('windowInternalId'); +export const getItemList = Symbol('getItemList'); +export const requiredExtensions = Symbol('requiredExtensions'); +export const systemLanguage = Symbol('systemLanguage'); +export const transform = Symbol('transform'); +export const baseVal = Symbol('baseVal'); +export const animVal = Symbol('animVal'); +export const pathLength = Symbol('pathLength'); +export const unitType = Symbol('unitType'); +export const viewBox = Symbol('viewBox'); +export const markerUnits = Symbol('markerUnits'); +export const markerWidth = Symbol('markerWidth'); +export const markerHeight = Symbol('markerHeight'); +export const values = Symbol('values'); +export const orientType = Symbol('orientType'); +export const orientAngle = Symbol('orientAngle'); +export const refX = Symbol('refX'); +export const refY = Symbol('refY'); +export const readOnly = Symbol('readOnly'); +export const preserveAspectRatio = Symbol('preserveAspectRatio'); +export const animatedPoints = Symbol('animatedPoints'); +export const points = Symbol('points'); +export const rx = Symbol('rx'); +export const ry = Symbol('ry'); +export const cx = Symbol('cx'); +export const cy = Symbol('cy'); +export const r = Symbol('r'); +export const clipPathUnits = Symbol('clipPathUnits'); +export const maskUnits = Symbol('maskUnits'); +export const maskContentUnits = Symbol('maskContentUnits'); +export const filterUnits = Symbol('filterUnits'); +export const primitiveUnits = Symbol('primitiveUnits'); +export const href = Symbol('href'); +export const x1 = Symbol('x1'); +export const y1 = Symbol('y1'); +export const x2 = Symbol('x2'); +export const y2 = Symbol('y2'); +export const gradientUnits = Symbol('gradientUnits'); +export const gradientTransform = Symbol('gradientTransform'); +export const spreadMethod = Symbol('spreadMethod'); +export const patternUnits = Symbol('patternUnits'); +export const patternContentUnits = Symbol('patternContentUnits'); +export const patternTransform = Symbol('patternTransform'); +export const fx = Symbol('fx'); +export const fy = Symbol('fy'); +export const offset = Symbol('offset'); +export const disabled = Symbol('disabled'); +export const textLength = Symbol('textLength'); +export const lengthAdjust = Symbol('lengthAdjust'); +export const getAttribute = Symbol('getAttribute'); +export const setAttribute = Symbol('setAttribute'); +export const z = Symbol('z'); +export const w = Symbol('w'); +export const toArray = Symbol('toArray'); +export const fromString = Symbol('fromString'); +export const fromArray = Symbol('fromArray'); +export const angle = Symbol('angle'); +export const m11 = Symbol('m11'); +export const m12 = Symbol('m12'); +export const m13 = Symbol('m13'); +export const m14 = Symbol('m14'); +export const m21 = Symbol('m21'); +export const m22 = Symbol('m22'); +export const m23 = Symbol('m23'); +export const m24 = Symbol('m24'); +export const m31 = Symbol('m31'); +export const m32 = Symbol('m32'); +export const m33 = Symbol('m33'); +export const m34 = Symbol('m34'); +export const m41 = Symbol('m41'); +export const m42 = Symbol('m42'); +export const m43 = Symbol('m43'); +export const m44 = Symbol('m44'); +export const setMatrixValue = Symbol('setMatrixValue'); +export const translateSelf = Symbol('translateSelf'); +export const rotateSelf = Symbol('rotateSelf'); +export const rotateAxisAngleSelf = Symbol('rotateAxisAngleSelf'); +export const scaleSelf = Symbol('scaleSelf'); +export const scale3dSelf = Symbol('scale3dSelf'); +export const scaleNonUniformSelf = Symbol('scaleNonUniformSelf'); +export const skewXSelf = Symbol('skewXSelf'); +export const skewYSelf = Symbol('skewYSelf'); +export const multiplySelf = Symbol('multiplySelf'); +export const matrix = Symbol('matrix'); +export const domMatrix = Symbol('domMatrix'); +export const getDOMMatrix = Symbol('getDOMMatrix'); +export const setDOMMatrix = Symbol('setDOMMatrix'); +export const attributeValue = Symbol('attributeValue'); +export const startOffset = Symbol('startOffset'); +export const method = Symbol('method'); +export const spacing = Symbol('spacing'); +export const in1 = Symbol('in1'); +export const in2 = Symbol('in2'); +export const result = Symbol('result'); +export const bias = Symbol('bias'); +export const divisor = Symbol('divisor'); +export const edgeMode = Symbol('edgeMode'); +export const kernelMatrix = Symbol('kernelMatrix'); +export const kernelUnitLengthX = Symbol('kernelUnitLengthX'); +export const kernelUnitLengthY = Symbol('kernelUnitLengthY'); +export const orderX = Symbol('orderX'); +export const orderY = Symbol('orderY'); +export const preserveAlpha = Symbol('preserveAlpha'); +export const targetX = Symbol('targetX'); +export const targetY = Symbol('targetY'); +export const diffuseConstant = Symbol('diffuseConstant'); +export const surfaceScale = Symbol('surfaceScale'); +export const scale = Symbol('scale'); +export const xChannelSelector = Symbol('xChannelSelector'); +export const yChannelSelector = Symbol('yChannelSelector'); +export const azimuth = Symbol('azimuth'); +export const elevation = Symbol('elevation'); +export const dx = Symbol('dx'); +export const dy = Symbol('dy'); +export const stdDeviationX = Symbol('stdDeviationX'); +export const stdDeviationY = Symbol('stdDeviationY'); +export const tableValues = Symbol('tableValues'); +export const slope = Symbol('slope'); +export const intercept = Symbol('intercept'); +export const amplitude = Symbol('amplitude'); +export const exponent = Symbol('exponent'); +export const crossOrigin = Symbol('crossOrigin'); +export const operator = Symbol('operator'); +export const radiusX = Symbol('radiusX'); +export const radiusY = Symbol('radiusY'); +export const specularConstant = Symbol('specularConstant'); +export const specularExponent = Symbol('specularExponent'); +export const pointsAtX = Symbol('pointsAtX'); +export const pointsAtY = Symbol('pointsAtY'); +export const pointsAtZ = Symbol('pointsAtZ'); +export const limitingConeAngle = Symbol('limitingConeAngle'); +export const baseFrequencyX = Symbol('baseFrequencyX'); +export const baseFrequencyY = Symbol('baseFrequencyY'); +export const numOctaves = Symbol('numOctaves'); +export const seed = Symbol('seed'); +export const stitchTiles = Symbol('stitchTiles'); +export const rotateFromVectorSelf = Symbol('rotateFromVectorSelf'); +export const flipXSelf = Symbol('flipXSelf'); +export const flipYSelf = Symbol('flipYSelf'); +export const invertSelf = Symbol('invertSelf'); +export const getLength = Symbol('getLength'); +export const currentScale = Symbol('currentScale'); +export const rotate = Symbol('rotate'); +export const bindMethods = Symbol('bindMethods'); diff --git a/packages/happy-dom/src/StringUtility.ts b/packages/happy-dom/src/StringUtility.ts new file mode 100644 index 000000000..f15e44777 --- /dev/null +++ b/packages/happy-dom/src/StringUtility.ts @@ -0,0 +1,57 @@ +const ASCII_LOWER_CASE_CACHE: Map = new Map(); +const ASCII_UPPER_CASE_CACHE: Map = new Map(); + +/** + * String utility. + */ +export default class StringUtility { + /** + * ASCII lowercase. + * + * @see https://infra.spec.whatwg.org/#ascii-lowercase + * @param text Text. + * @returns Lowercase text. + */ + public static asciiLowerCase(text: string): string { + const cached = ASCII_LOWER_CASE_CACHE.get(text); + if (cached) { + return cached; + } + let newText = ''; + for (const char of text) { + const value = char.charCodeAt(0); + if (value >= 65 && value <= 90) { + newText += String.fromCharCode(value + 32); + } else { + newText += char; + } + } + ASCII_LOWER_CASE_CACHE.set(text, newText); + return newText; + } + + /** + * ASCII uppercase. + * + * @see https://infra.spec.whatwg.org/#ascii-uppercase + * @param text Text. + * @returns Uppercase text. + */ + public static asciiUpperCase(text: string): string { + const cached = ASCII_UPPER_CASE_CACHE.get(text); + if (cached) { + return cached; + } + let newText = ''; + for (const char of text) { + const value = char.charCodeAt(0); + if (value >= 97 && value <= 122) { + newText += String.fromCharCode(value - 32); + } else { + newText += char; + } + } + ASCII_UPPER_CASE_CACHE.set(text, newText); + return newText; + } +} diff --git a/packages/happy-dom/src/config/HTMLElementConfig.ts b/packages/happy-dom/src/config/HTMLElementConfig.ts index bb88ca205..27a1e10bb 100644 --- a/packages/happy-dom/src/config/HTMLElementConfig.ts +++ b/packages/happy-dom/src/config/HTMLElementConfig.ts @@ -1,692 +1,465 @@ import HTMLElementConfigContentModelEnum from './HTMLElementConfigContentModelEnum.js'; -import IHTMLElementConfigEntity from './IHTMLElementConfigEntity.js'; /** * @see https://html.spec.whatwg.org/multipage/indices.html */ -export default <{ [key: string]: IHTMLElementConfigEntity }>{ +export default < + { [key: string]: { className: string; contentModel: HTMLElementConfigContentModelEnum } } +>{ a: { className: 'HTMLAnchorElement', - localName: 'a', - tagName: 'A', contentModel: HTMLElementConfigContentModelEnum.noSelfDescendants }, abbr: { className: 'HTMLElement', - localName: 'abbr', - tagName: 'ABBR', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, address: { className: 'HTMLElement', - localName: 'address', - tagName: 'ADDRESS', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, area: { className: 'HTMLAreaElement', - localName: 'area', - tagName: 'AREA', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, article: { className: 'HTMLElement', - localName: 'article', - tagName: 'ARTICLE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, aside: { className: 'HTMLElement', - localName: 'aside', - tagName: 'ASIDE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, audio: { className: 'HTMLAudioElement', - localName: 'audio', - tagName: 'AUDIO', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, b: { className: 'HTMLElement', - localName: 'b', - tagName: 'B', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, base: { className: 'HTMLBaseElement', - localName: 'base', - tagName: 'BASE', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, bdi: { className: 'HTMLElement', - localName: 'bdi', - tagName: 'BDI', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, bdo: { className: 'HTMLElement', - localName: 'bdo', - tagName: 'BDO', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, body: { className: 'HTMLBodyElement', - localName: 'body', - tagName: 'BODY', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, template: { className: 'HTMLTemplateElement', - localName: 'template', - tagName: 'TEMPLATE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, form: { className: 'HTMLFormElement', - localName: 'form', - tagName: 'FORM', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, input: { className: 'HTMLInputElement', - localName: 'input', - tagName: 'INPUT', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, textarea: { className: 'HTMLTextAreaElement', - localName: 'textarea', - tagName: 'TEXTAREA', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, script: { className: 'HTMLScriptElement', - localName: 'script', - tagName: 'SCRIPT', contentModel: HTMLElementConfigContentModelEnum.rawText }, img: { className: 'HTMLImageElement', - localName: 'img', - tagName: 'IMG', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, link: { className: 'HTMLLinkElement', - localName: 'link', - tagName: 'LINK', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, style: { className: 'HTMLStyleElement', - localName: 'style', - tagName: 'STYLE', contentModel: HTMLElementConfigContentModelEnum.rawText }, label: { className: 'HTMLLabelElement', - localName: 'label', - tagName: 'LABEL', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, slot: { className: 'HTMLSlotElement', - localName: 'slot', - tagName: 'SLOT', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, meta: { className: 'HTMLMetaElement', - localName: 'meta', - tagName: 'META', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, blockquote: { className: 'HTMLQuoteElement', - localName: 'blockquote', - tagName: 'BLOCKQUOTE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, br: { className: 'HTMLBRElement', - localName: 'br', - tagName: 'BR', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, button: { className: 'HTMLButtonElement', - localName: 'button', - tagName: 'BUTTON', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, canvas: { className: 'HTMLCanvasElement', - localName: 'canvas', - tagName: 'CANVAS', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, caption: { className: 'HTMLTableCaptionElement', - localName: 'caption', - tagName: 'CAPTION', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, cite: { className: 'HTMLElement', - localName: 'cite', - tagName: 'CITE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, code: { className: 'HTMLElement', - localName: 'code', - tagName: 'CODE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, col: { className: 'HTMLTableColElement', - localName: 'col', - tagName: 'COL', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, colgroup: { className: 'HTMLTableColElement', - localName: 'colgroup', - tagName: 'COLGROUP', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, data: { className: 'HTMLDataElement', - localName: 'data', - tagName: 'DATA', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, datalist: { className: 'HTMLDataListElement', - localName: 'datalist', - tagName: 'DATALIST', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, dd: { className: 'HTMLElement', - localName: 'dd', - tagName: 'DD', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, del: { className: 'HTMLModElement', - localName: 'del', - tagName: 'DEL', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, details: { className: 'HTMLDetailsElement', - localName: 'details', - tagName: 'DETAILS', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, dfn: { className: 'HTMLElement', - localName: 'dfn', - tagName: 'DFN', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, dialog: { className: 'HTMLDialogElement', - localName: 'dialog', - tagName: 'DIALOG', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, div: { className: 'HTMLDivElement', - localName: 'div', - tagName: 'DIV', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, dl: { className: 'HTMLDListElement', - localName: 'dl', - tagName: 'DL', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, dt: { className: 'HTMLElement', - localName: 'dt', - tagName: 'DT', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, em: { className: 'HTMLElement', - localName: 'em', - tagName: 'EM', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, embed: { className: 'HTMLEmbedElement', - localName: 'embed', - tagName: 'EMBED', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, fieldset: { className: 'HTMLFieldSetElement', - localName: 'fieldset', - tagName: 'FIELDSET', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, figcaption: { className: 'HTMLElement', - localName: 'figcaption', - tagName: 'FIGCAPTION', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, figure: { className: 'HTMLElement', - localName: 'figure', - tagName: 'FIGURE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, footer: { className: 'HTMLElement', - localName: 'footer', - tagName: 'FOOTER', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, h1: { className: 'HTMLHeadingElement', - localName: 'h1', - tagName: 'H1', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, h2: { className: 'HTMLHeadingElement', - localName: 'h2', - tagName: 'H2', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, h3: { className: 'HTMLHeadingElement', - localName: 'h3', - tagName: 'H3', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, h4: { className: 'HTMLHeadingElement', - localName: 'h4', - tagName: 'H4', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, h5: { className: 'HTMLHeadingElement', - localName: 'h5', - tagName: 'H5', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, h6: { className: 'HTMLHeadingElement', - localName: 'h6', - tagName: 'H6', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, head: { className: 'HTMLHeadElement', - localName: 'head', - tagName: 'HEAD', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, header: { className: 'HTMLElement', - localName: 'header', - tagName: 'HEADER', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, hgroup: { className: 'HTMLElement', - localName: 'hgroup', - tagName: 'HGROUP', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, hr: { className: 'HTMLHRElement', - localName: 'hr', - tagName: 'HR', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, html: { className: 'HTMLHtmlElement', - localName: 'html', - tagName: 'HTML', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, i: { className: 'HTMLElement', - localName: 'i', - tagName: 'I', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, iframe: { className: 'HTMLIFrameElement', - localName: 'iframe', - tagName: 'IFRAME', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, ins: { className: 'HTMLModElement', - localName: 'ins', - tagName: 'INS', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, kbd: { className: 'HTMLElement', - localName: 'kbd', - tagName: 'KBD', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, legend: { className: 'HTMLLegendElement', - localName: 'legend', - tagName: 'LEGEND', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, li: { className: 'HTMLLIElement', - localName: 'li', - tagName: 'LI', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, main: { className: 'HTMLElement', - localName: 'main', - tagName: 'MAIN', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, map: { className: 'HTMLMapElement', - localName: 'map', - tagName: 'MAP', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, mark: { className: 'HTMLElement', - localName: 'mark', - tagName: 'MARK', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, menu: { className: 'HTMLMenuElement', - localName: 'menu', - tagName: 'MENU', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, meter: { className: 'HTMLMeterElement', - localName: 'meter', - tagName: 'METER', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, nav: { className: 'HTMLElement', - localName: 'nav', - tagName: 'NAV', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, noscript: { className: 'HTMLElement', - localName: 'noscript', - tagName: 'NOSCRIPT', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, object: { className: 'HTMLObjectElement', - localName: 'object', - tagName: 'OBJECT', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, ol: { className: 'HTMLOListElement', - localName: 'ol', - tagName: 'OL', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, optgroup: { className: 'HTMLOptGroupElement', - localName: 'optgroup', - tagName: 'OPTGROUP', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, option: { className: 'HTMLOptionElement', - localName: 'option', - tagName: 'OPTION', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, output: { className: 'HTMLOutputElement', - localName: 'output', - tagName: 'OUTPUT', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, p: { className: 'HTMLParagraphElement', - localName: 'p', - tagName: 'P', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, param: { className: 'HTMLParamElement', - localName: 'param', - tagName: 'PARAM', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, picture: { className: 'HTMLPictureElement', - localName: 'picture', - tagName: 'PICTURE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, pre: { className: 'HTMLPreElement', - localName: 'pre', - tagName: 'PRE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, progress: { className: 'HTMLProgressElement', - localName: 'progress', - tagName: 'PROGRESS', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, q: { className: 'HTMLQuoteElement', - localName: 'q', - tagName: 'Q', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, rb: { className: 'HTMLElement', - localName: 'rb', - tagName: 'RB', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, rp: { className: 'HTMLElement', - localName: 'rp', - tagName: 'RP', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, rt: { className: 'HTMLElement', - localName: 'rt', - tagName: 'RT', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, rtc: { className: 'HTMLElement', - localName: 'rtc', - tagName: 'RTC', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, ruby: { className: 'HTMLElement', - localName: 'ruby', - tagName: 'RUBY', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, s: { className: 'HTMLElement', - localName: 's', - tagName: 'S', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, samp: { className: 'HTMLElement', - localName: 'samp', - tagName: 'SAMP', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, section: { className: 'HTMLElement', - localName: 'section', - tagName: 'SECTION', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, select: { className: 'HTMLSelectElement', - localName: 'select', - tagName: 'SELECT', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, small: { className: 'HTMLElement', - localName: 'small', - tagName: 'SMALL', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, source: { className: 'HTMLSourceElement', - localName: 'source', - tagName: 'SOURCE', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, span: { className: 'HTMLSpanElement', - localName: 'span', - tagName: 'SPAN', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, strong: { className: 'HTMLElement', - localName: 'strong', - tagName: 'STRONG', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, sub: { className: 'HTMLElement', - localName: 'sub', - tagName: 'SUB', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, summary: { className: 'HTMLElement', - localName: 'summary', - tagName: 'SUMMARY', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, sup: { className: 'HTMLElement', - localName: 'sup', - tagName: 'SUP', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, table: { className: 'HTMLTableElement', - localName: 'table', - tagName: 'TABLE', contentModel: HTMLElementConfigContentModelEnum.noFirstLevelSelfDescendants }, tbody: { className: 'HTMLTableSectionElement', - localName: 'tbody', - tagName: 'TBODY', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, td: { className: 'HTMLTableCellElement', - localName: 'td', - tagName: 'TD', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, tfoot: { className: 'HTMLTableSectionElement', - localName: 'tfoot', - tagName: 'TFOOT', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, th: { className: 'HTMLTableCellElement', - localName: 'th', - tagName: 'TH', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, thead: { className: 'HTMLTableSectionElement', - localName: 'thead', - tagName: 'THEAD', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, time: { className: 'HTMLTimeElement', - localName: 'time', - tagName: 'TIME', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, title: { className: 'HTMLTitleElement', - localName: 'title', - tagName: 'TITLE', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, tr: { className: 'HTMLTableRowElement', - localName: 'tr', - tagName: 'TR', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, track: { className: 'HTMLTrackElement', - localName: 'track', - tagName: 'TRACK', contentModel: HTMLElementConfigContentModelEnum.noDescendants }, u: { className: 'HTMLElement', - localName: 'u', - tagName: 'U', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, ul: { className: 'HTMLUListElement', - localName: 'ul', - tagName: 'UL', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, var: { className: 'HTMLElement', - localName: 'var', - tagName: 'VAR', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, video: { className: 'HTMLVideoElement', - localName: 'video', - tagName: 'VIDEO', contentModel: HTMLElementConfigContentModelEnum.anyDescendants }, wbr: { className: 'HTMLElement', - localName: 'wbr', - tagName: 'WBR', contentModel: HTMLElementConfigContentModelEnum.noDescendants } }; diff --git a/packages/happy-dom/src/config/IHTMLElementConfigEntity.ts b/packages/happy-dom/src/config/IHTMLElementConfigEntity.ts deleted file mode 100644 index 0deea8c70..000000000 --- a/packages/happy-dom/src/config/IHTMLElementConfigEntity.ts +++ /dev/null @@ -1,8 +0,0 @@ -import HTMLElementConfigContentModelEnum from './HTMLElementConfigContentModelEnum.js'; - -export default interface IHTMLElementConfigEntity { - className: string; - localName: string; - tagName: string; - contentModel: HTMLElementConfigContentModelEnum; -} diff --git a/packages/happy-dom/src/config/ISVGElementTagNameMap.ts b/packages/happy-dom/src/config/ISVGElementTagNameMap.ts index 50fd091f1..2eb73f187 100644 --- a/packages/happy-dom/src/config/ISVGElementTagNameMap.ts +++ b/packages/happy-dom/src/config/ISVGElementTagNameMap.ts @@ -1,67 +1,127 @@ -import SVGSVGElement from '../nodes/svg-element/SVGSVGElement.js'; -import SVGElement from '../nodes/svg-element/SVGElement.js'; +import SVGSVGElement from '../nodes/svg-svg-element/SVGSVGElement.js'; +import SVGAnimateElement from '../nodes/svg-animate-element/SVGAnimateElement.js'; +import SVGAnimateMotionElement from '../nodes/svg-animate-motion-element/SVGAnimateMotionElement.js'; +import SVGAnimateTransformElement from '../nodes/svg-animate-transform-element/SVGAnimateTransformElement.js'; +import SVGCircleElement from '../nodes/svg-circle-element/SVGCircleElement.js'; +import SVGClipPathElement from '../nodes/svg-clip-path-element/SVGClipPathElement.js'; +import SVGDefsElement from '../nodes/svg-defs-element/SVGDefsElement.js'; +import SVGDescElement from '../nodes/svg-desc-element/SVGDescElement.js'; +import SVGEllipseElement from '../nodes/svg-ellipse-element/SVGEllipseElement.js'; +import SVGFEBlendElement from '../nodes/svg-fe-blend-element/SVGFEBlendElement.js'; +import SVGFEColorMatrixElement from '../nodes/svg-fe-color-matrix-element/SVGFEColorMatrixElement.js'; +import SVGFEComponentTransferElement from '../nodes/svg-fe-component-transfer-element/SVGFEComponentTransferElement.js'; +import SVGFECompositeElement from '../nodes/svg-fe-composite-element/SVGFECompositeElement.js'; +import SVGFEConvolveMatrixElement from '../nodes/svg-fe-convolve-matrix-element/SVGFEConvolveMatrixElement.js'; +import SVGFEDiffuseLightingElement from '../nodes/svg-fe-diffuse-lighting-element/SVGFEDiffuseLightingElement.js'; +import SVGFEDisplacementMapElement from '../nodes/svg-fe-displacement-map-element/SVGFEDisplacementMapElement.js'; +import SVGFEDistantLightElement from '../nodes/svg-fe-distant-light-element/SVGFEDistantLightElement.js'; +import SVGFEDropShadowElement from '../nodes/svg-fe-drop-shadow-element/SVGFEDropShadowElement.js'; +import SVGFEFloodElement from '../nodes/svg-fe-flood-element/SVGFEFloodElement.js'; +import SVGFEFuncAElement from '../nodes/svg-fe-func-a-element/SVGFEFuncAElement.js'; +import SVGFEFuncBElement from '../nodes/svg-fe-func-b-element/SVGFEFuncBElement.js'; +import SVGFEFuncGElement from '../nodes/svg-fe-func-g-element/SVGFEFuncGElement.js'; +import SVGFEFuncRElement from '../nodes/svg-fe-func-r-element/SVGFEFuncRElement.js'; +import SVGFEGaussianBlurElement from '../nodes/svg-fe-gaussian-blur-element/SVGFEGaussianBlurElement.js'; +import SVGFEImageElement from '../nodes/svg-fe-image-element/SVGFEImageElement.js'; +import SVGFEMergeElement from '../nodes/svg-fe-merge-element/SVGFEMergeElement.js'; +import SVGFEMergeNodeElement from '../nodes/svg-fe-merge-node-element/SVGFEMergeNodeElement.js'; +import SVGFEMorphologyElement from '../nodes/svg-fe-morphology-element/SVGFEMorphologyElement.js'; +import SVGFEOffsetElement from '../nodes/svg-fe-offset-element/SVGFEOffsetElement.js'; +import SVGFEPointLightElement from '../nodes/svg-fe-point-light-element/SVGFEPointLightElement.js'; +import SVGFESpecularLightingElement from '../nodes/svg-fe-specular-lighting-element/SVGFESpecularLightingElement.js'; +import SVGFESpotLightElement from '../nodes/svg-fe-spot-light-element/SVGFESpotLightElement.js'; +import SVGFETileElement from '../nodes/svg-fe-tile-element/SVGFETileElement.js'; +import SVGFETurbulenceElement from '../nodes/svg-fe-turbulence-element/SVGFETurbulenceElement.js'; +import SVGFilterElement from '../nodes/svg-filter-element/SVGFilterElement.js'; +import SVGForeignObjectElement from '../nodes/svg-foreign-object-element/SVGForeignObjectElement.js'; +import SVGGElement from '../nodes/svg-g-element/SVGGElement.js'; +import SVGImageElement from '../nodes/svg-image-element/SVGImageElement.js'; +import SVGLineElement from '../nodes/svg-line-element/SVGLineElement.js'; +import SVGLinearGradientElement from '../nodes/svg-linear-gradient-element/SVGLinearGradientElement.js'; +import SVGMarkerElement from '../nodes/svg-marker-element/SVGMarkerElement.js'; +import SVGMaskElement from '../nodes/svg-mask-element/SVGMaskElement.js'; +import SVGMetadataElement from '../nodes/svg-metadata-element/SVGMetadataElement.js'; +import SVGMPathElement from '../nodes/svg-m-path-element/SVGMPathElement.js'; +import SVGPathElement from '../nodes/svg-path-element/SVGPathElement.js'; +import SVGPatternElement from '../nodes/svg-pattern-element/SVGPatternElement.js'; +import SVGPolygonElement from '../nodes/svg-polygon-element/SVGPolygonElement.js'; +import SVGPolylineElement from '../nodes/svg-polyline-element/SVGPolylineElement.js'; +import SVGRadialGradientElement from '../nodes/svg-radial-gradient-element/SVGRadialGradientElement.js'; +import SVGRectElement from '../nodes/svg-rect-element/SVGRectElement.js'; +import SVGScriptElement from '../nodes/svg-script-element/SVGScriptElement.js'; +import SVGSetElement from '../nodes/svg-set-element/SVGSetElement.js'; +import SVGStopElement from '../nodes/svg-stop-element/SVGStopElement.js'; +import SVGStyleElement from '../nodes/svg-style-element/SVGStyleElement.js'; +import SVGSwitchElement from '../nodes/svg-switch-element/SVGSwitchElement.js'; +import SVGSymbolElement from '../nodes/svg-symbol-element/SVGSymbolElement.js'; +import SVGTextElement from '../nodes/svg-text-element/SVGTextElement.js'; +import SVGTextPathElement from '../nodes/svg-text-path-element/SVGTextPathElement.js'; +import SVGTitleElement from '../nodes/svg-title-element/SVGTitleElement.js'; +import SVGTSpanElement from '../nodes/svg-t-span-element/SVGTSpanElement.js'; +import SVGUseElement from '../nodes/svg-use-element/SVGUseElement.js'; +import SVGViewElement from '../nodes/svg-view-element/SVGViewElement.js'; export default interface ISVGElementTagNameMap { svg: SVGSVGElement; - animate: SVGElement; - animateMotion: SVGElement; - animateTransform: SVGElement; - circle: SVGElement; - clipPath: SVGElement; - defs: SVGElement; - desc: SVGElement; - ellipse: SVGElement; - feBlend: SVGElement; - feColorMatrix: SVGElement; - feComponentTransfer: SVGElement; - feComposite: SVGElement; - feConvolveMatrix: SVGElement; - feDiffuseLighting: SVGElement; - feDisplacementMap: SVGElement; - feDistantLight: SVGElement; - feDropShadow: SVGElement; - feFlood: SVGElement; - feFuncA: SVGElement; - feFuncB: SVGElement; - feFuncG: SVGElement; - feFuncR: SVGElement; - feGaussianBlur: SVGElement; - feImage: SVGElement; - feMerge: SVGElement; - feMergeNode: SVGElement; - feMorphology: SVGElement; - feOffset: SVGElement; - fePointLight: SVGElement; - feSpecularLighting: SVGElement; - feSpotLight: SVGElement; - feTile: SVGElement; - feTurbulence: SVGElement; - filter: SVGElement; - foreignObject: SVGElement; - g: SVGElement; - image: SVGElement; - line: SVGElement; - linearGradient: SVGElement; - marker: SVGElement; - mask: SVGElement; - metadata: SVGElement; - mpath: SVGElement; - path: SVGElement; - pattern: SVGElement; - polygon: SVGElement; - polyline: SVGElement; - radialGradient: SVGElement; - rect: SVGElement; - script: SVGElement; - set: SVGElement; - stop: SVGElement; - style: SVGElement; - switch: SVGElement; - symbol: SVGElement; - text: SVGElement; - textPath: SVGElement; - title: SVGElement; - tspan: SVGElement; - use: SVGElement; - view: SVGElement; + animate: SVGAnimateElement; + animateMotion: SVGAnimateMotionElement; + animateTransform: SVGAnimateTransformElement; + circle: SVGCircleElement; + clipPath: SVGClipPathElement; + defs: SVGDefsElement; + desc: SVGDescElement; + ellipse: SVGEllipseElement; + feBlend: SVGFEBlendElement; + feColorMatrix: SVGFEColorMatrixElement; + feComponentTransfer: SVGFEComponentTransferElement; + feComposite: SVGFECompositeElement; + feConvolveMatrix: SVGFEConvolveMatrixElement; + feDiffuseLighting: SVGFEDiffuseLightingElement; + feDisplacementMap: SVGFEDisplacementMapElement; + feDistantLight: SVGFEDistantLightElement; + feDropShadow: SVGFEDropShadowElement; + feFlood: SVGFEFloodElement; + feFuncA: SVGFEFuncAElement; + feFuncB: SVGFEFuncBElement; + feFuncG: SVGFEFuncGElement; + feFuncR: SVGFEFuncRElement; + feGaussianBlur: SVGFEGaussianBlurElement; + feImage: SVGFEImageElement; + feMerge: SVGFEMergeElement; + feMergeNode: SVGFEMergeNodeElement; + feMorphology: SVGFEMorphologyElement; + feOffset: SVGFEOffsetElement; + fePointLight: SVGFEPointLightElement; + feSpecularLighting: SVGFESpecularLightingElement; + feSpotLight: SVGFESpotLightElement; + feTile: SVGFETileElement; + feTurbulence: SVGFETurbulenceElement; + filter: SVGFilterElement; + foreignObject: SVGForeignObjectElement; + g: SVGGElement; + image: SVGImageElement; + line: SVGLineElement; + linearGradient: SVGLinearGradientElement; + marker: SVGMarkerElement; + mask: SVGMaskElement; + metadata: SVGMetadataElement; + mpath: SVGMPathElement; + path: SVGPathElement; + pattern: SVGPatternElement; + polygon: SVGPolygonElement; + polyline: SVGPolylineElement; + radialGradient: SVGRadialGradientElement; + rect: SVGRectElement; + script: SVGScriptElement; + set: SVGSetElement; + stop: SVGStopElement; + style: SVGStyleElement; + switch: SVGSwitchElement; + symbol: SVGSymbolElement; + text: SVGTextElement; + textPath: SVGTextPathElement; + title: SVGTitleElement; + tspan: SVGTSpanElement; + use: SVGUseElement; + view: SVGViewElement; } diff --git a/packages/happy-dom/src/config/SVGElementConfig.ts b/packages/happy-dom/src/config/SVGElementConfig.ts new file mode 100644 index 000000000..f0cc90139 --- /dev/null +++ b/packages/happy-dom/src/config/SVGElementConfig.ts @@ -0,0 +1,70 @@ +export default <{ [key: string]: { localName: string; className: string } }>{ + svg: { localName: 'svg', className: 'SVGSVGElement' }, + animate: { localName: 'animate', className: 'SVGAnimateElement' }, + animatemotion: { localName: 'animateMotion', className: 'SVGAnimateMotionElement' }, + animatetransform: { localName: 'animateTransform', className: 'SVGAnimateTransformElement' }, + circle: { localName: 'circle', className: 'SVGCircleElement' }, + clippath: { localName: 'clipPath', className: 'SVGClipPathElement' }, + defs: { localName: 'defs', className: 'SVGDefsElement' }, + desc: { localName: 'desc', className: 'SVGDescElement' }, + ellipse: { localName: 'ellipse', className: 'SVGEllipseElement' }, + feblend: { localName: 'feBlend', className: 'SVGFEBlendElement' }, + fecolormatrix: { localName: 'feColorMatrix', className: 'SVGFEColorMatrixElement' }, + fecomponenttransfer: { + localName: 'feComponentTransfer', + className: 'SVGFEComponentTransferElement' + }, + fecomposite: { localName: 'feComposite', className: 'SVGFECompositeElement' }, + feconvolvematrix: { localName: 'feConvolveMatrix', className: 'SVGFEConvolveMatrixElement' }, + fediffuselighting: { localName: 'feDiffuseLighting', className: 'SVGFEDiffuseLightingElement' }, + fedisplacementmap: { localName: 'feDisplacementMap', className: 'SVGFEDisplacementMapElement' }, + fedistantlight: { localName: 'feDistantLight', className: 'SVGFEDistantLightElement' }, + fedropshadow: { localName: 'feDropShadow', className: 'SVGFEDropShadowElement' }, + feflood: { localName: 'feFlood', className: 'SVGFEFloodElement' }, + fefunca: { localName: 'feFuncA', className: 'SVGFEFuncAElement' }, + fefuncb: { localName: 'feFuncB', className: 'SVGFEFuncBElement' }, + fefuncg: { localName: 'feFuncG', className: 'SVGFEFuncGElement' }, + fefuncr: { localName: 'feFuncR', className: 'SVGFEFuncRElement' }, + fegaussianblur: { localName: 'feGaussianBlur', className: 'SVGFEGaussianBlurElement' }, + feimage: { localName: 'feImage', className: 'SVGFEImageElement' }, + femerge: { localName: 'feMerge', className: 'SVGFEMergeElement' }, + femergenode: { localName: 'feMergeNode', className: 'SVGFEMergeNodeElement' }, + femorphology: { localName: 'feMorphology', className: 'SVGFEMorphologyElement' }, + feoffset: { localName: 'feOffset', className: 'SVGFEOffsetElement' }, + fepointlight: { localName: 'fePointLight', className: 'SVGFEPointLightElement' }, + fespecularlighting: { + localName: 'feSpecularLighting', + className: 'SVGFESpecularLightingElement' + }, + fespotlight: { localName: 'feSpotLight', className: 'SVGFESpotLightElement' }, + fetile: { localName: 'feTile', className: 'SVGFETileElement' }, + feturbulence: { localName: 'feTurbulence', className: 'SVGFETurbulenceElement' }, + filter: { localName: 'filter', className: 'SVGFilterElement' }, + foreignobject: { localName: 'foreignObject', className: 'SVGForeignObjectElement' }, + g: { localName: 'g', className: 'SVGGElement' }, + image: { localName: 'image', className: 'SVGImageElement' }, + line: { localName: 'line', className: 'SVGLineElement' }, + lineargradient: { localName: 'linearGradient', className: 'SVGLinearGradientElement' }, + marker: { localName: 'marker', className: 'SVGMarkerElement' }, + mask: { localName: 'mask', className: 'SVGMaskElement' }, + metadata: { localName: 'metadata', className: 'SVGMetadataElement' }, + mpath: { localName: 'mpath', className: 'SVGMPathElement' }, + path: { localName: 'path', className: 'SVGPathElement' }, + pattern: { localName: 'pattern', className: 'SVGPatternElement' }, + polygon: { localName: 'polygon', className: 'SVGPolygonElement' }, + polyline: { localName: 'polyline', className: 'SVGPolylineElement' }, + radialgradient: { localName: 'radialGradient', className: 'SVGRadialGradientElement' }, + rect: { localName: 'rect', className: 'SVGRectElement' }, + script: { localName: 'script', className: 'SVGScriptElement' }, + set: { localName: 'set', className: 'SVGSetElement' }, + stop: { localName: 'stop', className: 'SVGStopElement' }, + style: { localName: 'style', className: 'SVGStyleElement' }, + switch: { localName: 'switch', className: 'SVGSwitchElement' }, + symbol: { localName: 'symbol', className: 'SVGSymbolElement' }, + text: { localName: 'text', className: 'SVGTextElement' }, + textpath: { localName: 'textPath', className: 'SVGTextPathElement' }, + title: { localName: 'title', className: 'SVGTitleElement' }, + tspan: { localName: 'tspan', className: 'SVGTSpanElement' }, + use: { localName: 'use', className: 'SVGUseElement' }, + view: { localName: 'view', className: 'SVGViewElement' } +}; diff --git a/packages/happy-dom/src/css/CSSRule.ts b/packages/happy-dom/src/css/CSSRule.ts index 3bbe5828e..ef031bbb4 100644 --- a/packages/happy-dom/src/css/CSSRule.ts +++ b/packages/happy-dom/src/css/CSSRule.ts @@ -1,10 +1,13 @@ import CSSStyleSheet from './CSSStyleSheet.js'; import CSSRuleTypeEnum from './CSSRuleTypeEnum.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; /** * CSSRule interface. */ export default class CSSRule { + // Static properties public static CONTAINER_RULE = CSSRuleTypeEnum.containerRule; public static STYLE_RULE = CSSRuleTypeEnum.styleRule; public static IMPORT_RULE = CSSRuleTypeEnum.importRule; @@ -20,10 +23,28 @@ export default class CSSRule { public static FONT_FEATURE_VALUES_RULE = CSSRuleTypeEnum.fontFeatureValuesRule; public static REGION_STYLE_RULE = CSSRuleTypeEnum.regionStyleRule; + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + + // Public properties public parentRule: CSSRule = null; public parentStyleSheet: CSSStyleSheet = null; public type: number = null; + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + */ + constructor(illegalConstructorSymbol: Symbol, window: BrowserWindow) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + } + /** * Returns selector text. * diff --git a/packages/happy-dom/src/css/CSSStyleSheet.ts b/packages/happy-dom/src/css/CSSStyleSheet.ts index 0dc440355..41f866ac0 100644 --- a/packages/happy-dom/src/css/CSSStyleSheet.ts +++ b/packages/happy-dom/src/css/CSSStyleSheet.ts @@ -1,8 +1,9 @@ -import DOMException from '../exception/DOMException.js'; import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js'; import CSSParser from './utilities/CSSParser.js'; import CSSRule from './CSSRule.js'; import MediaList from './MediaList.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import * as PropertySymbol from '../PropertySymbol.js'; /** * CSS StyleSheet. @@ -11,6 +12,9 @@ import MediaList from './MediaList.js'; * https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet. */ export default class CSSStyleSheet { + // Injected by WindowClassExtender + protected declare [PropertySymbol.window]: BrowserWindow; + public value: string = null; public name: string = null; public namespaceURI: string = null; @@ -56,11 +60,14 @@ export default class CSSStyleSheet { const rules = CSSParser.parseFromString(this, rule); if (rules.length === 0) { - throw new DOMException('Invalid CSS rule.', DOMExceptionNameEnum.hierarchyRequestError); + throw new this[PropertySymbol.window].DOMException( + 'Invalid CSS rule.', + DOMExceptionNameEnum.hierarchyRequestError + ); } if (rules.length > 1) { - throw new DOMException( + throw new this[PropertySymbol.window].DOMException( 'Only one rule is allowed to be added.', DOMExceptionNameEnum.syntaxError ); @@ -68,7 +75,7 @@ export default class CSSStyleSheet { if (index !== undefined) { if (index > this.cssRules.length) { - throw new DOMException( + throw new this[PropertySymbol.window].DOMException( 'Index is more than the length of CSSRuleList.', DOMExceptionNameEnum.indexSizeError ); diff --git a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts deleted file mode 100644 index 4d4a45c14..000000000 --- a/packages/happy-dom/src/css/declaration/AbstractCSSStyleDeclaration.ts +++ /dev/null @@ -1,190 +0,0 @@ -import Element from '../../nodes/element/Element.js'; -import CSSRule from '../CSSRule.js'; -import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; -import CSSStyleDeclarationElementStyle from './element-style/CSSStyleDeclarationElementStyle.js'; -import CSSStyleDeclarationPropertyManager from './property-manager/CSSStyleDeclarationPropertyManager.js'; -import * as PropertySymbol from '../../PropertySymbol.js'; - -/** - * CSS Style Declaration. - */ -export default abstract class AbstractCSSStyleDeclaration { - public readonly parentRule: CSSRule = null; - #style: CSSStyleDeclarationPropertyManager = null; - #ownerElement: Element; - #computed: boolean; - #elementStyle: CSSStyleDeclarationElementStyle = null; - - /** - * Constructor. - * - * @param [ownerElement] Computed style element. - * @param [computed] Computed. - */ - constructor(ownerElement: Element = null, computed = false) { - this.#style = !ownerElement ? new CSSStyleDeclarationPropertyManager() : null; - this.#ownerElement = ownerElement; - this.#computed = ownerElement ? computed : false; - this.#elementStyle = ownerElement - ? new CSSStyleDeclarationElementStyle(ownerElement, this.#computed) - : null; - } - - /** - * Returns length. - * - * @returns Length. - */ - public get length(): number { - if (this.#ownerElement) { - const style = this.#elementStyle.getElementStyle(); - return style.size(); - } - - return this.#style.size(); - } - - /** - * Returns the style decleration as a CSS text. - * - * @returns CSS text. - */ - public get cssText(): string { - if (this.#ownerElement) { - if (this.#computed) { - return ''; - } - - return this.#elementStyle.getElementStyle().toString(); - } - - return this.#style.toString(); - } - - /** - * Sets CSS text. - * - * @param cssText CSS text. - */ - public set cssText(cssText: string) { - if (this.#computed) { - throw new this[PropertySymbol.window].DOMException( - `Failed to execute 'cssText' on 'CSSStyleDeclaration': These styles are computed, and the properties are therefore read-only.`, - DOMExceptionNameEnum.domException - ); - } - - if (this.#ownerElement) { - this.#ownerElement.setAttribute( - 'style', - new CSSStyleDeclarationPropertyManager({ cssText }).toString() - ); - } else { - this.#style = new CSSStyleDeclarationPropertyManager({ cssText }); - } - } - - /** - * Returns item. - * - * @param index Index. - * @returns Item. - */ - public item(index: number): string { - if (this.#ownerElement) { - return this.#elementStyle.getElementStyle().item(index); - } - return this.#style.item(index); - } - - /** - * Set a property. - * - * @param name Property name. - * @param value Value. Must not contain "!important" as that should be set using the priority parameter. - * @param [priority] Can be "important", or an empty string. - */ - public setProperty(name: string, value: string, priority?: 'important' | '' | undefined): void { - if (this.#computed) { - throw new this[PropertySymbol.window].DOMException( - `Failed to execute 'setProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${name}' property is read-only.`, - DOMExceptionNameEnum.domException - ); - } - - if (priority !== '' && priority !== undefined && priority !== 'important') { - return; - } - - const stringValue = String(value); - - if (!stringValue) { - this.removeProperty(name); - } else if (this.#ownerElement) { - const style = this.#elementStyle.getElementStyle(); - style.set(name, stringValue, !!priority); - - this.#ownerElement.setAttribute('style', style.toString()); - } else { - this.#style.set(name, stringValue, !!priority); - } - } - - /** - * Removes a property. - * - * @param name Property name in kebab case. - * @param value Value. Must not contain "!important" as that should be set using the priority parameter. - * @param [priority] Can be "important", or an empty string. - */ - public removeProperty(name: string): void { - if (this.#computed) { - throw new this[PropertySymbol.window].DOMException( - `Failed to execute 'removeProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${name}' property is read-only.`, - DOMExceptionNameEnum.domException - ); - } - - if (this.#ownerElement) { - const style = this.#elementStyle.getElementStyle(); - style.remove(name); - const newCSSText = style.toString(); - - if (newCSSText) { - this.#ownerElement.setAttribute('style', newCSSText); - } else { - this.#ownerElement.removeAttribute('style'); - } - } else { - this.#style.remove(name); - } - } - - /** - * Returns a property. - * - * @param name Property name in kebab case. - * @returns Property value. - */ - public getPropertyValue(name: string): string { - if (this.#ownerElement) { - const style = this.#elementStyle.getElementStyle(); - return style.get(name)?.value || ''; - } - return this.#style.get(name)?.value || ''; - } - - /** - * Returns a property. - * - * @param name Property name in kebab case. - * @returns "important" if set to be important. - */ - public getPropertyPriority(name: string): string { - if (this.#ownerElement) { - const style = this.#elementStyle.getElementStyle(); - return style.get(name)?.important ? 'important' : ''; - } - return this.#style.get(name)?.important ? 'important' : ''; - } -} diff --git a/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts b/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts index c394d72bf..ec4b81318 100644 --- a/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts +++ b/packages/happy-dom/src/css/declaration/CSSStyleDeclaration.ts @@ -1,15 +1,65 @@ -import AbstractCSSStyleDeclaration from './AbstractCSSStyleDeclaration.js'; - -/* eslint-disable jsdoc/require-jsdoc */ +import Element from '../../nodes/element/Element.js'; +import CSSRule from '../CSSRule.js'; +import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; +import CSSStyleDeclarationPropertyManager from './property-manager/CSSStyleDeclarationPropertyManager.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import BrowserWindow from '../../window/BrowserWindow.js'; +import CSSStyleDeclarationComputedStyle from './computed-style/CSSStyleDeclarationComputedStyle.js'; /** * CSS Style Declaration. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration */ -export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { +export default class CSSStyleDeclaration { + // Public properties + public readonly parentRule: CSSRule = null; + + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + + // Private properties + #element: Element; + #computed: boolean; + #cache: { + attributeValue: string | null; + propertyManager: CSSStyleDeclarationPropertyManager | null; + } = { + attributeValue: null, + propertyManager: null + }; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.element] Element. + * @param [options.computed] Computed. + */ + constructor( + illegalConstructorSymbol: Symbol, + window: BrowserWindow, + options?: { + element?: Element; + computed?: boolean; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + this[PropertySymbol.window] = window; + this.#element = options?.element; + this.#computed = options?.element ? !!options?.computed : false; + } + /** * Index properties */ + /* eslint-disable jsdoc/require-jsdoc */ + public get 0(): string { return this.item(0) || undefined; } @@ -4740,4 +4790,180 @@ export default class CSSStyleDeclaration extends AbstractCSSStyleDeclaration { public set containerName(value: string) { this.setProperty('container-name', value); } + + /* eslint-enable jsdoc/require-jsdoc */ + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this.#getPropertyManager().size(); + } + + /** + * Returns the style decleration as a CSS text. + * + * @returns CSS text. + */ + public get cssText(): string { + if (this.#element && this.#computed) { + return ''; + } + + return this.#getPropertyManager().toString(); + } + + /** + * Sets CSS text. + * + * @param cssText CSS text. + */ + public set cssText(cssText: string) { + if (this.#computed) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'cssText' on 'CSSStyleDeclaration': These styles are computed, and the properties are therefore read-only.`, + DOMExceptionNameEnum.domException + ); + } + + if (this.#element) { + this.#cache.propertyManager = new CSSStyleDeclarationPropertyManager({ cssText }); + this.#cache.attributeValue = cssText; + this.#element.setAttribute('style', this.#cache.propertyManager.toString()); + } else { + this.#cache.propertyManager = new CSSStyleDeclarationPropertyManager({ cssText }); + } + } + + /** + * Returns item. + * + * @param index Index. + * @returns Item. + */ + public item(index: number): string { + return this.#getPropertyManager().item(index); + } + + /** + * Set a property. + * + * @param name Property name. + * @param value Value. Must not contain "!important" as that should be set using the priority parameter. + * @param [priority] Can be "important", or an empty string. + */ + public setProperty(name: string, value: string, priority?: 'important' | '' | undefined): void { + if (this.#computed) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'setProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${name}' property is read-only.`, + DOMExceptionNameEnum.domException + ); + } + + if (priority !== '' && priority !== undefined && priority !== 'important') { + return; + } + + const stringValue = String(value).trim(); + const propertyManager = this.#getPropertyManager(); + + if (stringValue) { + propertyManager.set(name, stringValue, !!priority); + } else { + propertyManager.remove(name); + } + + if (this.#element) { + this.#cache.attributeValue = propertyManager.toString(); + if (this.#cache.attributeValue) { + this.#element.setAttribute('style', this.#cache.attributeValue); + } else { + this.#element.removeAttribute('style'); + } + } + } + + /** + * Removes a property. + * + * @param name Property name in kebab case. + * @param value Value. Must not contain "!important" as that should be set using the priority parameter. + * @param [priority] Can be "important", or an empty string. + */ + public removeProperty(name: string): void { + if (this.#computed) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeProperty' on 'CSSStyleDeclaration': These styles are computed, and therefore the '${name}' property is read-only.`, + DOMExceptionNameEnum.domException + ); + } + + const propertyManager = this.#getPropertyManager(); + + propertyManager.remove(name); + + if (this.#element) { + this.#cache.attributeValue = propertyManager.toString(); + + if (this.#cache.attributeValue) { + this.#element.setAttribute('style', this.#cache.attributeValue); + } else { + this.#element.removeAttribute('style'); + } + } + } + + /** + * Returns a property. + * + * @param name Property name in kebab case. + * @returns Property value. + */ + public getPropertyValue(name: string): string { + return this.#getPropertyManager().get(name)?.value || ''; + } + + /** + * Returns a property. + * + * @param name Property name in kebab case. + * @returns "important" if set to be important. + */ + public getPropertyPriority(name: string): string { + return this.#getPropertyManager().get(name)?.important ? 'important' : ''; + } + + /** + * Returns property manager. + * + * @returns Property manager. + */ + #getPropertyManager(): CSSStyleDeclarationPropertyManager { + const element = this.#element; + const cache = this.#cache; + + if (!element) { + if (!cache.propertyManager) { + cache.propertyManager = new CSSStyleDeclarationPropertyManager(); + } + return cache.propertyManager; + } + + if (this.#computed) { + return new CSSStyleDeclarationComputedStyle(element).getComputedStyle(); + } + + const attributeValue = + element[PropertySymbol.attributes][PropertySymbol.namedItems].get('style')?.[ + PropertySymbol.value + ]; + + if (cache.attributeValue !== attributeValue) { + cache.propertyManager = new CSSStyleDeclarationPropertyManager({ cssText: attributeValue }); + } + + return cache.propertyManager; + } } diff --git a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts b/packages/happy-dom/src/css/declaration/computed-style/CSSStyleDeclarationComputedStyle.ts similarity index 92% rename from packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts rename to packages/happy-dom/src/css/declaration/computed-style/CSSStyleDeclarationComputedStyle.ts index 224d2bc7a..b78051caf 100644 --- a/packages/happy-dom/src/css/declaration/element-style/CSSStyleDeclarationElementStyle.ts +++ b/packages/happy-dom/src/css/declaration/computed-style/CSSStyleDeclarationComputedStyle.ts @@ -30,9 +30,8 @@ type IStyleAndElement = { /** * CSS Style Declaration utility */ -export default class CSSStyleDeclarationElementStyle { +export default class CSSStyleDeclarationComputedStyle { private element: Element; - private computed: boolean; /** * Constructor. @@ -40,44 +39,8 @@ export default class CSSStyleDeclarationElementStyle { * @param element Element. * @param [computed] Computed. */ - constructor(element: Element, computed = false) { + constructor(element: Element) { this.element = element; - this.computed = computed; - } - - /** - * Returns element style properties. - * - * @returns Element style properties. - */ - public getElementStyle(): CSSStyleDeclarationPropertyManager { - if (this.computed) { - return this.getComputedElementStyle(); - } - - const cachedResult = this.element[PropertySymbol.cache].style; - - if (cachedResult?.result) { - const result = cachedResult.result.deref(); - if (result) { - return result; - } - } - - const cssText = - this.element[PropertySymbol.attributes][PropertySymbol.namedItems].get('style')?.[ - PropertySymbol.value - ]; - - if (cssText) { - const propertyManager = new CSSStyleDeclarationPropertyManager({ cssText }); - this.element[PropertySymbol.cache].style = { - result: new WeakRef(propertyManager) - }; - return propertyManager; - } - - return new CSSStyleDeclarationPropertyManager(); } /** @@ -86,7 +49,7 @@ export default class CSSStyleDeclarationElementStyle { * @param element Element. * @returns Style sheets. */ - private getComputedElementStyle(): CSSStyleDeclarationPropertyManager { + public getComputedStyle(): CSSStyleDeclarationPropertyManager { const documentElements: Array = []; const parentElements: Array = []; let styleAndElement: IStyleAndElement = { diff --git a/packages/happy-dom/src/css/declaration/element-style/config/CSSStyleDeclarationElementDefaultCSS.ts b/packages/happy-dom/src/css/declaration/computed-style/config/CSSStyleDeclarationElementDefaultCSS.ts similarity index 100% rename from packages/happy-dom/src/css/declaration/element-style/config/CSSStyleDeclarationElementDefaultCSS.ts rename to packages/happy-dom/src/css/declaration/computed-style/config/CSSStyleDeclarationElementDefaultCSS.ts diff --git a/packages/happy-dom/src/css/declaration/element-style/config/CSSStyleDeclarationElementInheritedProperties.ts b/packages/happy-dom/src/css/declaration/computed-style/config/CSSStyleDeclarationElementInheritedProperties.ts similarity index 100% rename from packages/happy-dom/src/css/declaration/element-style/config/CSSStyleDeclarationElementInheritedProperties.ts rename to packages/happy-dom/src/css/declaration/computed-style/config/CSSStyleDeclarationElementInheritedProperties.ts diff --git a/packages/happy-dom/src/css/declaration/element-style/config/CSSStyleDeclarationElementMeasurementProperties.ts b/packages/happy-dom/src/css/declaration/computed-style/config/CSSStyleDeclarationElementMeasurementProperties.ts similarity index 100% rename from packages/happy-dom/src/css/declaration/element-style/config/CSSStyleDeclarationElementMeasurementProperties.ts rename to packages/happy-dom/src/css/declaration/computed-style/config/CSSStyleDeclarationElementMeasurementProperties.ts diff --git a/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts b/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts index e4bc3283f..6363ee1f8 100644 --- a/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts +++ b/packages/happy-dom/src/css/rules/CSSFontFaceRule.ts @@ -17,7 +17,10 @@ export default class CSSFontFaceRule extends CSSRule { */ public get style(): CSSStyleDeclaration { if (!this.#style) { - this.#style = new CSSStyleDeclaration(); + this.#style = new CSSStyleDeclaration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window] + ); (this.#style.parentRule) = this; this.#style.cssText = this[PropertySymbol.cssText]; } diff --git a/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts b/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts index 01deb49a7..5a5173d1c 100644 --- a/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts +++ b/packages/happy-dom/src/css/rules/CSSKeyframeRule.ts @@ -18,7 +18,10 @@ export default class CSSKeyframeRule extends CSSRule { */ public get style(): CSSStyleDeclaration { if (!this.#style) { - this.#style = new CSSStyleDeclaration(); + this.#style = new CSSStyleDeclaration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window] + ); (this.#style.parentRule) = this; this.#style.cssText = this[PropertySymbol.cssText]; } diff --git a/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts b/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts index e4cb5a5d7..545e8851e 100644 --- a/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts +++ b/packages/happy-dom/src/css/rules/CSSKeyframesRule.ts @@ -1,6 +1,7 @@ import CSSRule from '../CSSRule.js'; import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration.js'; import CSSKeyframeRule from './CSSKeyframeRule.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; const CSS_RULE_REGEXP = /([^{]+){([^}]+)}/; @@ -33,8 +34,14 @@ export default class CSSKeyframesRule extends CSSRule { public appendRule(rule: string): void { const match = rule.match(CSS_RULE_REGEXP); if (match) { - const cssRule = new CSSKeyframeRule(); - const style = new CSSStyleDeclaration(); + const cssRule = new CSSKeyframeRule( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window] + ); + const style = new CSSStyleDeclaration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window] + ); (cssRule.parentRule) = this; (cssRule.keyText) = match[1].trim(); diff --git a/packages/happy-dom/src/css/rules/CSSStyleRule.ts b/packages/happy-dom/src/css/rules/CSSStyleRule.ts index 0c2aed2d9..a02c1e6e6 100644 --- a/packages/happy-dom/src/css/rules/CSSStyleRule.ts +++ b/packages/happy-dom/src/css/rules/CSSStyleRule.ts @@ -19,7 +19,10 @@ export default class CSSStyleRule extends CSSRule { */ public get style(): CSSStyleDeclaration { if (!this.#style) { - this.#style = new CSSStyleDeclaration(); + this.#style = new CSSStyleDeclaration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window] + ); (this.#style.parentRule) = this; this.#style.cssText = this[PropertySymbol.cssText]; } diff --git a/packages/happy-dom/src/css/utilities/CSSParser.ts b/packages/happy-dom/src/css/utilities/CSSParser.ts index 83604ec58..c8b31baab 100644 --- a/packages/happy-dom/src/css/utilities/CSSParser.ts +++ b/packages/happy-dom/src/css/utilities/CSSParser.ts @@ -24,6 +24,7 @@ export default class CSSParser { * @returns Root element. */ public static parseFromString(parentStyleSheet: CSSStyleSheet, cssText: string): CSSRule[] { + const window = parentStyleSheet[PropertySymbol.window]; const css = cssText.replace(COMMENT_REGEXP, ''); const cssRules = []; const regExp = /{|}/gm; @@ -40,7 +41,7 @@ export default class CSSParser { selectorText.startsWith('@keyframes') || selectorText.startsWith('@-webkit-keyframes') ) { - const newRule = new CSSKeyframesRule(); + const newRule = new CSSKeyframesRule(PropertySymbol.illegalConstructor, window); (newRule.name) = selectorText.replace(/@(-webkit-){0,1}keyframes +/, ''); newRule.parentStyleSheet = parentStyleSheet; @@ -48,7 +49,7 @@ export default class CSSParser { parentRule = newRule; } else if (selectorText.startsWith('@media')) { const mediums = selectorText.replace('@media', '').split(','); - const newRule = new CSSMediaRule(); + const newRule = new CSSMediaRule(PropertySymbol.illegalConstructor, window); for (const medium of mediums) { newRule.media.appendMedium(medium.trim()); @@ -62,7 +63,7 @@ export default class CSSParser { selectorText.startsWith('@-webkit-container') ) { const conditionText = selectorText.replace(/@(-webkit-){0,1}container +/, ''); - const newRule = new CSSContainerRule(); + const newRule = new CSSContainerRule(PropertySymbol.illegalConstructor, window); (newRule.conditionText) = conditionText; newRule.parentStyleSheet = parentStyleSheet; @@ -73,7 +74,7 @@ export default class CSSParser { selectorText.startsWith('@-webkit-supports') ) { const conditionText = selectorText.replace(/@(-webkit-){0,1}supports +/, ''); - const newRule = new CSSSupportsRule(); + const newRule = new CSSSupportsRule(PropertySymbol.illegalConstructor, window); (newRule.conditionText) = conditionText; newRule.parentStyleSheet = parentStyleSheet; @@ -81,7 +82,7 @@ export default class CSSParser { parentRule = newRule; } else if (selectorText.startsWith('@font-face')) { const conditionText = selectorText.replace('@font-face', ''); - const newRule = new CSSFontFaceRule(); + const newRule = new CSSFontFaceRule(PropertySymbol.illegalConstructor, window); newRule[PropertySymbol.cssText] = conditionText; newRule.parentStyleSheet = parentStyleSheet; @@ -90,11 +91,11 @@ export default class CSSParser { } else if (selectorText.startsWith('@')) { // Unknown rule. // We will create a new rule to let it grab its content, but we will not add it to the cssRules array. - const newRule = new CSSRule(); + const newRule = new CSSRule(PropertySymbol.illegalConstructor, window); newRule.parentStyleSheet = parentStyleSheet; parentRule = newRule; } else if (parentRule && parentRule.type === CSSRule.KEYFRAMES_RULE) { - const newRule = new CSSKeyframeRule(); + const newRule = new CSSKeyframeRule(PropertySymbol.illegalConstructor, window); (newRule.keyText) = selectorText.trim(); newRule.parentStyleSheet = parentStyleSheet; newRule.parentRule = parentRule; @@ -108,7 +109,7 @@ export default class CSSParser { parentRule.type === CSSRule.SUPPORTS_RULE) ) { if (this.validateSelectorText(selectorText)) { - const newRule = new CSSStyleRule(); + const newRule = new CSSStyleRule(PropertySymbol.illegalConstructor, window); (newRule.selectorText) = selectorText; newRule.parentStyleSheet = parentStyleSheet; newRule.parentRule = parentRule; @@ -117,7 +118,7 @@ export default class CSSParser { } } else { if (this.validateSelectorText(selectorText)) { - const newRule = new CSSStyleRule(); + const newRule = new CSSStyleRule(PropertySymbol.illegalConstructor, window); (newRule.selectorText) = selectorText; newRule.parentStyleSheet = parentStyleSheet; newRule.parentRule = parentRule; diff --git a/packages/happy-dom/src/custom-element/CustomElementRegistry.ts b/packages/happy-dom/src/custom-element/CustomElementRegistry.ts index 8aa4a2698..2cfdf6889 100644 --- a/packages/happy-dom/src/custom-element/CustomElementRegistry.ts +++ b/packages/happy-dom/src/custom-element/CustomElementRegistry.ts @@ -11,7 +11,7 @@ export default class CustomElementRegistry { public [PropertySymbol.registry]: { [k: string]: { elementClass: typeof HTMLElement; extends: string }; } = {}; - public [PropertySymbol.registedClass]: Map = new Map(); + public [PropertySymbol.classRegistry]: Map = new Map(); public [PropertySymbol.callbacks]: Map void>> = new Map(); public [PropertySymbol.destroyed]: boolean = false; #window: BrowserWindow; @@ -57,7 +57,7 @@ export default class CustomElementRegistry { ); } - if (this[PropertySymbol.registedClass].has(elementClass)) { + if (this[PropertySymbol.classRegistry].has(elementClass)) { throw new this.#window.DOMException( "Failed to execute 'define' on 'CustomElementRegistry': this constructor has already been used with this registry" ); @@ -75,7 +75,7 @@ export default class CustomElementRegistry { elementClass, extends: options && options.extends ? options.extends.toLowerCase() : null }; - this[PropertySymbol.registedClass].set(elementClass, name); + this[PropertySymbol.classRegistry].set(elementClass, name); // ObservedAttributes should only be called once by CustomElementRegistry (see #117) elementClass[PropertySymbol.observedAttributes] = (elementClass.observedAttributes || []).map( @@ -152,7 +152,7 @@ export default class CustomElementRegistry { * @returns Found tag name or `null`. */ public getName(elementClass: typeof HTMLElement): string | null { - return this[PropertySymbol.registedClass].get(elementClass) || null; + return this[PropertySymbol.classRegistry].get(elementClass) || null; } /** @@ -168,7 +168,7 @@ export default class CustomElementRegistry { entity.elementClass.prototype[PropertySymbol.namespaceURI] = null; } this[PropertySymbol.registry] = {}; - this[PropertySymbol.registedClass] = new Map(); + this[PropertySymbol.classRegistry] = new Map(); this[PropertySymbol.callbacks] = new Map(); } diff --git a/packages/happy-dom/src/dom-parser/DOMParser.ts b/packages/happy-dom/src/dom-parser/DOMParser.ts index a176d0c6d..21a97c48e 100644 --- a/packages/happy-dom/src/dom-parser/DOMParser.ts +++ b/packages/happy-dom/src/dom-parser/DOMParser.ts @@ -106,7 +106,6 @@ export default class DOMParser { case 'text/html': return new window.HTMLDocument(); case 'image/svg+xml': - return new window.SVGDocument(); case 'text/xml': case 'application/xml': case 'application/xhtml+xml': diff --git a/packages/happy-dom/src/dom/DOMPoint.ts b/packages/happy-dom/src/dom/DOMPoint.ts new file mode 100644 index 000000000..74a81af34 --- /dev/null +++ b/packages/happy-dom/src/dom/DOMPoint.ts @@ -0,0 +1,81 @@ +import DOMPointReadOnly from './DOMPointReadOnly.js'; +import * as PropertySymbol from '../PropertySymbol.js'; + +/** + * DOM Point. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMPoint + */ +export default class DOMPoint extends DOMPointReadOnly { + /** + * Sets x. + * + * @param value X. + */ + public set x(value: number) { + this[PropertySymbol.x] = value; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): number { + return this[PropertySymbol.x]; + } + + /** + * Sets y. + * + * @param value Y. + */ + public set y(value: number) { + this[PropertySymbol.y] = value; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): number { + return this[PropertySymbol.y]; + } + + /** + * Sets z. + * + * @param value Z. + */ + public set z(value: number) { + this[PropertySymbol.z] = value; + } + + /** + * Returns z. + * + * @returns Z. + */ + public get z(): number { + return this[PropertySymbol.z]; + } + + /** + * Sets w. + * + * @param value W. + */ + public set w(value: number) { + this[PropertySymbol.w] = value; + } + + /** + * Returns w. + * + * @returns W. + */ + public get w(): number { + return this[PropertySymbol.w]; + } +} diff --git a/packages/happy-dom/src/dom/DOMPointReadOnly.ts b/packages/happy-dom/src/dom/DOMPointReadOnly.ts new file mode 100644 index 000000000..b51945d9a --- /dev/null +++ b/packages/happy-dom/src/dom/DOMPointReadOnly.ts @@ -0,0 +1,97 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import IDOMPointInit from './IDOMPointInit.js'; + +/** + * DOM Point Readonly. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMPointReadOnly + */ +export default class DOMPointReadOnly implements IDOMPointInit { + protected [PropertySymbol.x]: number = 0; + protected [PropertySymbol.y]: number = 0; + protected [PropertySymbol.z]: number = 0; + protected [PropertySymbol.w]: number = 1; + + /** + * Constructor. + * + * @param [x] X position. + * @param [y] Y position. + * @param [z] Width. + * @param [w] Height. + */ + constructor(x?: number | null, y?: number | null, z?: number | null, w?: number | null) { + this[PropertySymbol.x] = x !== undefined && x !== null ? Number(x) : 0; + this[PropertySymbol.y] = y !== undefined && y !== null ? Number(y) : 0; + this[PropertySymbol.z] = z !== undefined && z !== null ? Number(z) : 0; + this[PropertySymbol.w] = w !== undefined && w !== null ? Number(w) : 1; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): number { + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): number { + return this[PropertySymbol.y]; + } + + /** + * Returns z. + * + * @returns Z. + */ + public get z(): number { + return this[PropertySymbol.z]; + } + + /** + * Returns w. + * + * @returns W. + */ + public get w(): number { + return this[PropertySymbol.w]; + } + + /** + * Returns the JSON representation of the object. + * + * @returns JSON representation. + */ + public toJSON(): object { + return { + x: this.x, + y: this.y, + z: this.z, + w: this.w + }; + } + + /** + * Returns a new DOMPointReadOnly object. + * + * @param [otherPoint] Other point. + * @returns Cloned object. + */ + public static fromPoint(otherPoint?: IDOMPointInit): DOMPointReadOnly { + if (!otherPoint) { + return new this(); + } + return new this( + otherPoint.x ?? null, + otherPoint.y ?? null, + otherPoint.z ?? null, + otherPoint.w ?? null + ); + } +} diff --git a/packages/happy-dom/src/nodes/element/DOMRect.ts b/packages/happy-dom/src/dom/DOMRect.ts similarity index 62% rename from packages/happy-dom/src/nodes/element/DOMRect.ts rename to packages/happy-dom/src/dom/DOMRect.ts index 26bffb801..addc4f7de 100644 --- a/packages/happy-dom/src/nodes/element/DOMRect.ts +++ b/packages/happy-dom/src/dom/DOMRect.ts @@ -1,47 +1,91 @@ import DOMRectReadOnly from './DOMRectReadOnly.js'; -import * as PropertySymbol from '../../PropertySymbol.js'; +import * as PropertySymbol from '../PropertySymbol.js'; import IDOMRectInit from './IDOMRectInit.js'; -/* eslint-disable jsdoc/require-jsdoc */ - /** - * Bounding rect object. + * DOM Rect. * * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMRect */ export default class DOMRect extends DOMRectReadOnly { + /** + * Sets x. + * + * @param value X. + */ public set x(value: number) { this[PropertySymbol.x] = value; } + /** + * Returns x. + * + * @returns X. + */ public get x(): number { return this[PropertySymbol.x]; } + /** + * Sets y. + * + * @param value Y. + */ public set y(value: number) { this[PropertySymbol.y] = value; } + /** + * Returns y. + * + * @returns Y. + */ public get y(): number { return this[PropertySymbol.y]; } + /** + * Sets width. + * + * @param value Width. + */ public set width(value: number) { this[PropertySymbol.width] = value; } + /** + * Returns width. + * + * @returns Width. + */ public get width(): number { return this[PropertySymbol.width]; } + /** + * Sets height. + * + * @param value Height. + */ public set height(value: number) { this[PropertySymbol.height] = value; } + /** + * Returns height. + * + * @returns Height. + */ public get height(): number { return this[PropertySymbol.height]; } + /** + * Returns a new DOMRect object. + * + * @param other + * @returns Cloned object. + */ public static fromRect(other: IDOMRectInit): DOMRect { return new DOMRect(other.x, other.y, other.width, other.height); } diff --git a/packages/happy-dom/src/dom/DOMRectList.ts b/packages/happy-dom/src/dom/DOMRectList.ts new file mode 100644 index 000000000..16f2555e1 --- /dev/null +++ b/packages/happy-dom/src/dom/DOMRectList.ts @@ -0,0 +1,34 @@ +import DOMRect from './DOMRect.js'; +import * as PropertySymbol from '../PropertySymbol.js'; + +/** + * DOM Rect List. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMRectList + */ +export default class DOMRectList extends Array { + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + */ + constructor(illegalConstructorSymbol: symbol) { + super(); + // "illegalConstructorSymbol" can be "1" when calling the "splice()" method + if ( + (illegalConstructorSymbol) !== 1 && + illegalConstructorSymbol !== PropertySymbol.illegalConstructor + ) { + throw new TypeError('Illegal constructor'); + } + } + + /** + * Returns item by index. + * + * @param index Index. + */ + public item(index: number): DOMRect { + return this[index] ?? null; + } +} diff --git a/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts b/packages/happy-dom/src/dom/DOMRectReadOnly.ts similarity index 72% rename from packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts rename to packages/happy-dom/src/dom/DOMRectReadOnly.ts index 5ca15808b..6577ea9e3 100644 --- a/packages/happy-dom/src/nodes/element/DOMRectReadOnly.ts +++ b/packages/happy-dom/src/dom/DOMRectReadOnly.ts @@ -1,12 +1,10 @@ -import * as PropertySymbol from '../../PropertySymbol.js'; +import * as PropertySymbol from '../PropertySymbol.js'; import IDOMRectInit from './IDOMRectInit.js'; -/* eslint-disable jsdoc/require-jsdoc */ - /** - * Bounding rect readonly object. + * DOM Rect Readonly. * - * @see https://drafts.fxtf.org/geometry/#DOMRect + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly */ export default class DOMRectReadOnly implements IDOMRectInit { protected [PropertySymbol.x]: number = 0; @@ -29,38 +27,83 @@ export default class DOMRectReadOnly implements IDOMRectInit { this[PropertySymbol.height] = height !== undefined && height !== null ? Number(height) : 0; } + /** + * Returns x. + * + * @returns X. + */ public get x(): number { return this[PropertySymbol.x]; } + /** + * Returns y. + * + * @returns Y. + */ public get y(): number { return this[PropertySymbol.y]; } + /** + * Returns width. + * + * @returns Width. + */ public get width(): number { return this[PropertySymbol.width]; } + /** + * Returns height. + * + * @returns Height. + */ public get height(): number { return this[PropertySymbol.height]; } + /** + * Returns top. + * + * @returns Top. + */ public get top(): number { return Math.min(this[PropertySymbol.y], this[PropertySymbol.y] + this[PropertySymbol.height]); } + /** + * Returns right. + * + * @returns Right. + */ public get right(): number { return Math.max(this[PropertySymbol.x], this[PropertySymbol.x] + this[PropertySymbol.width]); } + /** + * Returns bottom. + * + * @returns Bottom. + */ public get bottom(): number { return Math.max(this[PropertySymbol.y], this[PropertySymbol.y] + this[PropertySymbol.height]); } + /** + * Returns left. + * + * @returns Left. + */ public get left(): number { return Math.min(this[PropertySymbol.x], this[PropertySymbol.x] + this[PropertySymbol.width]); } + /** + * Returns the JSON representation of the object. + * + * @returns JSON representation. + */ public toJSON(): object { return { x: this.x, @@ -74,6 +117,12 @@ export default class DOMRectReadOnly implements IDOMRectInit { }; } + /** + * Returns a new DOMRectReadOnly object. + * + * @param other + * @returns Cloned object. + */ public static fromRect(other: IDOMRectInit): DOMRectReadOnly { return new DOMRectReadOnly(other.x, other.y, other.width, other.height); } diff --git a/packages/happy-dom/src/nodes/element/DOMStringMap.ts b/packages/happy-dom/src/dom/DOMStringMap.ts similarity index 88% rename from packages/happy-dom/src/nodes/element/DOMStringMap.ts rename to packages/happy-dom/src/dom/DOMStringMap.ts index 298ebbfc7..0790b3f8d 100644 --- a/packages/happy-dom/src/nodes/element/DOMStringMap.ts +++ b/packages/happy-dom/src/dom/DOMStringMap.ts @@ -1,5 +1,5 @@ -import Element from './Element.js'; -import * as PropertySymbol from '../../PropertySymbol.js'; +import Element from '../nodes/element/Element.js'; +import * as PropertySymbol from '../PropertySymbol.js'; import DOMStringMapUtility from './DOMStringMapUtility.js'; /** @@ -14,9 +14,14 @@ export default class DOMStringMap { /** * Constructor. * + * @param illegalConstructorSymbol Illegal constructor symbol. * @param element Element. */ - constructor(element: Element) { + constructor(illegalConstructorSymbol: symbol, element: Element) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + // Documentation for Proxy: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy return new Proxy(this, { diff --git a/packages/happy-dom/src/nodes/element/DOMStringMapUtility.ts b/packages/happy-dom/src/dom/DOMStringMapUtility.ts similarity index 100% rename from packages/happy-dom/src/nodes/element/DOMStringMapUtility.ts rename to packages/happy-dom/src/dom/DOMStringMapUtility.ts diff --git a/packages/happy-dom/src/nodes/element/DOMTokenList.ts b/packages/happy-dom/src/dom/DOMTokenList.ts similarity index 88% rename from packages/happy-dom/src/nodes/element/DOMTokenList.ts rename to packages/happy-dom/src/dom/DOMTokenList.ts index 009cf631d..9d3a15513 100644 --- a/packages/happy-dom/src/nodes/element/DOMTokenList.ts +++ b/packages/happy-dom/src/dom/DOMTokenList.ts @@ -1,6 +1,6 @@ -import Element from './Element.js'; -import * as PropertySymbol from '../../PropertySymbol.js'; -import ClassMethodBinder from '../../ClassMethodBinder.js'; +import ClassMethodBinder from '../ClassMethodBinder.js'; +import Element from '../nodes/element/Element.js'; +import * as PropertySymbol from '../PropertySymbol.js'; const ATTRIBUTE_SPLIT_REGEXP = /[\t\f\n\r ]+/; @@ -23,17 +23,19 @@ export default class DOMTokenList { /** * Constructor. * + * @param illegalConstructorSymbol Illegal constructor symbol. * @param ownerElement Owner element. * @param attributeName Attribute name. */ - constructor(ownerElement: Element, attributeName) { + constructor(illegalConstructorSymbol: symbol, ownerElement: Element, attributeName: string) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + this[PropertySymbol.ownerElement] = ownerElement; this[PropertySymbol.attributeName] = attributeName; - ClassMethodBinder.bindMethods(this, [DOMTokenList], { - bindSymbols: true, - forwardToPrototype: true - }); + const methodBinder = new ClassMethodBinder(this, [DOMTokenList]); return new Proxy(this, { get: (target, property) => { @@ -41,6 +43,7 @@ export default class DOMTokenList { return target[PropertySymbol.getTokenList]().length; } if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } const index = Number(property); @@ -49,6 +52,7 @@ export default class DOMTokenList { } }, set(target, property, newValue): boolean { + methodBinder.bind(property); if (typeof property === 'symbol') { target[property] = newValue; return true; @@ -78,10 +82,16 @@ export default class DOMTokenList { return true; } + if (typeof property === 'symbol') { + return false; + } + const index = Number(property); return !isNaN(index) && index >= 0 && index < target[PropertySymbol.getTokenList]().length; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + if (property in target) { Object.defineProperty(target, property, descriptor); return true; @@ -99,7 +109,7 @@ export default class DOMTokenList { if (!isNaN(index) && items[index]) { return { - value: target[PropertySymbol.getTokenList]()[index], + value: items[index], writable: false, enumerable: true, configurable: true @@ -148,8 +158,12 @@ export default class DOMTokenList { * */ public item(index: number | string): string { const items = this[PropertySymbol.getTokenList](); - index = typeof index === 'number' ? index : 0; - return index >= 0 && items[index] ? items[index] : null; + if (typeof index === 'number') { + return items[index] ? items[index] : null; + } + index = Number(index); + index = isNaN(index) ? 0 : index; + return items[index] ? items[index] : null; } /** @@ -313,7 +327,7 @@ export default class DOMTokenList { const trimmed = attributeValue.trim(); if (trimmed) { - for (const item of attributeValue.trim().split(ATTRIBUTE_SPLIT_REGEXP)) { + for (const item of trimmed.split(ATTRIBUTE_SPLIT_REGEXP)) { if (!items.includes(item)) { items.push(item); } diff --git a/packages/happy-dom/src/dom/IDOMPointInit.ts b/packages/happy-dom/src/dom/IDOMPointInit.ts new file mode 100644 index 000000000..98ddcbffa --- /dev/null +++ b/packages/happy-dom/src/dom/IDOMPointInit.ts @@ -0,0 +1,6 @@ +export default interface IDOMPointInit { + readonly x?: number; + readonly y?: number; + readonly z?: number; + readonly w?: number; +} diff --git a/packages/happy-dom/src/nodes/element/IDOMRectInit.ts b/packages/happy-dom/src/dom/IDOMRectInit.ts similarity index 100% rename from packages/happy-dom/src/nodes/element/IDOMRectInit.ts rename to packages/happy-dom/src/dom/IDOMRectInit.ts diff --git a/packages/happy-dom/src/dom/dom-matrix/DOMMatrix.ts b/packages/happy-dom/src/dom/dom-matrix/DOMMatrix.ts new file mode 100644 index 000000000..ef22a058f --- /dev/null +++ b/packages/happy-dom/src/dom/dom-matrix/DOMMatrix.ts @@ -0,0 +1,491 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import DOMMatrixReadOnly from './DOMMatrixReadOnly.js'; +import IDOMMatrixCompatibleObject from './IDOMMatrixCompatibleObject.js'; +import TDOMMatrixInit from './TDOMMatrixInit.js'; + +/** + * DOM Matrix. + * + * Based on: + * - https://github.com/thednp/dommatrix/tree/master + * - https://github.com/trusktr/geometry-interfaces + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix + */ +export default class DOMMatrix extends DOMMatrixReadOnly { + /** + * Returns the `a` value of the matrix. + */ + public get a(): number { + return this[PropertySymbol.m11]; + } + + /** + * Sets the `a` value of the matrix. + */ + public set a(value: number) { + this[PropertySymbol.m11] = value; + } + + /** + * Returns the `b` value of the matrix. + */ + public get b(): number { + return this[PropertySymbol.m12]; + } + + /** + * Sets the `b` value of the matrix. + */ + public set b(value: number) { + this[PropertySymbol.m12] = value; + } + + /** + * Returns the `c` value of the matrix. + */ + public get c(): number { + return this[PropertySymbol.m21]; + } + + /** + * Sets the `c` value of the matrix. + */ + public set c(value: number) { + this[PropertySymbol.m21] = value; + } + + /** + * Returns the `d` value of the matrix. + */ + public get d(): number { + return this[PropertySymbol.m22]; + } + + /** + * Sets the `d` value of the matrix. + */ + public set d(value: number) { + this[PropertySymbol.m22] = value; + } + + /** + * Returns the `e` value of the matrix. + */ + public get e(): number { + return this[PropertySymbol.m41]; + } + + /** + * Sets the `e` value of the matrix. + */ + public set e(value: number) { + this[PropertySymbol.m41] = value; + } + + /** + * Returns the `f` value of the matrix. + */ + public get f(): number { + return this[PropertySymbol.m42]; + } + + /** + * Sets the `f` value of the matrix. + */ + public set f(value: number) { + this[PropertySymbol.m42] = value; + } + + /** + * Returns the `m11` value of the matrix. + */ + public get m11(): number { + return this[PropertySymbol.m11]; + } + + /** + * Sets the `m11` value of the matrix. + */ + public set m11(value: number) { + this[PropertySymbol.m11] = value; + } + + /** + * Returns the `m12` value of the matrix. + */ + public get m12(): number { + return this[PropertySymbol.m12]; + } + + /** + * Sets the `m12` value of the matrix. + */ + public set m12(value: number) { + this[PropertySymbol.m12] = value; + } + + /** + * Returns the `m13` value of the matrix. + */ + public get m13(): number { + return this[PropertySymbol.m13]; + } + + /** + * Sets the `m13` value of the matrix. + */ + public set m13(value: number) { + this[PropertySymbol.m13] = value; + } + + /** + * Returns the `m14` value of the matrix. + */ + public get m14(): number { + return this[PropertySymbol.m14]; + } + + /** + * Sets the `m14` value of the matrix. + */ + public set m14(value: number) { + this[PropertySymbol.m14] = value; + } + + /** + * Returns the `m21` value of the matrix. + */ + public get m21(): number { + return this[PropertySymbol.m21]; + } + + /** + * Sets the `m21` value of the matrix. + */ + public set m21(value: number) { + this[PropertySymbol.m21] = value; + } + + /** + * Returns the `m22` value of the matrix. + */ + public get m22(): number { + return this[PropertySymbol.m22]; + } + + /** + * Sets the `m22` value of the matrix. + */ + public set m22(value: number) { + this[PropertySymbol.m22] = value; + } + + /** + * Returns the `m23` value of the matrix. + */ + public get m23(): number { + return this[PropertySymbol.m23]; + } + + /** + * Sets the `m23` value of the matrix. + */ + public set m23(value: number) { + this[PropertySymbol.m23] = value; + } + + /** + * Returns the `m24` value of the matrix. + */ + public get m24(): number { + return this[PropertySymbol.m24]; + } + + /** + * Sets the `m24` value of the matrix. + */ + public set m24(value: number) { + this[PropertySymbol.m24] = value; + } + + /** + * Returns the `m31` value of the matrix. + */ + public get m31(): number { + return this[PropertySymbol.m31]; + } + + /** + * Sets the `m31` value of the matrix. + */ + public set m31(value: number) { + this[PropertySymbol.m31] = value; + } + + /** + * Returns the `m32` value of the matrix. + */ + public get m32(): number { + return this[PropertySymbol.m32]; + } + + /** + * Sets the `m32` value of the matrix. + */ + public set m32(value: number) { + this[PropertySymbol.m32] = value; + } + + /** + * Returns the `m33` value of the matrix. + */ + public get m33(): number { + return this[PropertySymbol.m33]; + } + + /** + * Sets the `m33` value of the matrix. + */ + public set m33(value: number) { + this[PropertySymbol.m33] = value; + } + + /** + * Returns the `m34` value of the matrix. + */ + public get m34(): number { + return this[PropertySymbol.m34]; + } + + /** + * Sets the `m34` value of the matrix. + */ + public set m34(value: number) { + this[PropertySymbol.m34] = value; + } + + /** + * Returns the `m41` value of the matrix. + */ + public get m41(): number { + return this[PropertySymbol.m41]; + } + + /** + * Sets the `m41` value of the matrix. + */ + public set m41(value: number) { + this[PropertySymbol.m41] = value; + } + + /** + * Returns the `m42` value of the matrix. + */ + public get m42(): number { + return this[PropertySymbol.m42]; + } + + /** + * Sets the `m42` value of the matrix. + */ + public set m42(value: number) { + this[PropertySymbol.m42] = value; + } + + /** + * Returns the `m43` value of the matrix. + */ + public get m43(): number { + return this[PropertySymbol.m43]; + } + + /** + * Sets the `m43` value of the matrix. + */ + public set m43(value: number) { + this[PropertySymbol.m43] = value; + } + + /** + * Returns the `m44` value of the matrix. + */ + public get m44(): number { + return this[PropertySymbol.m44]; + } + + /** + * Sets the `m44` value of the matrix. + */ + public set m44(value: number) { + this[PropertySymbol.m44] = value; + } + + /** + * The `setMatrixValue` method replaces the existing matrix with one computed in the browser (e.g.`matrix(1,0.25,-0.25,1,0,0)`). + * + * @param source A `DOMMatrix`, `Float32Array`, `Float64Array`, `Array`, or DOMMatrix compatible object to set the matrix values from. + * @returns Self. + */ + public setMatrixValue(source?: TDOMMatrixInit): DOMMatrix { + this[PropertySymbol.setMatrixValue](source); + return this; + } + + /** + * Sets self to be multiplied by the passed matrix. + * + * @param secondMatrix DOMMatrix + * @returns Self. + */ + public multiplySelf(secondMatrix: IDOMMatrixCompatibleObject): DOMMatrix { + this[PropertySymbol.multiplySelf](secondMatrix); + return this; + } + + /** + * Sets self to multiplied by a translation matrix containing the passed values. + * + * @param [x=0] X component of the translation value. + * @param [y=0] Y component of the translation value. + * @param [z=0] Z component of the translation value. + * @returns Self. + */ + public translateSelf(x: number = 0, y: number = 0, z: number = 0): DOMMatrix { + this[PropertySymbol.translateSelf](x, y, z); + return this; + } + + /** + * Sets self to be multiplied by a scale matrix containing the passed values. + * + * @param [scaleX] X-Axis scale. + * @param [scaleY] Y-Axis scale. + * @param [scaleZ] Z-Axis scale. + * @param [originX] X-Axis scale. + * @param [originY] Y-Axis scale. + * @param [originZ] Z-Axis scale. + * @returns Self. + */ + public scaleSelf( + scaleX?: number, + scaleY?: number, + scaleZ = 1, + originX = 0, + originY = 0, + originZ = 0 + ): DOMMatrix { + this[PropertySymbol.scaleSelf](scaleX, scaleY, scaleZ, originX, originY, originZ); + return this; + } + + /** + * Sets self to be multiplied by a scale matrix containing the passed values. + * + * @param [scale] The scale factor. + * @param [originX] X-Axis scale. + * @param [originY] Y-Axis scale. + * @param [originZ] Z-Axis scale. + * @returns Self. + */ + public scale3dSelf(scale = 1, originX = 0, originY = 0, originZ = 0): DOMMatrix { + this[PropertySymbol.scale3dSelf](scale, originX, originY, originZ); + return this; + } + + /** + * Sets self to be multiplied by a scale matrix containing the passed values. + * + * @param [scaleX] X-Axis scale. + * @param [scaleY] Y-Axis scale. + * @returns Self. + */ + public scaleNonUniformSelf(scaleX = 1, scaleY = 1): DOMMatrix { + this[PropertySymbol.scaleNonUniformSelf](scaleX, scaleY); + return this; + } + + /** + * Sets self to be multiplied by a rotation matrix with the given axis and `angle`. + * + * @param [x] The X component of the axis vector. + * @param [y] The Y component of the axis vector. + * @param [z] The Z component of the axis vector. + * @param [angle] Angle of rotation about the axis vector, in degrees. + * @returns Self. + */ + public rotateAxisAngleSelf(x = 0, y = 0, z = 0, angle = 0): DOMMatrixReadOnly { + this[PropertySymbol.rotateAxisAngleSelf](x, y, z, angle); + return this; + } + + /** + * Set self to be multiplied by each of 3 rotation matrices about the major axes, first X, then Y, then Z. + * + * @param [x] X component of the rotation, or Z if Y and Z are null. + * @param [y] Y component of the rotation value. + * @param [z] Z component of the rotation value. + * @returns Self. + */ + public rotateSelf(x = 0, y?: number, z?: number): DOMMatrixReadOnly { + this[PropertySymbol.rotateSelf](x, y, z); + return this; + } + + /** + * Sets self to be multiplied by a skew matrix along the X axis by the given angle. + * + * @param [x] X-Axis skew. + * @param [y] Y-Axis skew. + */ + public rotateFromVectorSelf(x = 0, y = 0): DOMMatrixReadOnly { + this[PropertySymbol.rotateFromVectorSelf](x, y); + return this; + } + + /** + * Set self to be specified as a skew transformation along X-Axis by the given angle. + * + * @param angle Angle amount in degrees to skew. + * @returns Self. + */ + public skewXSelf(angle: number): DOMMatrixReadOnly { + this[PropertySymbol.skewXSelf](angle); + return this; + } + + /** + * Set self to be specified as a skew transformation along Y-Axis by the given angle. + * + * @param angle Angle amount in degrees to skew. + * @returns Self. + */ + public skewYSelf(angle: number): DOMMatrixReadOnly { + this[PropertySymbol.skewYSelf](angle); + return this; + } + + /** + * Set self to be specified as matrix flipped on X-axis. + */ + public flipXSelf(): DOMMatrixReadOnly { + this[PropertySymbol.flipXSelf](); + return this; + } + + /** + * Set self to be specified as matrix flipped on Y-axis. + */ + public flipYSelf(): DOMMatrixReadOnly { + this[PropertySymbol.flipYSelf](); + return this; + } + + /** + * Set self to be specified as matrix inverted. + */ + public invertSelf(): DOMMatrixReadOnly { + this[PropertySymbol.invertSelf](); + return this; + } +} diff --git a/packages/happy-dom/src/dom/dom-matrix/DOMMatrixReadOnly.ts b/packages/happy-dom/src/dom/dom-matrix/DOMMatrixReadOnly.ts new file mode 100644 index 000000000..fa6b68f12 --- /dev/null +++ b/packages/happy-dom/src/dom/dom-matrix/DOMMatrixReadOnly.ts @@ -0,0 +1,1594 @@ +import DOMPoint from '../DOMPoint.js'; +import IDOMPointInit from '../IDOMPointInit.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import TDOMMatrixInit from './TDOMMatrixInit.js'; +import TDOMMatrix2DArray from './TDOMMatrix2DArray.js'; +import TDOMMatrix3DArray from './TDOMMatrix3DArray.js'; +import IDOMMatrixJSON from './IDOMMatrixJSON.js'; +import IDOMMatrixCompatibleObject from './IDOMMatrixCompatibleObject.js'; + +const DEFAULT_MATRIX_JSON: IDOMMatrixJSON = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0, + m11: 1, + m12: 0, + m13: 0, + m14: 0, + m21: 0, + m22: 1, + m23: 0, + m24: 0, + m31: 0, + m32: 0, + m33: 1, + m34: 0, + m41: 0, + m42: 0, + m43: 0, + m44: 1, + is2D: true, + isIdentity: true +}; + +const TRANSFORM_REGEXP = /([a-zA-Z0-9]+)\(([^)]+)\)/gm; +const TRANSFORM_PARAMETER_SPLIT_REGEXP = /[\s,]+/; + +/** + * DOM Matrix. + * + * Based on: + * - https://github.com/trusktr/geometry-interfaces + * - https://github.com/thednp/dommatrix/tree/master + * - https://github.com/jarek-foksa/geometry-polyfill/blob/master/geometry-polyfill.js + * - https://github.com/Automattic/node-canvas/blob/master/lib/DOMMatrix.js + * + * + * 3D Matrix: + * _________________________ + * | m11 | m21 | m31 | m41 | + * | m12 | m22 | m32 | m42 | + * | m13 | m23 | m33 | m43 | + * | m14 | m24 | m34 | m44 | + * -------------------------̣ + * + * 2D Matrix: + * _________________________ + * | m11 | m21 | 0 | m41 | + * | m12 | m22 | 0 | m42 | + * | 0 | 0 | 1 | 0 | + * | 0 | 0 | 0 | 1 | + * ------------------------- + * + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrixReadOnly + */ +export default class DOMMatrixReadOnly { + public [PropertySymbol.m11]: number = 1; + public [PropertySymbol.m12]: number = 0; + public [PropertySymbol.m13]: number = 0; + public [PropertySymbol.m14]: number = 0; + public [PropertySymbol.m21]: number = 0; + public [PropertySymbol.m22]: number = 1; + public [PropertySymbol.m23]: number = 0; + public [PropertySymbol.m24]: number = 0; + public [PropertySymbol.m31]: number = 0; + public [PropertySymbol.m32]: number = 0; + public [PropertySymbol.m33]: number = 1; + public [PropertySymbol.m34]: number = 0; + public [PropertySymbol.m41]: number = 0; + public [PropertySymbol.m42]: number = 0; + public [PropertySymbol.m43]: number = 0; + public [PropertySymbol.m44]: number = 1; + + /** + * Constructor. + * + * @param init Init parameter. + */ + constructor(init?: TDOMMatrixInit) { + if (init) { + this[PropertySymbol.setMatrixValue](init); + } + } + + /** + * Returns the `a` value of the matrix. + */ + public get a(): number { + return this[PropertySymbol.m11]; + } + + /** + * Returns the `b` value of the matrix. + */ + public get b(): number { + return this[PropertySymbol.m12]; + } + + /** + * Returns the `c` value of the matrix. + */ + public get c(): number { + return this[PropertySymbol.m21]; + } + + /** + * Returns the `d` value of the matrix. + */ + public get d(): number { + return this[PropertySymbol.m22]; + } + + /** + * Returns the `e` value of the matrix. + */ + public get e(): number { + return this[PropertySymbol.m41]; + } + + /** + * Returns the `f` value of the matrix. + */ + public get f(): number { + return this[PropertySymbol.m42]; + } + + /** + * Returns the `m11` value of the matrix. + */ + public get m11(): number { + return this[PropertySymbol.m11]; + } + + /** + * Returns the `m12` value of the matrix. + */ + public get m12(): number { + return this[PropertySymbol.m12]; + } + + /** + * Returns the `m13` value of the matrix. + */ + public get m13(): number { + return this[PropertySymbol.m13]; + } + + /** + * Returns the `m14` value of the matrix. + */ + public get m14(): number { + return this[PropertySymbol.m14]; + } + + /** + * Returns the `m21` value of the matrix. + */ + public get m21(): number { + return this[PropertySymbol.m21]; + } + + /** + * Returns the `m22` value of the matrix. + */ + public get m22(): number { + return this[PropertySymbol.m22]; + } + + /** + * Returns the `m23` value of the matrix. + */ + public get m23(): number { + return this[PropertySymbol.m23]; + } + + /** + * Returns the `m24` value of the matrix. + */ + public get m24(): number { + return this[PropertySymbol.m24]; + } + + /** + * Returns the `m31` value of the matrix. + */ + public get m31(): number { + return this[PropertySymbol.m31]; + } + + /** + * Returns the `m32` value of the matrix. + */ + public get m32(): number { + return this[PropertySymbol.m32]; + } + + /** + * Returns the `m33` value of the matrix. + */ + public get m33(): number { + return this[PropertySymbol.m33]; + } + + /** + * Returns the `m34` value of the matrix. + */ + public get m34(): number { + return this[PropertySymbol.m34]; + } + + /** + * Returns the `m41` value of the matrix. + */ + public get m41(): number { + return this[PropertySymbol.m41]; + } + + /** + * Returns the `m42` value of the matrix. + */ + public get m42(): number { + return this[PropertySymbol.m42]; + } + + /** + * Returns the `m43` value of the matrix. + */ + public get m43(): number { + return this[PropertySymbol.m43]; + } + + /** + * Returns the `m44` value of the matrix. + */ + public get m44(): number { + return this[PropertySymbol.m44]; + } + + /** + * A `Boolean` whose value is `true` if the matrix is the identity matrix. + * + * The identity matrix is one in which every value is 0 except those on the main diagonal from top-left to bottom-right corner (in other words, where the offsets in each direction are equal). + * + * @returns "true" if the matrix is the identity matrix. + */ + public get isIdentity(): boolean { + return ( + this[PropertySymbol.m11] === 1 && + this[PropertySymbol.m12] === 0 && + this[PropertySymbol.m13] === 0 && + this[PropertySymbol.m14] === 0 && + this[PropertySymbol.m21] === 0 && + this[PropertySymbol.m22] === 1 && + this[PropertySymbol.m23] === 0 && + this[PropertySymbol.m24] === 0 && + this[PropertySymbol.m31] === 0 && + this[PropertySymbol.m32] === 0 && + this[PropertySymbol.m33] === 1 && + this[PropertySymbol.m34] === 0 && + this[PropertySymbol.m41] === 0 && + this[PropertySymbol.m42] === 0 && + this[PropertySymbol.m43] === 0 && + this[PropertySymbol.m44] === 1 + ); + } + + /** + * A `Boolean` flag whose value is `true` if the matrix is a 2D matrix and `false` if the matrix is 3D. + * + * @returns "true" if the matrix is a 2D matrix. + */ + public get is2D(): boolean { + return ( + this[PropertySymbol.m31] === 0 && + this[PropertySymbol.m32] === 0 && + this[PropertySymbol.m33] === 1 && + this[PropertySymbol.m34] === 0 && + this[PropertySymbol.m43] === 0 && + this[PropertySymbol.m44] === 1 + ); + } + + /** + * Returns a *Float32Array* containing elements which comprise the matrix. + * + * The method can return either the 16 elements or the 6 elements depending on the value of the `is2D` parameter. + * + * @param [is2D] Set to `true` to return a 2D matrix. + * @returns An *Array* representation of the matrix. + */ + public toFloat32Array(is2D?: boolean): Float32Array { + return Float32Array.from(this[PropertySymbol.toArray](is2D)); + } + + /** + * Returns a *Float64Array* containing elements which comprise the matrix. + * + * The method can return either the 16 elements or the 6 elements depending on the value of the `is2D` parameter. + * + * @param [is2D] Set to `true` to return a 2D matrix. + * @returns An *Array* representation of the matrix + */ + public toFloat64Array(is2D?: boolean): Float64Array { + return Float64Array.from(this[PropertySymbol.toArray](is2D)); + } + + /** + * Returns a string representation of the matrix in `CSS` matrix syntax, using the appropriate `CSS` matrix notation. + * + * Examples: + * - `matrix3d(m11, m12, m13, m14, m21, ...)` + * - `matrix(a, b, c, d, e, f)` + * + * @returns A string representation of the matrix. + */ + public toString(): string { + const is2D = this.is2D; + const values = this[PropertySymbol.toArray](is2D).join(', '); + const type = is2D ? 'matrix' : 'matrix3d'; + return `${type}(${values})`; + } + + /** + * Returns an Object that can be serialized to a JSON string. + * + * The result can be used as a second parameter for the `fromMatrix` static method to load values into another matrix instance. + * + * @returns An *Object* with matrix values. + */ + public toJSON(): IDOMMatrixJSON { + const { is2D, isIdentity } = this; + return { + m11: this[PropertySymbol.m11], + m12: this[PropertySymbol.m12], + m13: this[PropertySymbol.m13], + m14: this[PropertySymbol.m14], + m21: this[PropertySymbol.m21], + m22: this[PropertySymbol.m22], + m23: this[PropertySymbol.m23], + m24: this[PropertySymbol.m24], + m31: this[PropertySymbol.m31], + m32: this[PropertySymbol.m32], + m33: this[PropertySymbol.m33], + m34: this[PropertySymbol.m34], + m41: this[PropertySymbol.m41], + m42: this[PropertySymbol.m42], + m43: this[PropertySymbol.m43], + m44: this[PropertySymbol.m44], + a: this[PropertySymbol.m11], + b: this[PropertySymbol.m12], + c: this[PropertySymbol.m21], + d: this[PropertySymbol.m22], + e: this[PropertySymbol.m41], + f: this[PropertySymbol.m42], + is2D, + isIdentity + }; + } + + /** + * Returns a new DOMMatrix instance which is the result of this matrix multiplied by the passed matrix. + * + * @param secondMatrix DOMMatrix + * @returns A new DOMMatrix object. + */ + public multiply(secondMatrix: IDOMMatrixCompatibleObject): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.multiplySelf](secondMatrix); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix post multiplied by a translation matrix containing the passed values. + * + * @param [x=0] X component of the translation value. + * @param [y=0] Y component of the translation value. + * @param [z=0] Z component of the translation value. + * @returns The resulted matrix + */ + public translate(x: number = 0, y: number = 0, z: number = 0): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.translateSelf](x, y, z); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix post multiplied by a scale 2D matrix containing the passed values. + * + * @param [scaleX] X-Axis scale. + * @param [scaleY] Y-Axis scale. + * @param [scaleZ] Z-Axis scale. + * @param [originX] X-Axis scale. + * @param [originY] Y-Axis scale. + * @param [originZ] Z-Axis scale. + * @returns The resulted matrix + */ + public scale( + scaleX?: number, + scaleY?: number, + scaleZ = 1, + originX = 0, + originY = 0, + originZ = 0 + ): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.scaleSelf](scaleX, scaleY, scaleZ, originX, originY, originZ); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix post multiplied by a scale 3D matrix containing the passed values. + * + * @param [scale] The scale factor. + * @param [originX] X-Axis scale. + * @param [originY] Y-Axis scale. + * @param [originZ] Z-Axis scale. + * @returns The resulted matrix + */ + public scale3d(scale = 1, originX = 0, originY = 0, originZ = 0): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.scale3dSelf](scale, originX, originY, originZ); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix post multiplied by a scale 3D matrix containing the passed values. + * + * @param [scaleX] X-Axis scale. + * @param [scaleY] Y-Axis scale. + * @returns The resulted matrix + */ + public scaleNonUniform(scaleX = 1, scaleY = 1): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.scaleNonUniformSelf](scaleX, scaleY); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix post multiplied by a rotation matrix with the given axis and `angle`. + * + * @param [x] The X component of the axis vector. + * @param [y] The Y component of the axis vector. + * @param [z] The Z component of the axis vector. + * @param [angle] Angle of rotation about the axis vector, in degrees. + * @returns The resulted matrix + */ + public rotateAxisAngle(x = 0, y = 0, z = 0, angle = 0): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.rotateAxisAngleSelf](x, y, z, angle); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix post multiplied by each of 3 rotation matrices about the major axes, first X, then Y, then Z. + * + * @param [x] X component of the rotation, or Z if Y and Z are null. + * @param [y] Y component of the rotation value. + * @param [z] Z component of the rotation value. + * @returns The resulted matrix + */ + public rotate(x = 0, y?: number, z?: number): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.rotateSelf](x, y, z); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix post multiplied by a rotation matrix with the angle between the specified vector and (1, 0). + * + * @param [x] X-Axis skew. + * @param [y] Y-Axis skew. + */ + public rotateFromVector(x = 0, y = 0): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.rotateFromVectorSelf](x, y); + return matrix; + } + + /** + * Returns a new DOMMatrix instance that specifies a skew transformation along X-Axis by the given angle. + * + * @param angle Angle amount in degrees to skew. + * @returns The resulted matrix + */ + public skewX(angle: number): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.skewXSelf](angle); + return matrix; + } + + /** + * Returns a new DOMMatrix instance that specifies a skew transformation along Y-Axis by the given angle. + * + * @param angle Angle amount in degrees to skew. + * @returns The resulted matrix + */ + public skewY(angle: number): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.skewYSelf](angle); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix flipped on X-axis. + */ + public flipX(): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.flipXSelf](); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix flipped on Y-axis. + */ + public flipY(): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.flipYSelf](); + return matrix; + } + + /** + * Returns a new DOMMatrix instance which is this matrix inversed. + */ + public inverse(): DOMMatrixReadOnly { + const matrix = new (this.constructor)(this); + matrix[PropertySymbol.invertSelf](); + return matrix; + } + + /** + * Returns a new DOMPoint instance with the vector transformed using the matrix. + * + * @param domPoint DOM point compatible object. + * @returns A new DOMPoint object. + */ + public transformPoint(domPoint: IDOMPointInit): DOMPoint { + const xPoint = domPoint.x ?? 0; + const yPoint = domPoint.y ?? 0; + const zPoint = domPoint.z ?? 0; + const wPoint = domPoint.w ?? 1; + + const x = + this[PropertySymbol.m11] * xPoint + + this[PropertySymbol.m21] * yPoint + + this[PropertySymbol.m31] * zPoint + + this[PropertySymbol.m41] * wPoint; + const y = + this[PropertySymbol.m12] * xPoint + + this[PropertySymbol.m22] * yPoint + + this[PropertySymbol.m32] * zPoint + + this[PropertySymbol.m42] * wPoint; + const z = + this[PropertySymbol.m13] * xPoint + + this[PropertySymbol.m23] * yPoint + + this[PropertySymbol.m33] * zPoint + + this[PropertySymbol.m43] * wPoint; + const w = + this[PropertySymbol.m14] * xPoint + + this[PropertySymbol.m24] * yPoint + + this[PropertySymbol.m34] * zPoint + + this[PropertySymbol.m44] * wPoint; + + return new DOMPoint(x, y, z, w); + } + + /** + * The `setMatrixValue` method replaces the existing matrix with one computed in the browser (e.g.`matrix(1,0.25,-0.25,1,0,0)`). + * + * @param source A `DOMMatrix`, `Float32Array`, `Float64Array`, `Array`, or DOMMatrix compatible object to set the matrix values from. + */ + public [PropertySymbol.setMatrixValue](source?: TDOMMatrixInit): void { + let matrix: DOMMatrixReadOnly; + + // String + if (typeof source === 'string' && source.length && source !== 'none') { + matrix = (this.constructor)[PropertySymbol.fromString](source); + } + // Array + else if ( + Array.isArray(source) || + source instanceof Float64Array || + source instanceof Float32Array + ) { + matrix = (this.constructor)[PropertySymbol.fromArray](source); + } + // DOMMatrixReadOnly or IDOMMatrixCompatibleObject + else if (typeof source === 'object') { + matrix = (this.constructor).fromMatrix( + source + ); + } + + this[PropertySymbol.m11] = matrix[PropertySymbol.m11]; + this[PropertySymbol.m12] = matrix[PropertySymbol.m12]; + this[PropertySymbol.m13] = matrix[PropertySymbol.m13]; + this[PropertySymbol.m14] = matrix[PropertySymbol.m14]; + this[PropertySymbol.m21] = matrix[PropertySymbol.m21]; + this[PropertySymbol.m22] = matrix[PropertySymbol.m22]; + this[PropertySymbol.m23] = matrix[PropertySymbol.m23]; + this[PropertySymbol.m24] = matrix[PropertySymbol.m24]; + this[PropertySymbol.m31] = matrix[PropertySymbol.m31]; + this[PropertySymbol.m32] = matrix[PropertySymbol.m32]; + this[PropertySymbol.m33] = matrix[PropertySymbol.m33]; + this[PropertySymbol.m34] = matrix[PropertySymbol.m34]; + this[PropertySymbol.m41] = matrix[PropertySymbol.m41]; + this[PropertySymbol.m42] = matrix[PropertySymbol.m42]; + this[PropertySymbol.m43] = matrix[PropertySymbol.m43]; + this[PropertySymbol.m44] = matrix[PropertySymbol.m44]; + } + + /** + * Applies a multiply operation to the current matrix. + * + * @param matrix Second matrix. + */ + public [PropertySymbol.multiplySelf](matrix: IDOMMatrixCompatibleObject): void { + if (!(matrix instanceof DOMMatrixReadOnly)) { + if (matrix?.m11 === undefined && matrix?.a !== undefined) { + matrix = Object.assign({}, DEFAULT_MATRIX_JSON, matrix); + matrix.m11 = matrix.a; + matrix.m12 = matrix.b; + matrix.m21 = matrix.c; + matrix.m22 = matrix.d; + matrix.m41 = matrix.e; + matrix.m42 = matrix.f; + } else { + matrix = Object.assign({}, DEFAULT_MATRIX_JSON, matrix); + } + } + + const m11 = + this[PropertySymbol.m11] * matrix.m11 + + this[PropertySymbol.m21] * matrix.m12 + + this[PropertySymbol.m31] * matrix.m13 + + this[PropertySymbol.m41] * matrix.m14; + const m21 = + this[PropertySymbol.m11] * matrix.m21 + + this[PropertySymbol.m21] * matrix.m22 + + this[PropertySymbol.m31] * matrix.m23 + + this[PropertySymbol.m41] * matrix.m24; + const m31 = + this[PropertySymbol.m11] * matrix.m31 + + this[PropertySymbol.m21] * matrix.m32 + + this[PropertySymbol.m31] * matrix.m33 + + this[PropertySymbol.m41] * matrix.m34; + const m41 = + this[PropertySymbol.m11] * matrix.m41 + + this[PropertySymbol.m21] * matrix.m42 + + this[PropertySymbol.m31] * matrix.m43 + + this[PropertySymbol.m41] * matrix.m44; + + const m12 = + this[PropertySymbol.m12] * matrix.m11 + + this[PropertySymbol.m22] * matrix.m12 + + this[PropertySymbol.m32] * matrix.m13 + + this[PropertySymbol.m42] * matrix.m14; + const m22 = + this[PropertySymbol.m12] * matrix.m21 + + this[PropertySymbol.m22] * matrix.m22 + + this[PropertySymbol.m32] * matrix.m23 + + this[PropertySymbol.m42] * matrix.m24; + const m32 = + this[PropertySymbol.m12] * matrix.m31 + + this[PropertySymbol.m22] * matrix.m32 + + this[PropertySymbol.m32] * matrix.m33 + + this[PropertySymbol.m42] * matrix.m34; + const m42 = + this[PropertySymbol.m12] * matrix.m41 + + this[PropertySymbol.m22] * matrix.m42 + + this[PropertySymbol.m32] * matrix.m43 + + this[PropertySymbol.m42] * matrix.m44; + + const m13 = + this[PropertySymbol.m13] * matrix.m11 + + this[PropertySymbol.m23] * matrix.m12 + + this[PropertySymbol.m33] * matrix.m13 + + this[PropertySymbol.m43] * matrix.m14; + const m23 = + this[PropertySymbol.m13] * matrix.m21 + + this[PropertySymbol.m23] * matrix.m22 + + this[PropertySymbol.m33] * matrix.m23 + + this[PropertySymbol.m43] * matrix.m24; + const m33 = + this[PropertySymbol.m13] * matrix.m31 + + this[PropertySymbol.m23] * matrix.m32 + + this[PropertySymbol.m33] * matrix.m33 + + this[PropertySymbol.m43] * matrix.m34; + const m43 = + this[PropertySymbol.m13] * matrix.m41 + + this[PropertySymbol.m23] * matrix.m42 + + this[PropertySymbol.m33] * matrix.m43 + + this[PropertySymbol.m43] * matrix.m44; + + const m14 = + this[PropertySymbol.m14] * matrix.m11 + + this[PropertySymbol.m24] * matrix.m12 + + this[PropertySymbol.m34] * matrix.m13 + + this[PropertySymbol.m44] * matrix.m14; + const m24 = + this[PropertySymbol.m14] * matrix.m21 + + this[PropertySymbol.m24] * matrix.m22 + + this[PropertySymbol.m34] * matrix.m23 + + this[PropertySymbol.m44] * matrix.m24; + const m34 = + this[PropertySymbol.m14] * matrix.m31 + + this[PropertySymbol.m24] * matrix.m32 + + this[PropertySymbol.m34] * matrix.m33 + + this[PropertySymbol.m44] * matrix.m34; + const m44 = + this[PropertySymbol.m14] * matrix.m41 + + this[PropertySymbol.m24] * matrix.m42 + + this[PropertySymbol.m34] * matrix.m43 + + this[PropertySymbol.m44] * matrix.m44; + + this[PropertySymbol.m11] = m11; + this[PropertySymbol.m12] = m12; + this[PropertySymbol.m13] = m13; + this[PropertySymbol.m14] = m14; + this[PropertySymbol.m21] = m21; + this[PropertySymbol.m22] = m22; + this[PropertySymbol.m23] = m23; + this[PropertySymbol.m24] = m24; + this[PropertySymbol.m31] = m31; + this[PropertySymbol.m32] = m32; + this[PropertySymbol.m33] = m33; + this[PropertySymbol.m34] = m34; + this[PropertySymbol.m41] = m41; + this[PropertySymbol.m42] = m42; + this[PropertySymbol.m43] = m43; + this[PropertySymbol.m44] = m44; + } + + /** + * Applies translate to the matrix. + * + * This method is equivalent to the CSS `translate3d()` function. + * + * @see https://drafts.csswg.org/css-transforms-1/#TranslateDefined + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @param [x] X-Axis position. + * @param [y] Y-Axis position. + * @param [z] Z-Axis position. + */ + public [PropertySymbol.translateSelf](x: number = 0, y: number = 0, z: number = 0): void { + // prettier-ignore + const translationMatrix = (this.constructor)[PropertySymbol.fromArray]([ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + x, y, z, 1, + ]); + + this[PropertySymbol.multiplySelf](translationMatrix); + } + + /** + * Applies a scale to the matrix. + * + * This method is equivalent to the CSS `scale()` function. + * + * @see https://drafts.csswg.org/css-transforms-1/#ScaleDefined + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @param [scaleX] X-Axis scale. + * @param [scaleY] Y-Axis scale. + * @param [scaleZ] Z-Axis scale. + * @param [originX] X-Axis scale. + * @param [originY] Y-Axis scale. + * @param [originZ] Z-Axis scale. + */ + public [PropertySymbol.scaleSelf]( + scaleX?: number, + scaleY?: number, + scaleZ = 1, + originX = 0, + originY = 0, + originZ = 0 + ): void { + // If scaleY is missing, set scaleY to the value of scaleX. + scaleX = scaleX === undefined ? 1 : Number(scaleX); + scaleY = scaleY === undefined ? scaleX : Number(scaleY); + + if (originX !== 0 || originY !== 0 || originZ !== 0) { + this[PropertySymbol.translateSelf](originX, originY, originZ); + } + + if (scaleX !== 1 || scaleY !== 1 || scaleZ !== 1) { + // prettier-ignore + this[PropertySymbol.multiplySelf]((this.constructor)[PropertySymbol.fromArray]([ + scaleX, 0, 0, 0, + 0, scaleY, 0, 0, + 0, 0, scaleZ, 0, + 0, 0, 0, 1, + ])); + } + + if (originX !== 0 || originY !== 0 || originZ !== 0) { + this[PropertySymbol.translateSelf](-originX, -originY, -originZ); + } + } + + /** + * Applies a scale to the matrix. + * + * This method is equivalent to the CSS `scale()` function. + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale3d + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @param [scale] The scale factor. + * @param [originX] X-Axis scale. + * @param [originY] Y-Axis scale. + * @param [originZ] Z-Axis scale. + */ + public [PropertySymbol.scale3dSelf](scale = 1, originX = 0, originY = 0, originZ = 0): void { + if (originX !== 0 || originY !== 0 || originZ !== 0) { + this[PropertySymbol.translateSelf](originX, originY, originZ); + } + + if (scale !== 1) { + // prettier-ignore + this[PropertySymbol.multiplySelf]((this.constructor)[PropertySymbol.fromArray]([ + scale, 0, 0, 0, + 0, scale, 0, 0, + 0, 0, scale, 0, + 0, 0, 0, 1, + ])); + } + + if (originX !== 0 || originY !== 0 || originZ !== 0) { + this[PropertySymbol.translateSelf](-originX, -originY, -originZ); + } + } + + /** + * Applies a scale to the matrix. + * + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @param [scaleX] X-Axis scale. + * @param [scaleY] Y-Axis scale. + */ + public [PropertySymbol.scaleNonUniformSelf](scaleX = 1, scaleY = 1): void { + if (scaleX === 1 && scaleY === 1) { + return; + } + + // prettier-ignore + this[PropertySymbol.multiplySelf]((this.constructor)[PropertySymbol.fromArray]([ + scaleX, 0, 0, 0, + 0, scaleY, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ])); + } + + /** + * Applies a rotation to the matrix. + * + * This method is equivalent to the CSS `rotate3d()` function. + * + * @see https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-rotateaxisangleself + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d + * @param [x] X-Axis vector. + * @param [y] Y-Axis vector. + * @param [z] Z-Axis vector. + * @param [angle] Angle in degrees of the rotation. + */ + public [PropertySymbol.rotateAxisAngleSelf](x = 0, y = 0, z = 0, angle = 0): void { + x = Number(x); + y = Number(y); + z = Number(z); + angle = Number(angle); + + if (isNaN(x) || isNaN(y) || isNaN(z) || isNaN(angle)) { + throw new TypeError( + `Failed to execute 'rotateAxisAngleSelf' on 'DOMMatrix': The arguments must be numbers.` + ); + } + + const length = Math.hypot(x, y, z); + + if (length === 0) { + return; + } + + if (length !== 1) { + x /= length; + y /= length; + z /= length; + } + + // Degree to radian divided by 2 + const alpha = -((angle * Math.PI) / 360); + const round = this.#round; + const sc = Math.sin(alpha) * Math.cos(alpha); + const sq = Math.sin(alpha) * Math.sin(alpha); + + // We round it as the calculation is off by about 0.000000000000001, which causes the matrix to be different from the browser. + // The rounding doesn't solve the issue for all cases, it would be better to find a more accurate solution. + + const m11 = round(1 - 2 * (y * y + z * z) * sq); + const m12 = round(2 * (x * y * sq + z * sc)); + const m13 = round(2 * (x * z * sq - y * sc)); + const m21 = round(2 * (x * y * sq - z * sc)); + const m22 = round(1 - 2 * (x * x + z * z) * sq); + const m23 = round(2 * (y * z * sq + x * sc)); + const m31 = round(2 * (x * z * sq + y * sc)); + const m32 = round(2 * (y * z * sq - x * sc)); + const m33 = round(1 - 2 * (x * x + y * y) * sq); + + // prettier-ignore + const matrix = (this.constructor)[PropertySymbol.fromArray]([ + m11, m21, m31, 0, + m12, m22, m32, 0, + m13, m23, m33, 0, + 0, 0, 0, 1 + ]); + + this[PropertySymbol.multiplySelf](matrix); + } + + /** + * Applies a rotation to the matrix. + * + * @see http://en.wikipedia.org/wiki/Rotation_matrix + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @param [x] X-Axis rotation in degrees. + * @param [y] Y-Axis rotation in degrees. + * @param [z] Z-Axis rotation in degrees. + */ + public [PropertySymbol.rotateSelf](x = 0, y?: number, z?: number): void { + // If Y and Z are both missing, set Z to the value of X and set X and Y to 0. + if (y === undefined && z === undefined) { + z = x; + x = 0; + y = 0; + } + + // If Y is still missing, set Y to 0. + if (y === undefined) { + y = 0; + } + + // If Z is still missing, set Z to 0 + if (z === undefined) { + z = 0; + } + + x = Number(x); + y = Number(y); + z = Number(z); + + if (isNaN(x) || isNaN(y) || isNaN(z)) { + throw new TypeError( + `Failed to execute 'rotateSelf' on 'DOMMatrix': The arguments must be numbers.` + ); + } + + if (z !== 0) { + this[PropertySymbol.rotateAxisAngleSelf](0, 0, 1, z); + } + if (y !== 0) { + this[PropertySymbol.rotateAxisAngleSelf](0, 1, 0, y); + } + if (x !== 0) { + this[PropertySymbol.rotateAxisAngleSelf](1, 0, 0, x); + } + } + + /** + * Modifies the matrix by rotating it by the angle between the specified vector and (1, 0). + * + * @param x The X component of the axis vector. + * @param y The Y component of the axis vector. + */ + public [PropertySymbol.rotateFromVectorSelf](x = 0, y = 0): void { + if (x === 0 && y === 0) { + return; + } + this[PropertySymbol.rotateSelf]((Math.atan2(y, x) * 180) / Math.PI); + } + + /** + * Applies a skew operation to the matrix on the X axis. + * + * This method is equivalent to the CSS `skewX()` function. + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewX + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @param angle Angle in degrees. + */ + public [PropertySymbol.skewXSelf](angle: number): void { + const matrix = Object.assign({}, DEFAULT_MATRIX_JSON); + const value = Math.tan((angle * Math.PI) / 180); + + matrix.m21 = value; + matrix.c = value; + + this[PropertySymbol.multiplySelf](matrix); + } + + /** + * Applies a skew operation to the matrix on the Y axis. + * + * This method is equivalent to the CSS `skewY()` function. + * + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skewY + * @see https://www.w3.org/TR/css-transforms-1/#transform-functions + * @param angle Angle in degrees. + */ + public [PropertySymbol.skewYSelf](angle: number): void { + const matrix = Object.assign({}, DEFAULT_MATRIX_JSON); + const value = Math.tan((angle * Math.PI) / 180); + + matrix.m12 = value; + matrix.b = value; + + this[PropertySymbol.multiplySelf](matrix); + } + + /** + * Applies a flip operation to the matrix on the X axis. + */ + public [PropertySymbol.flipXSelf](): void { + // prettier-ignore + const matrix = (this.constructor)[PropertySymbol.fromArray]([ + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + this[PropertySymbol.multiplySelf](matrix); + } + + /** + * Applies a flip operation to the matrix on the Y axis. + */ + public [PropertySymbol.flipYSelf](): void { + // prettier-ignore + const matrix = (this.constructor)[PropertySymbol.fromArray]([ + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]); + this[PropertySymbol.multiplySelf](matrix); + } + + /** + * Applies an inversion operation to the matrix. + */ + public [PropertySymbol.invertSelf](): void { + const m11 = + this[PropertySymbol.m22] * this[PropertySymbol.m33] * this[PropertySymbol.m44] - + this[PropertySymbol.m22] * this[PropertySymbol.m34] * this[PropertySymbol.m43] - + this[PropertySymbol.m32] * this[PropertySymbol.m23] * this[PropertySymbol.m44] + + this[PropertySymbol.m32] * this[PropertySymbol.m24] * this[PropertySymbol.m43] + + this[PropertySymbol.m42] * this[PropertySymbol.m23] * this[PropertySymbol.m34] - + this[PropertySymbol.m42] * this[PropertySymbol.m24] * this[PropertySymbol.m33]; + + const m12 = + -this[PropertySymbol.m12] * this[PropertySymbol.m33] * this[PropertySymbol.m44] + + this[PropertySymbol.m12] * this[PropertySymbol.m34] * this[PropertySymbol.m43] + + this[PropertySymbol.m32] * this[PropertySymbol.m13] * this[PropertySymbol.m44] - + this[PropertySymbol.m32] * this[PropertySymbol.m14] * this[PropertySymbol.m43] - + this[PropertySymbol.m42] * this[PropertySymbol.m13] * this[PropertySymbol.m34] + + this[PropertySymbol.m42] * this[PropertySymbol.m14] * this[PropertySymbol.m33]; + + const m13 = + this[PropertySymbol.m12] * this[PropertySymbol.m23] * this[PropertySymbol.m44] - + this[PropertySymbol.m12] * this[PropertySymbol.m24] * this[PropertySymbol.m43] - + this[PropertySymbol.m22] * this[PropertySymbol.m13] * this[PropertySymbol.m44] + + this[PropertySymbol.m22] * this[PropertySymbol.m14] * this[PropertySymbol.m43] + + this[PropertySymbol.m42] * this[PropertySymbol.m13] * this[PropertySymbol.m24] - + this[PropertySymbol.m42] * this[PropertySymbol.m14] * this[PropertySymbol.m23]; + + const m14 = + -this[PropertySymbol.m12] * this[PropertySymbol.m23] * this[PropertySymbol.m34] + + this[PropertySymbol.m12] * this[PropertySymbol.m24] * this[PropertySymbol.m33] + + this[PropertySymbol.m22] * this[PropertySymbol.m13] * this[PropertySymbol.m34] - + this[PropertySymbol.m22] * this[PropertySymbol.m14] * this[PropertySymbol.m33] - + this[PropertySymbol.m32] * this[PropertySymbol.m13] * this[PropertySymbol.m24] + + this[PropertySymbol.m32] * this[PropertySymbol.m14] * this[PropertySymbol.m23]; + + const det = + this[PropertySymbol.m11] * m11 + + this[PropertySymbol.m21] * m12 + + this[PropertySymbol.m31] * m13 + + this[PropertySymbol.m41] * m14; + + // If the current matrix is not invertible set all attributes to NaN and set is 2D to false. + if (det === 0) { + this[PropertySymbol.m11] = NaN; + this[PropertySymbol.m12] = NaN; + this[PropertySymbol.m13] = NaN; + this[PropertySymbol.m14] = NaN; + this[PropertySymbol.m21] = NaN; + this[PropertySymbol.m22] = NaN; + this[PropertySymbol.m23] = NaN; + this[PropertySymbol.m24] = NaN; + this[PropertySymbol.m31] = NaN; + this[PropertySymbol.m32] = NaN; + this[PropertySymbol.m33] = NaN; + this[PropertySymbol.m34] = NaN; + this[PropertySymbol.m41] = NaN; + this[PropertySymbol.m42] = NaN; + this[PropertySymbol.m43] = NaN; + this[PropertySymbol.m44] = NaN; + return; + } + + const m21 = + -this[PropertySymbol.m21] * this[PropertySymbol.m33] * this[PropertySymbol.m44] + + this[PropertySymbol.m21] * this[PropertySymbol.m34] * this[PropertySymbol.m43] + + this[PropertySymbol.m31] * this[PropertySymbol.m23] * this[PropertySymbol.m44] - + this[PropertySymbol.m31] * this[PropertySymbol.m24] * this[PropertySymbol.m43] - + this[PropertySymbol.m41] * this[PropertySymbol.m23] * this[PropertySymbol.m34] + + this[PropertySymbol.m41] * this[PropertySymbol.m24] * this[PropertySymbol.m33]; + + const m22 = + this[PropertySymbol.m11] * this[PropertySymbol.m33] * this[PropertySymbol.m44] - + this[PropertySymbol.m11] * this[PropertySymbol.m34] * this[PropertySymbol.m43] - + this[PropertySymbol.m31] * this[PropertySymbol.m13] * this[PropertySymbol.m44] + + this[PropertySymbol.m31] * this[PropertySymbol.m14] * this[PropertySymbol.m43] + + this[PropertySymbol.m41] * this[PropertySymbol.m13] * this[PropertySymbol.m34] - + this[PropertySymbol.m41] * this[PropertySymbol.m14] * this[PropertySymbol.m33]; + + const m23 = + -this[PropertySymbol.m11] * this[PropertySymbol.m23] * this[PropertySymbol.m44] + + this[PropertySymbol.m11] * this[PropertySymbol.m24] * this[PropertySymbol.m43] + + this[PropertySymbol.m21] * this[PropertySymbol.m13] * this[PropertySymbol.m44] - + this[PropertySymbol.m21] * this[PropertySymbol.m14] * this[PropertySymbol.m43] - + this[PropertySymbol.m41] * this[PropertySymbol.m13] * this[PropertySymbol.m24] + + this[PropertySymbol.m41] * this[PropertySymbol.m14] * this[PropertySymbol.m23]; + + const m24 = + this[PropertySymbol.m11] * this[PropertySymbol.m23] * this[PropertySymbol.m34] - + this[PropertySymbol.m11] * this[PropertySymbol.m24] * this[PropertySymbol.m33] - + this[PropertySymbol.m21] * this[PropertySymbol.m13] * this[PropertySymbol.m34] + + this[PropertySymbol.m21] * this[PropertySymbol.m14] * this[PropertySymbol.m33] + + this[PropertySymbol.m31] * this[PropertySymbol.m13] * this[PropertySymbol.m24] - + this[PropertySymbol.m31] * this[PropertySymbol.m14] * this[PropertySymbol.m23]; + + const m31 = + this[PropertySymbol.m21] * this[PropertySymbol.m32] * this[PropertySymbol.m44] - + this[PropertySymbol.m21] * this[PropertySymbol.m34] * this[PropertySymbol.m42] - + this[PropertySymbol.m31] * this[PropertySymbol.m22] * this[PropertySymbol.m44] + + this[PropertySymbol.m31] * this[PropertySymbol.m24] * this[PropertySymbol.m42] + + this[PropertySymbol.m41] * this[PropertySymbol.m22] * this[PropertySymbol.m34] - + this[PropertySymbol.m41] * this[PropertySymbol.m24] * this[PropertySymbol.m32]; + + const m32 = + -this[PropertySymbol.m11] * this[PropertySymbol.m32] * this[PropertySymbol.m44] + + this[PropertySymbol.m11] * this[PropertySymbol.m34] * this[PropertySymbol.m42] + + this[PropertySymbol.m31] * this[PropertySymbol.m12] * this[PropertySymbol.m44] - + this[PropertySymbol.m31] * this[PropertySymbol.m14] * this[PropertySymbol.m42] - + this[PropertySymbol.m41] * this[PropertySymbol.m12] * this[PropertySymbol.m34] + + this[PropertySymbol.m41] * this[PropertySymbol.m14] * this[PropertySymbol.m32]; + + const m33 = + this[PropertySymbol.m11] * this[PropertySymbol.m22] * this[PropertySymbol.m44] - + this[PropertySymbol.m11] * this[PropertySymbol.m24] * this[PropertySymbol.m42] - + this[PropertySymbol.m21] * this[PropertySymbol.m12] * this[PropertySymbol.m44] + + this[PropertySymbol.m21] * this[PropertySymbol.m14] * this[PropertySymbol.m42] + + this[PropertySymbol.m41] * this[PropertySymbol.m12] * this[PropertySymbol.m24] - + this[PropertySymbol.m41] * this[PropertySymbol.m14] * this[PropertySymbol.m22]; + + const m34 = + -this[PropertySymbol.m11] * this[PropertySymbol.m22] * this[PropertySymbol.m34] + + this[PropertySymbol.m11] * this[PropertySymbol.m24] * this[PropertySymbol.m32] + + this[PropertySymbol.m21] * this[PropertySymbol.m12] * this[PropertySymbol.m34] - + this[PropertySymbol.m21] * this[PropertySymbol.m14] * this[PropertySymbol.m32] - + this[PropertySymbol.m31] * this[PropertySymbol.m12] * this[PropertySymbol.m24] + + this[PropertySymbol.m31] * this[PropertySymbol.m14] * this[PropertySymbol.m22]; + + const m41 = + -this[PropertySymbol.m21] * this[PropertySymbol.m32] * this[PropertySymbol.m43] + + this[PropertySymbol.m21] * this[PropertySymbol.m33] * this[PropertySymbol.m42] + + this[PropertySymbol.m31] * this[PropertySymbol.m22] * this[PropertySymbol.m43] - + this[PropertySymbol.m31] * this[PropertySymbol.m23] * this[PropertySymbol.m42] - + this[PropertySymbol.m41] * this[PropertySymbol.m22] * this[PropertySymbol.m33] + + this[PropertySymbol.m41] * this[PropertySymbol.m23] * this[PropertySymbol.m32]; + + const m42 = + this[PropertySymbol.m11] * this[PropertySymbol.m32] * this[PropertySymbol.m43] - + this[PropertySymbol.m11] * this[PropertySymbol.m33] * this[PropertySymbol.m42] - + this[PropertySymbol.m31] * this[PropertySymbol.m12] * this[PropertySymbol.m43] + + this[PropertySymbol.m31] * this[PropertySymbol.m13] * this[PropertySymbol.m42] + + this[PropertySymbol.m41] * this[PropertySymbol.m12] * this[PropertySymbol.m33] - + this[PropertySymbol.m41] * this[PropertySymbol.m13] * this[PropertySymbol.m32]; + + const m43 = + -this[PropertySymbol.m11] * this[PropertySymbol.m22] * this[PropertySymbol.m43] + + this[PropertySymbol.m11] * this[PropertySymbol.m23] * this[PropertySymbol.m42] + + this[PropertySymbol.m21] * this[PropertySymbol.m12] * this[PropertySymbol.m43] - + this[PropertySymbol.m21] * this[PropertySymbol.m13] * this[PropertySymbol.m42] - + this[PropertySymbol.m41] * this[PropertySymbol.m12] * this[PropertySymbol.m23] + + this[PropertySymbol.m41] * this[PropertySymbol.m13] * this[PropertySymbol.m22]; + + const m44 = + this[PropertySymbol.m11] * this[PropertySymbol.m22] * this[PropertySymbol.m33] - + this[PropertySymbol.m11] * this[PropertySymbol.m23] * this[PropertySymbol.m32] - + this[PropertySymbol.m21] * this[PropertySymbol.m12] * this[PropertySymbol.m33] + + this[PropertySymbol.m21] * this[PropertySymbol.m13] * this[PropertySymbol.m32] + + this[PropertySymbol.m31] * this[PropertySymbol.m12] * this[PropertySymbol.m23] - + this[PropertySymbol.m31] * this[PropertySymbol.m13] * this[PropertySymbol.m22]; + + this[PropertySymbol.m11] = m11 / det || 0; + this[PropertySymbol.m12] = m12 / det || 0; + this[PropertySymbol.m13] = m13 / det || 0; + this[PropertySymbol.m14] = m14 / det || 0; + this[PropertySymbol.m21] = m21 / det || 0; + this[PropertySymbol.m22] = m22 / det || 0; + this[PropertySymbol.m23] = m23 / det || 0; + this[PropertySymbol.m24] = m24 / det || 0; + this[PropertySymbol.m31] = m31 / det || 0; + this[PropertySymbol.m32] = m32 / det || 0; + this[PropertySymbol.m33] = m33 / det || 0; + this[PropertySymbol.m34] = m34 / det || 0; + this[PropertySymbol.m41] = m41 / det || 0; + this[PropertySymbol.m42] = m42 / det || 0; + this[PropertySymbol.m43] = m43 / det || 0; + this[PropertySymbol.m44] = m44 / det || 0; + } + + /** + * Returns an *Array* containing elements which comprise the matrix. + * + * @param matrix Matrix to convert. + * @param [is2D] If the matrix is 2D. + * @returns Array representation of the matrix. + */ + public [PropertySymbol.toArray](is2D: boolean = false): TDOMMatrix2DArray | TDOMMatrix3DArray { + if (is2D) { + return [ + this[PropertySymbol.m11], + this[PropertySymbol.m12], + this[PropertySymbol.m21], + this[PropertySymbol.m22], + this[PropertySymbol.m41], + this[PropertySymbol.m42] + ]; + } + + return [ + this[PropertySymbol.m11], + this[PropertySymbol.m12], + this[PropertySymbol.m13], + this[PropertySymbol.m14], + this[PropertySymbol.m21], + this[PropertySymbol.m22], + this[PropertySymbol.m23], + this[PropertySymbol.m24], + this[PropertySymbol.m31], + this[PropertySymbol.m32], + this[PropertySymbol.m33], + this[PropertySymbol.m34], + this[PropertySymbol.m41], + this[PropertySymbol.m42], + this[PropertySymbol.m43], + this[PropertySymbol.m44] + ]; + } + + /** + * Rounds the value to the given number of decimals. + * + * @param value The value to round. + * @param [decimals] The number of decimals to round to. + */ + #round(value: number, decimals: number = 1e15): number { + return Math.round(value * decimals) / decimals; + } + + /** + * Returns a new `DOMMatrix` instance given an existing matrix. + * + * @param matrix Matrix. + */ + public static fromMatrix(matrix: IDOMMatrixCompatibleObject): DOMMatrixReadOnly { + if (!(matrix instanceof DOMMatrixReadOnly)) { + if (matrix?.m11 === undefined && matrix?.a !== undefined) { + matrix = Object.assign({}, DEFAULT_MATRIX_JSON, matrix); + matrix.m11 = matrix.a; + matrix.m12 = matrix.b; + matrix.m21 = matrix.c; + matrix.m22 = matrix.d; + matrix.m41 = matrix.e; + matrix.m42 = matrix.f; + } else { + matrix = Object.assign({}, DEFAULT_MATRIX_JSON, matrix); + } + } + + return this[PropertySymbol.fromArray]([ + matrix.m11, + matrix.m12, + matrix.m13, + matrix.m14, + matrix.m21, + matrix.m22, + matrix.m23, + matrix.m24, + matrix.m31, + matrix.m32, + matrix.m33, + matrix.m34, + matrix.m41, + matrix.m42, + matrix.m43, + matrix.m44 + ]); + } + + /** + * Returns a new `DOMMatrix` instance given an array of 16/6 floating point values. + * + * @param array An `Array` to feed values from. + * @returns DOMMatrix instance. + */ + public static fromFloat32Array(array: Float32Array): DOMMatrixReadOnly { + return this[PropertySymbol.fromArray](array); + } + + /** + * Returns a new `DOMMatrix` instance given an array of 16/6 floating point values. + * + * @param array An `Array` to feed values from. + * @returns DOMMatrix instance. + */ + public static fromFloat64Array(array: Float64Array): DOMMatrixReadOnly { + return this[PropertySymbol.fromArray](array); + } + + /** + * Returns a new `DOMMatrix` instance given an array of 16/6 floating point values. + * + * Conditions: + * - If the array has six values, the result is a 2D matrix. + * - If the array has 16 values, the result is a 3D matrix. + * - Otherwise, a TypeError exception is thrown. + * + * @param array An `Array` to feed values from. + * @returns DOMMatrix instance. + */ + public static [PropertySymbol.fromArray]( + array: any[] | Float32Array | Float64Array + ): DOMMatrixReadOnly { + if ( + !(array instanceof Float64Array || array instanceof Float32Array || Array.isArray(array)) || + (array.length !== 6 && array.length !== 16) + ) { + throw TypeError( + `Failed to execute 'fromArray' on '${this.name}': '${String( + array + )}' is not a compatible array.` + ); + } + + const matrix = new (this)(); + + if (array.length === 16) { + const [m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44] = + array; + + matrix[PropertySymbol.m11] = m11; + matrix[PropertySymbol.m12] = m12; + matrix[PropertySymbol.m13] = m13; + matrix[PropertySymbol.m14] = m14; + matrix[PropertySymbol.m21] = m21; + matrix[PropertySymbol.m22] = m22; + matrix[PropertySymbol.m23] = m23; + matrix[PropertySymbol.m24] = m24; + matrix[PropertySymbol.m31] = m31; + matrix[PropertySymbol.m32] = m32; + matrix[PropertySymbol.m33] = m33; + matrix[PropertySymbol.m34] = m34; + matrix[PropertySymbol.m41] = m41; + matrix[PropertySymbol.m42] = m42; + matrix[PropertySymbol.m43] = m43; + matrix[PropertySymbol.m44] = m44; + } else if (array.length === 6) { + const [m11, m12, m21, m22, m41, m42] = array; + + matrix[PropertySymbol.m11] = m11; + matrix[PropertySymbol.m12] = m12; + matrix[PropertySymbol.m21] = m21; + matrix[PropertySymbol.m22] = m22; + matrix[PropertySymbol.m41] = m41; + matrix[PropertySymbol.m42] = m42; + } + + return matrix; + } + + /** + * Returns a new `DOMMatrix` instance from a DOM transform string. + * + * @param source valid CSS transform string syntax. + * @returns DOMMatrix instance. + */ + public static [PropertySymbol.fromString](source: string): DOMMatrixReadOnly { + if (typeof source !== 'string') { + throw TypeError( + `Failed to execute 'setMatrixValue' on '${this.name}': Expected '${String( + source + )}' to be a string.` + ); + } + + const domMatrix = new (this)(); + const regexp = new RegExp(TRANSFORM_REGEXP); + + let match: RegExpMatchArray | null; + + while ((match = regexp.exec(source))) { + const name = match[1]; + const parameters: number[] = ( + (match[2].split(TRANSFORM_PARAMETER_SPLIT_REGEXP)) + ); + + for (let i = 0, max = parameters.length; i < max; i++) { + parameters[i] = this[PropertySymbol.getLength]((parameters[i])); + } + + const [x, y, z, a] = parameters; + + switch (name) { + case 'perspective': + if (!isNaN(x) && x !== 0 && y === undefined && z === undefined) { + domMatrix[PropertySymbol.m34] = -1 / x; + } + break; + case 'translate': + if (!isNaN(x) && z === undefined) { + domMatrix[PropertySymbol.translateSelf](x, y || 0, 0); + } + break; + case 'translate3d': + if (!isNaN(x) && !isNaN(y) && !isNaN(z)) { + domMatrix[PropertySymbol.translateSelf](x, y, z); + } + break; + case 'translateX': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.translateSelf](x); + } + break; + case 'translateY': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.translateSelf](0, x); + } + break; + case 'translateZ': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.translateSelf](0, 0, x); + } + break; + case 'matrix': + case 'matrix3d': + if (parameters.length === 6 || parameters.length === 16) { + domMatrix[PropertySymbol.setMatrixValue](this[PropertySymbol.fromArray](parameters)); + } + break; + case 'rotate': + case 'rotateZ': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.rotateSelf](0, 0, x); + } + break; + case 'rotateX': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.rotateSelf](x, 0, 0); + } + break; + case 'rotateY': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.rotateSelf](0, x, 0); + } + case 'rotate3d': + if (!isNaN(x) && !isNaN(y) && !isNaN(z) && !isNaN(a)) { + domMatrix[PropertySymbol.rotateAxisAngleSelf](x, y, z, a); + } + break; + case 'scale': + if (!isNaN(x) && x !== 1 && z === undefined) { + domMatrix[PropertySymbol.scaleSelf](x, isNaN(y) ? x : y); + } + break; + case 'scale3d': + if (!isNaN(x) && !isNaN(y) && !isNaN(z)) { + domMatrix[PropertySymbol.scaleSelf](x, y, z); + } + break; + case 'scaleX': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.scaleSelf](x, 1, 1); + } + break; + case 'scaleY': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.scaleSelf](1, x, 1); + } + break; + case 'scaleZ': + if (!isNaN(x) && y === undefined && z === undefined) { + domMatrix[PropertySymbol.scaleSelf](1, 1, x); + } + break; + case 'skew': + if (!isNaN(x)) { + domMatrix[PropertySymbol.skewXSelf](x); + } + if (!isNaN(y)) { + domMatrix[PropertySymbol.skewYSelf](y); + } + break; + case 'skewX': + if (!isNaN(x) && y === undefined) { + domMatrix[PropertySymbol.skewXSelf](x); + } + break; + case 'skewY': + if (!isNaN(x) && y === undefined) { + domMatrix[PropertySymbol.skewYSelf](x); + } + break; + default: + throw TypeError( + `Failed to execute 'setMatrixValue' on '${this.name}': Unknown transform function '${match[1]}'.` + ); + } + } + + return domMatrix; + } + + /** + * Returns length. + * + * @param length Length to convert. + * @returns Length. + */ + private static [PropertySymbol.getLength](length: string): number { + const value = parseFloat(length); + const unit = length.replace(value.toString(), ''); + switch (unit) { + case 'rem': + case 'em': + case 'vw': + case 'vh': + case '%': + case 'vmin': + case 'vmax': + throw new SyntaxError( + `Failed to construct '${this.name}': Lengths must be absolute, not relative` + ); + case 'rad': + return value * (180 / Math.PI); + case 'turn': + return value * 360; + case 'px': + return value; + case 'cm': + return value * 37.7812; + case 'mm': + return value * 3.7781; + case 'in': + return value * 96; + case 'pt': + return value * 1.3281; + case 'pc': + return value * 16; + case 'Q': + return value * 0.945; + default: + return value; + } + } +} diff --git a/packages/happy-dom/src/dom/dom-matrix/IDOMMatrixCompatibleObject.ts b/packages/happy-dom/src/dom/dom-matrix/IDOMMatrixCompatibleObject.ts new file mode 100644 index 000000000..3167d4724 --- /dev/null +++ b/packages/happy-dom/src/dom/dom-matrix/IDOMMatrixCompatibleObject.ts @@ -0,0 +1,24 @@ +export default interface IDOMMatrixCompatibleObject { + a?: number; + b?: number; + c?: number; + d?: number; + e?: number; + f?: number; + m11?: number; + m12?: number; + m13?: number; + m14?: number; + m21?: number; + m22?: number; + m23?: number; + m24?: number; + m31?: number; + m32?: number; + m33?: number; + m34?: number; + m41?: number; + m42?: number; + m43?: number; + m44?: number; +} diff --git a/packages/happy-dom/src/dom/dom-matrix/IDOMMatrixJSON.ts b/packages/happy-dom/src/dom/dom-matrix/IDOMMatrixJSON.ts new file mode 100644 index 000000000..884ea17bc --- /dev/null +++ b/packages/happy-dom/src/dom/dom-matrix/IDOMMatrixJSON.ts @@ -0,0 +1,26 @@ +export default interface IDOMMatrixJSON { + a: number; + b: number; + c: number; + d: number; + e: number; + f: number; + m11: number; + m12: number; + m13: number; + m14: number; + m21: number; + m22: number; + m23: number; + m24: number; + m31: number; + m32: number; + m33: number; + m34: number; + m41: number; + m42: number; + m43: number; + m44: number; + is2D: boolean; + isIdentity: boolean; +} diff --git a/packages/happy-dom/src/dom/dom-matrix/TDOMMatrix2DArray.ts b/packages/happy-dom/src/dom/dom-matrix/TDOMMatrix2DArray.ts new file mode 100644 index 000000000..9044d55ed --- /dev/null +++ b/packages/happy-dom/src/dom/dom-matrix/TDOMMatrix2DArray.ts @@ -0,0 +1,3 @@ +/** An array of 6 numbers representing a 2D matrix. */ +type TDOMMatrix2DArray = [number, number, number, number, number, number]; +export default TDOMMatrix2DArray; diff --git a/packages/happy-dom/src/dom/dom-matrix/TDOMMatrix3DArray.ts b/packages/happy-dom/src/dom/dom-matrix/TDOMMatrix3DArray.ts new file mode 100644 index 000000000..435f5aad3 --- /dev/null +++ b/packages/happy-dom/src/dom/dom-matrix/TDOMMatrix3DArray.ts @@ -0,0 +1,20 @@ +/** An array of 16 numbers representing a 3D matrix. */ +type TDOMMatrix3DArray = [ + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number +]; +export default TDOMMatrix3DArray; diff --git a/packages/happy-dom/src/dom/dom-matrix/TDOMMatrixInit.ts b/packages/happy-dom/src/dom/dom-matrix/TDOMMatrixInit.ts new file mode 100644 index 000000000..1a89f7198 --- /dev/null +++ b/packages/happy-dom/src/dom/dom-matrix/TDOMMatrixInit.ts @@ -0,0 +1,4 @@ +import IDOMMatrixCompatibleObject from './IDOMMatrixCompatibleObject.js'; + +type TDOMMatrixInit = string | any[] | IDOMMatrixCompatibleObject | Float32Array | Float64Array; +export default TDOMMatrixInit; diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts index 1e07a1dba..a0b3fda2c 100644 --- a/packages/happy-dom/src/index.ts +++ b/packages/happy-dom/src/index.ts @@ -72,8 +72,8 @@ import Comment from './nodes/comment/Comment.js'; import DocumentFragment from './nodes/document-fragment/DocumentFragment.js'; import DocumentType from './nodes/document-type/DocumentType.js'; import Document from './nodes/document/Document.js'; -import DOMRect from './nodes/element/DOMRect.js'; -import DOMRectReadOnly from './nodes/element/DOMRectReadOnly.js'; +import DOMRect from './dom/DOMRect.js'; +import DOMRectReadOnly from './dom/DOMRectReadOnly.js'; import Element from './nodes/element/Element.js'; import HTMLCollection from './nodes/element/HTMLCollection.js'; import HTMLAnchorElement from './nodes/html-anchor-element/HTMLAnchorElement.js'; @@ -157,10 +157,9 @@ import HTMLVideoElement from './nodes/html-video-element/HTMLVideoElement.js'; import Node from './nodes/node/Node.js'; import ProcessingInstruction from './nodes/processing-instruction/ProcessingInstruction.js'; import ShadowRoot from './nodes/shadow-root/ShadowRoot.js'; -import SVGDocument from './nodes/svg-document/SVGDocument.js'; import SVGElement from './nodes/svg-element/SVGElement.js'; -import SVGGraphicsElement from './nodes/svg-element/SVGGraphicsElement.js'; -import SVGSVGElement from './nodes/svg-element/SVGSVGElement.js'; +import SVGGraphicsElement from './nodes/svg-graphics-element/SVGGraphicsElement.js'; +import SVGSVGElement from './nodes/svg-svg-element/SVGSVGElement.js'; import Text from './nodes/text/Text.js'; import XMLDocument from './nodes/xml-document/XMLDocument.js'; import PermissionStatus from './permissions/PermissionStatus.js'; @@ -391,7 +390,6 @@ export { ShadowRoot, Storage, SubmitEvent, - SVGDocument, SVGElement, SVGGraphicsElement, SVGSVGElement, diff --git a/packages/happy-dom/src/intersection-observer/IntersectionObserverEntry.ts b/packages/happy-dom/src/intersection-observer/IntersectionObserverEntry.ts index e95e652a5..7c1c7e4b8 100644 --- a/packages/happy-dom/src/intersection-observer/IntersectionObserverEntry.ts +++ b/packages/happy-dom/src/intersection-observer/IntersectionObserverEntry.ts @@ -1,4 +1,4 @@ -import DOMRect from '../nodes/element/DOMRect.js'; +import DOMRect from '../dom/DOMRect.js'; import Node from '../nodes/node/Node.js'; /** diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index 8fefd0755..b87298790 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -47,6 +47,8 @@ import ICachedResult from '../node/ICachedResult.js'; import HTMLTitleElement from '../html-title-element/HTMLTitleElement.js'; import WindowBrowserContext from '../../window/WindowBrowserContext.js'; import NodeFactory from '../NodeFactory.js'; +import SVGElementConfig from '../../config/SVGElementConfig.js'; +import StringUtility from '../../StringUtility.js'; const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/; @@ -71,7 +73,7 @@ export default class Document extends Node { public [PropertySymbol.defaultView]: BrowserWindow | null = null; public [PropertySymbol.forms]: HTMLCollection | null = null; public [PropertySymbol.affectsComputedStyleCache]: ICachedResult[] = []; - public [PropertySymbol.ownerDocument]: Document | null = null; + public [PropertySymbol.ownerDocument]: Document = (null); public [PropertySymbol.elementIdMap]: Map< string, { htmlCollection: HTMLCollection | null; elements: Element[] } @@ -1010,7 +1012,13 @@ export default class Document extends Node { * @returns Element. */ public createElement(qualifiedName: string, options?: { is?: string }): HTMLElement { - return this.createElementNS(NamespaceURI.html, qualifiedName, options); + return ( + this.createElementNS( + NamespaceURI.html, + StringUtility.asciiLowerCase(String(qualifiedName)), + options + ) + ); } /** @@ -1072,20 +1080,21 @@ export default class Document extends Node { qualifiedName: string, options?: { is?: string } ): Element { + const window = this[PropertySymbol.window]; + qualifiedName = String(qualifiedName); if (!qualifiedName) { - throw new this[PropertySymbol.window].DOMException( + throw new window.DOMException( "Failed to execute 'createElementNS' on 'Document': The qualified name provided is empty." ); } // SVG element if (namespaceURI === NamespaceURI.svg) { + const config = SVGElementConfig[qualifiedName.toLowerCase()]; const elementClass = - qualifiedName === 'svg' - ? this[PropertySymbol.window].SVGSVGElement - : this[PropertySymbol.window].SVGElement; + config && config.localName === qualifiedName ? window[config.className] : window.SVGElement; const element = NodeFactory.createNode(this, elementClass); @@ -1099,7 +1108,7 @@ export default class Document extends Node { // Custom HTML element const customElement = - this[PropertySymbol.window].customElements[PropertySymbol.registry]?.[ + window.customElements[PropertySymbol.registry]?.[ options && options.is ? String(options.is) : qualifiedName ]; @@ -1112,9 +1121,8 @@ export default class Document extends Node { return element; } - const localName = qualifiedName.toLowerCase(); - const elementClass = HTMLElementConfig[localName] - ? this[PropertySymbol.window][HTMLElementConfig[localName].className] + const elementClass = HTMLElementConfig[qualifiedName] + ? window[HTMLElementConfig[qualifiedName].className] : null; // Known HTML element @@ -1122,22 +1130,22 @@ export default class Document extends Node { const element = NodeFactory.createNode(this, elementClass); element[PropertySymbol.tagName] = qualifiedName.toUpperCase(); - element[PropertySymbol.localName] = localName; + element[PropertySymbol.localName] = qualifiedName; element[PropertySymbol.namespaceURI] = namespaceURI; element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null; return element; } - // Unknown HTML element - const unknownElementClass = localName.includes('-') - ? this[PropertySymbol.window].HTMLElement - : this[PropertySymbol.window].HTMLUnknownElement; + // Unknown HTML element (if it has an hyphen in the name, it is a custom element that hasn't been defined yet) + const unknownElementClass = qualifiedName.includes('-') + ? window.HTMLElement + : window.HTMLUnknownElement; const element = NodeFactory.createNode(this, unknownElementClass); element[PropertySymbol.tagName] = qualifiedName.toUpperCase(); - element[PropertySymbol.localName] = localName; + element[PropertySymbol.localName] = qualifiedName; element[PropertySymbol.namespaceURI] = namespaceURI; element[PropertySymbol.isValue] = options && options.is ? String(options.is) : null; diff --git a/packages/happy-dom/src/nodes/element/DOMRectList.ts b/packages/happy-dom/src/nodes/element/DOMRectList.ts deleted file mode 100644 index 4b7ef0382..000000000 --- a/packages/happy-dom/src/nodes/element/DOMRectList.ts +++ /dev/null @@ -1,15 +0,0 @@ -import DOMRect from './DOMRect.js'; - -/** - * DOMRectList. - */ -export default class DOMRectList extends Array { - /** - * Returns item by index. - * - * @param index Index. - */ - public item(index: number): DOMRect { - return this[index] ?? null; - } -} diff --git a/packages/happy-dom/src/nodes/element/Element.ts b/packages/happy-dom/src/nodes/element/Element.ts index d416fd7f6..d22cfd4a4 100644 --- a/packages/happy-dom/src/nodes/element/Element.ts +++ b/packages/happy-dom/src/nodes/element/Element.ts @@ -1,8 +1,8 @@ import Node from '../node/Node.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import ShadowRoot from '../shadow-root/ShadowRoot.js'; -import DOMRect from './DOMRect.js'; -import DOMTokenList from './DOMTokenList.js'; +import DOMRect from '../../dom/DOMRect.js'; +import DOMTokenList from '../../dom/DOMTokenList.js'; import QuerySelector from '../../query-selector/QuerySelector.js'; import XMLParser from '../../xml-parser/XMLParser.js'; import XMLSerializer from '../../xml-serializer/XMLSerializer.js'; @@ -11,7 +11,7 @@ import ParentNodeUtility from '../parent-node/ParentNodeUtility.js'; import NonDocumentChildNodeUtility from '../child-node/NonDocumentChildNodeUtility.js'; import HTMLCollection from './HTMLCollection.js'; import Text from '../text/Text.js'; -import DOMRectList from './DOMRectList.js'; +import DOMRectList from '../../dom/DOMRectList.js'; import Attr from '../attr/Attr.js'; import NamedNodeMap from './NamedNodeMap.js'; import Event from '../../event/Event.js'; @@ -254,7 +254,11 @@ export default class Element */ public get classList(): DOMTokenList { if (!this[PropertySymbol.classList]) { - this[PropertySymbol.classList] = new DOMTokenList(this, 'class'); + this[PropertySymbol.classList] = new DOMTokenList( + PropertySymbol.illegalConstructor, + this, + 'class' + ); } return this[PropertySymbol.classList]; } @@ -920,7 +924,7 @@ export default class Element */ public getClientRects(): DOMRectList { // TODO: Not full implementation - const domRectList = new DOMRectList(); + const domRectList = new DOMRectList(PropertySymbol.illegalConstructor); domRectList.push(this.getBoundingClientRect()); return domRectList; } @@ -1353,11 +1357,6 @@ export default class Element this.#addIdentifierToWindow(attribute[PropertySymbol.value]); } - if (this[PropertySymbol.cache].style) { - this[PropertySymbol.cache].style.result = null; - this[PropertySymbol.cache].style = null; - } - if ( this.attributeChangedCallback && (this.constructor)[PropertySymbol.observedAttributes] && @@ -1410,11 +1409,6 @@ export default class Element this.#removeIdentifierFromWindow(removedAttribute[PropertySymbol.value]); } - if (this[PropertySymbol.cache].style) { - this[PropertySymbol.cache].style.result = null; - this[PropertySymbol.cache].style = null; - } - if ( this.attributeChangedCallback && (this.constructor)[PropertySymbol.observedAttributes] && diff --git a/packages/happy-dom/src/nodes/element/HTMLCollection.ts b/packages/happy-dom/src/nodes/element/HTMLCollection.ts index 91963679f..eee45cbce 100644 --- a/packages/happy-dom/src/nodes/element/HTMLCollection.ts +++ b/packages/happy-dom/src/nodes/element/HTMLCollection.ts @@ -17,21 +17,19 @@ export default class HTMLCollection { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. + * @param illegalConstructorSymbol Illegal constructor symbol. * @param query Query function. */ - constructor(illegalConstructorSymbol?: symbol, query: () => T[] = () => []) { + constructor(illegalConstructorSymbol: symbol, query: () => T[]) { if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { throw new TypeError('Illegal constructor'); } this[PropertySymbol.query] = query; - // This only works for one level of inheritance, but it should be fine as there is no collection that goes deeper according to spec. - ClassMethodBinder.bindMethods( + const methodBinder = new ClassMethodBinder( this, - this.constructor !== HTMLCollection ? [HTMLCollection, this.constructor] : [HTMLCollection], - { bindSymbols: true } + this.constructor !== HTMLCollection ? [this.constructor, HTMLCollection] : [HTMLCollection] ); return new Proxy(this, { @@ -40,9 +38,9 @@ export default class HTMLCollection { return query().length; } if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } - const index = Number(property); if (!isNaN(index)) { return query()[index]; @@ -50,6 +48,7 @@ export default class HTMLCollection { return target.namedItem(property) || undefined; }, set(target, property, newValue): boolean { + methodBinder.bind(property); if (typeof property === 'symbol') { target[property] = newValue; return true; @@ -123,6 +122,8 @@ export default class HTMLCollection { return false; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + if (property in target) { Object.defineProperty(target, property, descriptor); return true; diff --git a/packages/happy-dom/src/nodes/element/NamedNodeMap.ts b/packages/happy-dom/src/nodes/element/NamedNodeMap.ts index ba6f29f49..bce3574cf 100644 --- a/packages/happy-dom/src/nodes/element/NamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/element/NamedNodeMap.ts @@ -4,7 +4,6 @@ import DOMException from '../../exception/DOMException.js'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import Element from './Element.js'; import NamespaceURI from '../../config/NamespaceURI.js'; -import ClassMethodBinder from '../../ClassMethodBinder.js'; /** * Named Node Map. @@ -29,9 +28,6 @@ export default class NamedNodeMap { */ constructor(ownerElement: Element) { this[PropertySymbol.ownerElement] = ownerElement; - ClassMethodBinder.bindMethods(this, [NamedNodeMap], { - bindSymbols: true - }); } /** diff --git a/packages/happy-dom/src/nodes/element/NamedNodeMapProxyFactory.ts b/packages/happy-dom/src/nodes/element/NamedNodeMapProxyFactory.ts index 1719c7ccc..a4c305fa0 100644 --- a/packages/happy-dom/src/nodes/element/NamedNodeMapProxyFactory.ts +++ b/packages/happy-dom/src/nodes/element/NamedNodeMapProxyFactory.ts @@ -1,5 +1,6 @@ /* eslint-disable filenames/match-exported */ +import ClassMethodBinder from '../../ClassMethodBinder.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import NamedNodeMap from './NamedNodeMap.js'; @@ -18,12 +19,15 @@ export default class NamedNodeMapProxyFactory { const namedItems = namedNodeMap[PropertySymbol.namedItems]; const namespaceItems = namedNodeMap[PropertySymbol.namespaceItems]; + const methodBinder = new ClassMethodBinder(this, [NamedNodeMap]); + return new Proxy(namedNodeMap, { get: (target, property) => { if (property === 'length') { return namespaceItems.size; } if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } const index = Number(property); @@ -33,6 +37,7 @@ export default class NamedNodeMapProxyFactory { return target.getNamedItem(property) || undefined; }, set(target, property, newValue): boolean { + methodBinder.bind(property); if (typeof property === 'symbol') { target[property] = newValue; return true; @@ -79,6 +84,8 @@ export default class NamedNodeMapProxyFactory { return false; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + if (property in target) { Object.defineProperty(target, property, descriptor); return true; diff --git a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts index 04559648a..ad1175ea1 100644 --- a/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts +++ b/packages/happy-dom/src/nodes/html-anchor-element/HTMLAnchorElement.ts @@ -1,6 +1,6 @@ import HTMLElement from '../html-element/HTMLElement.js'; import * as PropertySymbol from '../../PropertySymbol.js'; -import DOMTokenList from '../element/DOMTokenList.js'; +import DOMTokenList from '../../dom/DOMTokenList.js'; import Event from '../../event/Event.js'; import EventPhaseEnum from '../../event/EventPhaseEnum.js'; import HTMLHyperlinkElementUtility from '../html-hyperlink-element/HTMLHyperlinkElementUtility.js'; @@ -285,7 +285,11 @@ export default class HTMLAnchorElement extends HTMLElement implements IHTMLHyper */ public get relList(): DOMTokenList { if (!this[PropertySymbol.relList]) { - this[PropertySymbol.relList] = new DOMTokenList(this, 'rel'); + this[PropertySymbol.relList] = new DOMTokenList( + PropertySymbol.illegalConstructor, + this, + 'rel' + ); } return this[PropertySymbol.relList]; } diff --git a/packages/happy-dom/src/nodes/html-area-element/HTMLAreaElement.ts b/packages/happy-dom/src/nodes/html-area-element/HTMLAreaElement.ts index 34c80ad11..4a96ec94d 100644 --- a/packages/happy-dom/src/nodes/html-area-element/HTMLAreaElement.ts +++ b/packages/happy-dom/src/nodes/html-area-element/HTMLAreaElement.ts @@ -1,6 +1,6 @@ import HTMLElement from '../html-element/HTMLElement.js'; import * as PropertySymbol from '../../PropertySymbol.js'; -import DOMTokenList from '../element/DOMTokenList.js'; +import DOMTokenList from '../../dom/DOMTokenList.js'; import HTMLHyperlinkElementUtility from '../html-hyperlink-element/HTMLHyperlinkElementUtility.js'; import IHTMLHyperlinkElement from '../html-hyperlink-element/IHTMLHyperlinkElement.js'; import Event from '../../event/Event.js'; @@ -149,7 +149,11 @@ export default class HTMLAreaElement extends HTMLElement implements IHTMLHyperli */ public get relList(): DOMTokenList { if (!this[PropertySymbol.relList]) { - this[PropertySymbol.relList] = new DOMTokenList(this, 'rel'); + this[PropertySymbol.relList] = new DOMTokenList( + PropertySymbol.illegalConstructor, + this, + 'rel' + ); } return this[PropertySymbol.relList]; } diff --git a/packages/happy-dom/src/nodes/html-canvas-element/CanvasCaptureMediaStreamTrack.ts b/packages/happy-dom/src/nodes/html-canvas-element/CanvasCaptureMediaStreamTrack.ts index 163e717b0..3d0fa5047 100644 --- a/packages/happy-dom/src/nodes/html-canvas-element/CanvasCaptureMediaStreamTrack.ts +++ b/packages/happy-dom/src/nodes/html-canvas-element/CanvasCaptureMediaStreamTrack.ts @@ -13,13 +13,13 @@ export default class CanvasCaptureMediaStreamTrack extends MediaStreamTrack { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. - * @param [canvas] Canvas. + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param canvas Canvas. */ - constructor(illegalConstructorSymbol?: symbol, canvas: HTMLCanvasElement | null = null) { + constructor(illegalConstructorSymbol: symbol, canvas: HTMLCanvasElement) { super(illegalConstructorSymbol); - this[PropertySymbol.canvas] = canvas; + this[PropertySymbol.canvas] = canvas; } /** diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts index 7603452ec..ec77989a4 100644 --- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts +++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts @@ -5,7 +5,7 @@ import PointerEvent from '../../event/events/PointerEvent.js'; import NodeTypeEnum from '../node/NodeTypeEnum.js'; import Event from '../../event/Event.js'; import HTMLElementUtility from './HTMLElementUtility.js'; -import DOMStringMap from '../element/DOMStringMap.js'; +import DOMStringMap from '../../dom/DOMStringMap.js'; import WindowBrowserContext from '../../window/WindowBrowserContext.js'; /** @@ -330,7 +330,11 @@ export default class HTMLElement extends Element { */ public get style(): CSSStyleDeclaration { if (!this[PropertySymbol.style]) { - this[PropertySymbol.style] = new CSSStyleDeclaration(this); + this[PropertySymbol.style] = new CSSStyleDeclaration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { element: this } + ); } return this[PropertySymbol.style]; } @@ -351,7 +355,10 @@ export default class HTMLElement extends Element { * @returns Data set. */ public get dataset(): DOMStringMap { - return (this[PropertySymbol.dataset] ??= new DOMStringMap(this)); + return (this[PropertySymbol.dataset] ??= new DOMStringMap( + PropertySymbol.illegalConstructor, + this + )); } /** diff --git a/packages/happy-dom/src/nodes/html-form-element/HTMLFormControlsCollection.ts b/packages/happy-dom/src/nodes/html-form-element/HTMLFormControlsCollection.ts index e55c439b9..aa03624bf 100644 --- a/packages/happy-dom/src/nodes/html-form-element/HTMLFormControlsCollection.ts +++ b/packages/happy-dom/src/nodes/html-form-element/HTMLFormControlsCollection.ts @@ -18,10 +18,10 @@ export default class HTMLFormControlsCollection extends HTMLCollection< /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. - * @param [ownerElement] Form element. + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param ownerElement Form element. */ - constructor(illegalConstructorSymbol?: symbol, ownerElement: HTMLFormElement | null = null) { + constructor(illegalConstructorSymbol: symbol, ownerElement: HTMLFormElement) { super(illegalConstructorSymbol, () => ownerElement[PropertySymbol.getFormControlItems]()); this[PropertySymbol.ownerElement] = ownerElement; } diff --git a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts index 62af9b401..86b8fe922 100644 --- a/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts +++ b/packages/happy-dom/src/nodes/html-form-element/HTMLFormElement.ts @@ -12,11 +12,11 @@ import BrowserWindow from '../../window/BrowserWindow.js'; import THTMLFormControlElement from './THTMLFormControlElement.js'; import QuerySelector from '../../query-selector/QuerySelector.js'; import RadioNodeList from './RadioNodeList.js'; +import WindowBrowserContext from '../../window/WindowBrowserContext.js'; +import ClassMethodBinder from '../../ClassMethodBinder.js'; +import Node from '../node/Node.js'; import Element from '../element/Element.js'; import EventTarget from '../../event/EventTarget.js'; -import Node from '../node/Node.js'; -import ClassMethodBinder from '../../ClassMethodBinder.js'; -import WindowBrowserContext from '../../window/WindowBrowserContext.js'; /** * HTML Form Element. @@ -43,14 +43,13 @@ export default class HTMLFormElement extends HTMLElement { constructor() { super(); - ClassMethodBinder.bindMethods( - this, - [EventTarget, Node, Element, HTMLElement, HTMLFormElement], - { - bindSymbols: true, - forwardToPrototype: true - } - ); + const methodBinder = new ClassMethodBinder(this, [ + HTMLFormElement, + HTMLElement, + Element, + Node, + EventTarget + ]); const proxy = new Proxy(this, { get: (target, property) => { @@ -58,6 +57,7 @@ export default class HTMLFormElement extends HTMLElement { return target[PropertySymbol.getFormControlItems]().length; } if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } const index = Number(property); @@ -67,6 +67,7 @@ export default class HTMLFormElement extends HTMLElement { return target[PropertySymbol.getFormControlNamedItem](property) || undefined; }, set(target, property, newValue): boolean { + methodBinder.bind(property); if (typeof property === 'symbol') { target[property] = newValue; return true; @@ -127,6 +128,8 @@ export default class HTMLFormElement extends HTMLElement { return false; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + if (!descriptor.value) { Object.defineProperty(target, property, descriptor); return true; diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts index 7a6225a9f..073d22c8f 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElement.ts @@ -5,7 +5,7 @@ import Document from '../document/Document.js'; import HTMLElement from '../html-element/HTMLElement.js'; import CrossOriginBrowserWindow from '../../window/CrossOriginBrowserWindow.js'; import IBrowserFrame from '../../browser/types/IBrowserFrame.js'; -import DOMTokenList from '../element/DOMTokenList.js'; +import DOMTokenList from '../../dom/DOMTokenList.js'; import Attr from '../attr/Attr.js'; import BrowserFrameFactory from '../../browser/utilities/BrowserFrameFactory.js'; import BrowserFrameURL from '../../browser/utilities/BrowserFrameURL.js'; @@ -160,7 +160,11 @@ export default class HTMLIFrameElement extends HTMLElement { */ public get sandbox(): DOMTokenList { if (!this[PropertySymbol.sandbox]) { - this[PropertySymbol.sandbox] = new DOMTokenList(this, 'sandbox'); + this[PropertySymbol.sandbox] = new DOMTokenList( + PropertySymbol.illegalConstructor, + this, + 'sandbox' + ); } return this[PropertySymbol.sandbox]; } diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts index a5319f3ef..35d4e2201 100644 --- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts +++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts @@ -3,7 +3,7 @@ import * as PropertySymbol from '../../PropertySymbol.js'; import HTMLElement from '../html-element/HTMLElement.js'; import Event from '../../event/Event.js'; import ErrorEvent from '../../event/events/ErrorEvent.js'; -import DOMTokenList from '../element/DOMTokenList.js'; +import DOMTokenList from '../../dom/DOMTokenList.js'; import Attr from '../attr/Attr.js'; import WindowErrorUtility from '../../window/WindowErrorUtility.js'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; @@ -42,7 +42,11 @@ export default class HTMLLinkElement extends HTMLElement { */ public get relList(): DOMTokenList { if (!this[PropertySymbol.relList]) { - this[PropertySymbol.relList] = new DOMTokenList(this, 'rel'); + this[PropertySymbol.relList] = new DOMTokenList( + PropertySymbol.illegalConstructor, + this, + 'rel' + ); } return this[PropertySymbol.relList]; } diff --git a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts index 5d579d350..33fa57d7f 100644 --- a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts +++ b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts @@ -3,7 +3,7 @@ import Event from '../../event/Event.js'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import HTMLElement from '../html-element/HTMLElement.js'; import TimeRanges from './TimeRanges.js'; -import DOMTokenList from '../element/DOMTokenList.js'; +import DOMTokenList from '../../dom/DOMTokenList.js'; import RemotePlayback from './RemotePlayback.js'; import MediaStream from './MediaStream.js'; import TextTrackList from './TextTrackList.js'; @@ -299,7 +299,11 @@ export default class HTMLMediaElement extends HTMLElement { */ public get controlsList(): DOMTokenList { if (this[PropertySymbol.controlsList] === null) { - this[PropertySymbol.controlsList] = new DOMTokenList(this, 'controlslist'); + this[PropertySymbol.controlsList] = new DOMTokenList( + PropertySymbol.illegalConstructor, + this, + 'controlslist' + ); } return this[PropertySymbol.controlsList]; diff --git a/packages/happy-dom/src/nodes/html-media-element/MediaStreamTrack.ts b/packages/happy-dom/src/nodes/html-media-element/MediaStreamTrack.ts index 995960286..68f62dedc 100644 --- a/packages/happy-dom/src/nodes/html-media-element/MediaStreamTrack.ts +++ b/packages/happy-dom/src/nodes/html-media-element/MediaStreamTrack.ts @@ -66,9 +66,9 @@ export default class MediaStreamTrack extends EventTarget { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. + * @param illegalConstructorSymbol Illegal constructor symbol. */ - constructor(illegalConstructorSymbol?: symbol) { + constructor(illegalConstructorSymbol: symbol) { super(); if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { diff --git a/packages/happy-dom/src/nodes/html-media-element/TextTrack.ts b/packages/happy-dom/src/nodes/html-media-element/TextTrack.ts index 718dc58a5..2a3bc94bf 100644 --- a/packages/happy-dom/src/nodes/html-media-element/TextTrack.ts +++ b/packages/happy-dom/src/nodes/html-media-element/TextTrack.ts @@ -30,9 +30,9 @@ export default class TextTrack extends EventTarget { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. + * @param illegalConstructorSymbol Illegal constructor symbol. */ - constructor(illegalConstructorSymbol?: symbol) { + constructor(illegalConstructorSymbol: symbol) { super(); if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { diff --git a/packages/happy-dom/src/nodes/html-media-element/TextTrackCue.ts b/packages/happy-dom/src/nodes/html-media-element/TextTrackCue.ts index 484b8bc49..8899526b0 100644 --- a/packages/happy-dom/src/nodes/html-media-element/TextTrackCue.ts +++ b/packages/happy-dom/src/nodes/html-media-element/TextTrackCue.ts @@ -25,9 +25,9 @@ export default abstract class TextTrackCue extends EventTarget { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. + * @param illegalConstructorSymbol Illegal constructor symbol. */ - constructor(illegalConstructorSymbol?: symbol) { + constructor(illegalConstructorSymbol: symbol) { super(); if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { throw new TypeError('Illegal constructor'); diff --git a/packages/happy-dom/src/nodes/html-media-element/TextTrackList.ts b/packages/happy-dom/src/nodes/html-media-element/TextTrackList.ts index bedd0d467..5493d2c5c 100644 --- a/packages/happy-dom/src/nodes/html-media-element/TextTrackList.ts +++ b/packages/happy-dom/src/nodes/html-media-element/TextTrackList.ts @@ -20,10 +20,10 @@ export default class TextTrackList extends EventTarget { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. - * @param [items] Items. + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param items Items. */ - constructor(illegalConstructorSymbol?: symbol, items: TextTrack[] = []) { + constructor(illegalConstructorSymbol: symbol, items: TextTrack[]) { super(); if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { @@ -32,11 +32,7 @@ export default class TextTrackList extends EventTarget { this[PropertySymbol.items] = items; - // This only works for one level of inheritance, but it should be fine as there is no collection that goes deeper according to spec. - ClassMethodBinder.bindMethods(this, [TextTrackList], { - bindSymbols: true, - forwardToPrototype: true - }); + const methodBinder = new ClassMethodBinder(this, [TextTrackList, EventTarget]); return new Proxy(this, { get: (target, property) => { @@ -44,6 +40,7 @@ export default class TextTrackList extends EventTarget { return items.length; } if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } const index = Number(property); @@ -52,6 +49,7 @@ export default class TextTrackList extends EventTarget { } }, set(target, property, newValue): boolean { + methodBinder.bind(property); if (typeof property === 'symbol') { target[property] = newValue; return true; @@ -82,10 +80,16 @@ export default class TextTrackList extends EventTarget { return true; } + if (typeof property === 'symbol') { + return false; + } + const index = Number(property); return !isNaN(index) && index >= 0 && index < items.length; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + if (property in target) { Object.defineProperty(target, property, descriptor); return true; diff --git a/packages/happy-dom/src/nodes/html-media-element/TimeRanges.ts b/packages/happy-dom/src/nodes/html-media-element/TimeRanges.ts index 901a7d1c2..9221d0d73 100644 --- a/packages/happy-dom/src/nodes/html-media-element/TimeRanges.ts +++ b/packages/happy-dom/src/nodes/html-media-element/TimeRanges.ts @@ -9,9 +9,9 @@ export default class TimeRanges { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. + * @param illegalConstructorSymbol Illegal constructor symbol. */ - constructor(illegalConstructorSymbol?: symbol) { + constructor(illegalConstructorSymbol: symbol) { if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { throw new TypeError('Illegal constructor'); } diff --git a/packages/happy-dom/src/nodes/html-select-element/HTMLOptionsCollection.ts b/packages/happy-dom/src/nodes/html-select-element/HTMLOptionsCollection.ts index 8d4bafd07..7aa5c8aef 100644 --- a/packages/happy-dom/src/nodes/html-select-element/HTMLOptionsCollection.ts +++ b/packages/happy-dom/src/nodes/html-select-element/HTMLOptionsCollection.ts @@ -16,10 +16,10 @@ export default class HTMLOptionsCollection extends HTMLCollection diff --git a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts index adfa57dee..24e57d004 100644 --- a/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts +++ b/packages/happy-dom/src/nodes/html-select-element/HTMLSelectElement.ts @@ -12,8 +12,8 @@ import NodeTypeEnum from '../node/NodeTypeEnum.js'; import QuerySelector from '../../query-selector/QuerySelector.js'; import NodeList from '../node/NodeList.js'; import ClassMethodBinder from '../../ClassMethodBinder.js'; -import Element from '../element/Element.js'; import Node from '../node/Node.js'; +import Element from '../element/Element.js'; import EventTarget from '../../event/EventTarget.js'; /** @@ -40,18 +40,18 @@ export default class HTMLSelectElement extends HTMLElement { constructor() { super(); - ClassMethodBinder.bindMethods( - this, - [EventTarget, Node, Element, HTMLElement, HTMLSelectElement], - { - bindSymbols: true, - forwardToPrototype: true - } - ); + const methodBinder = new ClassMethodBinder(this, [ + HTMLSelectElement, + HTMLElement, + Element, + Node, + EventTarget + ]); const proxy = new Proxy(this, { get: (target, property) => { if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } const index = Number(property); @@ -60,6 +60,8 @@ export default class HTMLSelectElement extends HTMLElement { } }, set(target, property, newValue): boolean { + methodBinder.bind(property); + if (typeof property === 'symbol') { target[property] = newValue; return true; @@ -131,6 +133,8 @@ export default class HTMLSelectElement extends HTMLElement { return false; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + const index = Number(property); if (isNaN(index)) { diff --git a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts index e142d2765..a66ae59d9 100644 --- a/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts +++ b/packages/happy-dom/src/nodes/html-style-element/HTMLStyleElement.ts @@ -11,15 +11,7 @@ import HTMLElement from '../html-element/HTMLElement.js'; export default class HTMLStyleElement extends HTMLElement { private [PropertySymbol.sheet]: CSSStyleSheet | null = null; public [PropertySymbol.styleNode] = this; - - /** - * Returns CSS style sheet. - * - * @returns CSS style sheet. - */ - public get sheet(): CSSStyleSheet { - return this[PropertySymbol.sheet] ? this[PropertySymbol.sheet] : null; - } + public [PropertySymbol.disabled] = false; /** * Returns media. @@ -42,6 +34,7 @@ export default class HTMLStyleElement extends HTMLElement { /** * Returns type. * + * @deprecated * @returns Type. */ public get type(): string { @@ -51,6 +44,7 @@ export default class HTMLStyleElement extends HTMLElement { /** * Sets type. * + * @deprecated * @param type Type. */ public set type(type: string) { @@ -63,7 +57,7 @@ export default class HTMLStyleElement extends HTMLElement { * @returns Disabled. */ public get disabled(): boolean { - return this.getAttribute('disabled') !== null; + return this[PropertySymbol.disabled]; } /** @@ -72,20 +66,23 @@ export default class HTMLStyleElement extends HTMLElement { * @param disabled Disabled. */ public set disabled(disabled: boolean) { - if (!disabled) { - this.removeAttribute('disabled'); - } else { - this.setAttribute('disabled', ''); - } + this[PropertySymbol.disabled] = Boolean(disabled); } /** - * @override + * Returns CSS style sheet. + * + * @returns CSS style sheet. */ - public override [PropertySymbol.connectedToDocument](): void { - super[PropertySymbol.connectedToDocument](); - this[PropertySymbol.sheet] = new CSSStyleSheet(); - this[PropertySymbol.sheet].replaceSync(this.textContent); + public get sheet(): CSSStyleSheet { + if (!this[PropertySymbol.isConnected]) { + return null; + } + if (!this[PropertySymbol.sheet]) { + this[PropertySymbol.sheet] = new CSSStyleSheet(); + this[PropertySymbol.sheet].replaceSync(this.textContent); + } + return this[PropertySymbol.sheet]; } /** diff --git a/packages/happy-dom/src/nodes/html-table-cell-element/HTMLTableCellElement.ts b/packages/happy-dom/src/nodes/html-table-cell-element/HTMLTableCellElement.ts index 8070dca45..d4383b07b 100644 --- a/packages/happy-dom/src/nodes/html-table-cell-element/HTMLTableCellElement.ts +++ b/packages/happy-dom/src/nodes/html-table-cell-element/HTMLTableCellElement.ts @@ -1,7 +1,7 @@ import QuerySelector from '../../query-selector/QuerySelector.js'; import HTMLElement from '../html-element/HTMLElement.js'; import * as PropertySymbol from '../../PropertySymbol.js'; -import DOMTokenList from '../element/DOMTokenList.js'; +import DOMTokenList from '../../dom/DOMTokenList.js'; /** * HTMLTableCellElement @@ -75,7 +75,11 @@ export default class HTMLTableCellElement extends HTMLElement { */ public get headers(): DOMTokenList { if (!this[PropertySymbol.headers]) { - this[PropertySymbol.headers] = new DOMTokenList(this, 'headers'); + this[PropertySymbol.headers] = new DOMTokenList( + PropertySymbol.illegalConstructor, + this, + 'headers' + ); } return this[PropertySymbol.headers]; } diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index 9406c9e19..ffa3e72da 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -16,7 +16,6 @@ import ICachedQuerySelectorResult from './ICachedQuerySelectorResult.js'; import ICachedMatchesResult from './ICachedMatchesResult.js'; import ICachedElementsByTagNameResult from './ICachedElementsByTagNameResult.js'; import ICachedElementByTagNameResult from './ICachedElementByTagNameResult.js'; -import ICachedStyleResult from './ICachedStyleResult.js'; import ICachedComputedStyleResult from './ICachedComputedStyleResult.js'; import ICachedResult from './ICachedResult.js'; import ICachedElementByIdResult from './ICachedElementByIdResult.js'; @@ -27,6 +26,7 @@ import HTMLTextAreaElement from '../html-text-area-element/HTMLTextAreaElement.j import HTMLSlotElement from '../html-slot-element/HTMLSlotElement.js'; import WindowBrowserContext from '../../window/WindowBrowserContext.js'; import NodeFactory from '../NodeFactory.js'; +import SVGStyleElement from '../svg-style-element/SVGStyleElement.js'; /** * Node. @@ -75,7 +75,7 @@ export default class Node extends EventTarget { public [PropertySymbol.parentNode]: Node | null = null; public [PropertySymbol.nodeType]: NodeTypeEnum; public [PropertySymbol.rootNode]: Node = null; - public [PropertySymbol.styleNode]: HTMLStyleElement | null = null; + public [PropertySymbol.styleNode]: HTMLStyleElement | SVGStyleElement | null = null; public [PropertySymbol.textAreaNode]: HTMLTextAreaElement | null = null; public [PropertySymbol.formNode]: HTMLFormElement | null = null; public [PropertySymbol.selectNode]: HTMLSelectElement | null = null; @@ -92,7 +92,6 @@ export default class Node extends EventTarget { elementsByTagNameNS: Map; elementByTagName: Map; elementById: Map; - style: ICachedStyleResult | null; computedStyle: ICachedComputedStyleResult | null; } = { querySelector: new Map(), @@ -102,7 +101,6 @@ export default class Node extends EventTarget { elementsByTagNameNS: new Map(), elementByTagName: new Map(), elementById: new Map(), - style: null, computedStyle: null }; public [PropertySymbol.affectsCache]: ICachedResult[] = []; diff --git a/packages/happy-dom/src/nodes/node/NodeList.ts b/packages/happy-dom/src/nodes/node/NodeList.ts index 2055f7d5b..03dac3d08 100644 --- a/packages/happy-dom/src/nodes/node/NodeList.ts +++ b/packages/happy-dom/src/nodes/node/NodeList.ts @@ -14,22 +14,28 @@ class NodeList { /** * Constructor. * - * @param [illegalConstructorSymbol] Illegal constructor symbol. - * @param [items] Items. + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param items Items. */ - constructor(illegalConstructorSymbol?: symbol, items: T[] = []) { + constructor(illegalConstructorSymbol: symbol, items: T[]) { if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { throw new TypeError('Illegal constructor'); } this[PropertySymbol.items] = items; + const methodBinder = new ClassMethodBinder( + this, + this.constructor !== NodeList ? [this.constructor, NodeList] : [NodeList] + ); + const proxy = new Proxy(this, { get: (target, property) => { if (property === 'length') { return items.length; } if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } const index = Number(property); @@ -38,6 +44,8 @@ class NodeList { } }, set(target, property, newValue): boolean { + methodBinder.bind(property); + if (typeof property === 'symbol') { target[property] = newValue; return true; @@ -68,10 +76,16 @@ class NodeList { return true; } + if (typeof property === 'symbol') { + return false; + } + const index = Number(property); return !isNaN(index) && index >= 0 && index < items.length; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + if (property in target) { Object.defineProperty(target, property, descriptor); return true; @@ -97,13 +111,6 @@ class NodeList { } }); - // This only works for one level of inheritance, but it should be fine as there is no collection that goes deeper according to spec. - ClassMethodBinder.bindMethods( - this, - this.constructor !== NodeList ? [NodeList, this.constructor] : [NodeList], - { bindSymbols: true, proxy } - ); - return proxy; } diff --git a/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts b/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts index 3cb96acd3..75dc2e51b 100644 --- a/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts +++ b/packages/happy-dom/src/nodes/parent-node/ParentNodeUtility.ts @@ -110,7 +110,7 @@ export default class ParentNodeUtility { const elements: T[] = []; for (const element of (parent)[PropertySymbol.elementArray]) { - if (includeAll || element[PropertySymbol.tagName] === upperTagName) { + if (includeAll || element[PropertySymbol.tagName].toUpperCase() === upperTagName) { elements.push(element); } diff --git a/packages/happy-dom/src/nodes/svg-animate-element/SVGAnimateElement.ts b/packages/happy-dom/src/nodes/svg-animate-element/SVGAnimateElement.ts new file mode 100644 index 000000000..f543e4da8 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-animate-element/SVGAnimateElement.ts @@ -0,0 +1,8 @@ +import SVGAnimationElement from '../svg-animation-element/SVGAnimationElement.js'; + +/** + * SVG Animate Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimateElement + */ +export default class SVGAnimateElement extends SVGAnimationElement {} diff --git a/packages/happy-dom/src/nodes/svg-animate-motion-element copy/SVGAnimateMotionElement.test.ts b/packages/happy-dom/src/nodes/svg-animate-motion-element copy/SVGAnimateMotionElement.test.ts new file mode 100644 index 000000000..87f841354 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-animate-motion-element copy/SVGAnimateMotionElement.test.ts @@ -0,0 +1,27 @@ +import SVGAnimateMotionElement from '../../../src/nodes/svg-animate-motion-element/SVGAnimateMotionElement.js'; +import Window from '../../../src/window/Window.js'; +import Document from '../../../src/nodes/document/Document.js'; +import { beforeEach, describe, it, expect } from 'vitest'; +import SVGAnimationElement from '../../../src/nodes/svg-animation-element/SVGAnimationElement.js'; + +describe('SVGAnimateMotionElement', () => { + let window: Window; + let document: Document; + let element: SVGAnimateMotionElement; + + beforeEach(() => { + window = new Window(); + document = window.document; + element = document.createElementNS('http://www.w3.org/2000/svg', 'animateMotion'); + }); + + describe('constructor()', () => { + it('Should be an instanceof SVGAnimateMotionElement', () => { + expect(element instanceof SVGAnimateMotionElement).toBe(true); + }); + + it('Should be an instanceof SVGAnimationElement', () => { + expect(element instanceof SVGAnimationElement).toBe(true); + }); + }); +}); diff --git a/packages/happy-dom/src/nodes/svg-animate-motion-element/SVGAnimateMotionElement.ts b/packages/happy-dom/src/nodes/svg-animate-motion-element/SVGAnimateMotionElement.ts new file mode 100644 index 000000000..eaa24d403 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-animate-motion-element/SVGAnimateMotionElement.ts @@ -0,0 +1,8 @@ +import SVGAnimationElement from '../svg-animation-element/SVGAnimationElement.js'; + +/** + * SVG Animate Element Motion. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimateMotionElement + */ +export default class SVGAnimateMotionElement extends SVGAnimationElement {} diff --git a/packages/happy-dom/src/nodes/svg-animate-transform-element/SVGAnimateTransformElement.ts b/packages/happy-dom/src/nodes/svg-animate-transform-element/SVGAnimateTransformElement.ts new file mode 100644 index 000000000..a3b0b0194 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-animate-transform-element/SVGAnimateTransformElement.ts @@ -0,0 +1,8 @@ +import SVGAnimationElement from '../svg-animation-element/SVGAnimationElement.js'; + +/** + * SVG Animate Element Transform. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimateTransformElement + */ +export default class SVGAnimateTransformElement extends SVGAnimationElement {} diff --git a/packages/happy-dom/src/nodes/svg-animation-element/SVGAnimationElement.ts b/packages/happy-dom/src/nodes/svg-animation-element/SVGAnimationElement.ts new file mode 100644 index 000000000..8b6ebffb4 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-animation-element/SVGAnimationElement.ts @@ -0,0 +1,68 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import SVGStringList from '../../svg/SVGStringList.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import Event from '../../event/Event.js'; + +/** + * SVG Animation Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimationElement + */ +export default class SVGAnimationElement extends SVGElement { + // Internal properties + public [PropertySymbol.requiredExtensions]: SVGStringList | null = null; + public [PropertySymbol.systemLanguage]: SVGStringList | null = null; + + // Events + public onbegin: (event: Event) => void | null = null; + public onend: (event: Event) => void | null = null; + public onrepeat: (event: Event) => void | null = null; + + /** + * Returns required extensions. + * + * @returns Required extensions. + */ + public get requiredExtensions(): SVGStringList { + if (!this[PropertySymbol.requiredExtensions]) { + this[PropertySymbol.requiredExtensions] = new SVGStringList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('requiredExtensions'), + setAttribute: (value) => this.setAttribute('requiredExtensions', value) + } + ); + } + return this[PropertySymbol.requiredExtensions]; + } + + /** + * Returns system language. + * + * @returns System language. + */ + public get systemLanguage(): SVGStringList { + if (!this[PropertySymbol.systemLanguage]) { + this[PropertySymbol.systemLanguage] = new SVGStringList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('systemLanguage'), + setAttribute: (value) => this.setAttribute('systemLanguage', value) + } + ); + } + return this[PropertySymbol.systemLanguage]; + } + + /** + * Returns target element. + * + * @returns Target element. + */ + public get targetElement(): SVGElement | null { + // TODO: Implement targetElement + return null; + } +} diff --git a/packages/happy-dom/src/nodes/svg-circle-element/SVGCircleElement.ts b/packages/happy-dom/src/nodes/svg-circle-element/SVGCircleElement.ts new file mode 100644 index 000000000..76928cc0a --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-circle-element/SVGCircleElement.ts @@ -0,0 +1,72 @@ +import SVGGeometryElement from '../svg-geometry-element/SVGGeometryElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVG Circle Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGCircleElement + */ +export default class SVGCircleElement extends SVGGeometryElement { + // Internal properties + public [PropertySymbol.cx]: SVGAnimatedLength | null = null; + public [PropertySymbol.cy]: SVGAnimatedLength | null = null; + public [PropertySymbol.r]: SVGAnimatedLength | null = null; + + /** + * Returns cx. + * + * @returns Cx. + */ + public get cx(): SVGAnimatedLength { + if (!this[PropertySymbol.cx]) { + this[PropertySymbol.cx] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('cx'), + setAttribute: (value) => this.setAttribute('cx', value) + } + ); + } + return this[PropertySymbol.cx]; + } + + /** + * Returns cy. + * + * @returns Cy. + */ + public get cy(): SVGAnimatedLength { + if (!this[PropertySymbol.cy]) { + this[PropertySymbol.cy] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('cy'), + setAttribute: (value) => this.setAttribute('cy', value) + } + ); + } + return this[PropertySymbol.cy]; + } + + /** + * Returns r. + * + * @returns R. + */ + public get r(): SVGAnimatedLength { + if (!this[PropertySymbol.r]) { + this[PropertySymbol.r] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('r'), + setAttribute: (value) => this.setAttribute('r', value) + } + ); + } + return this[PropertySymbol.r]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-clip-path-element/SVGClipPathElement.ts b/packages/happy-dom/src/nodes/svg-clip-path-element/SVGClipPathElement.ts new file mode 100644 index 000000000..1f44f51d0 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-clip-path-element/SVGClipPathElement.ts @@ -0,0 +1,34 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; + +/** + * SVG ClipPath Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGClipPathElement + */ +export default class SVGClipPathElement extends SVGElement { + // Internal properties + public [PropertySymbol.clipPathUnits]: SVGAnimatedEnumeration | null = null; + + /** + * Returns clipPathUnits. + * + * @returns ClipPathUnits. + */ + public get clipPathUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.clipPathUnits]) { + this[PropertySymbol.clipPathUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('clipPathUnits'), + setAttribute: (value) => this.setAttribute('clipPathUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'userSpaceOnUse' + } + ); + } + return this[PropertySymbol.clipPathUnits]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-component-transfer-function-element/SVGComponentTransferFunctionElement.ts b/packages/happy-dom/src/nodes/svg-component-transfer-function-element/SVGComponentTransferFunctionElement.ts new file mode 100644 index 000000000..802a31022 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-component-transfer-function-element/SVGComponentTransferFunctionElement.ts @@ -0,0 +1,167 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedNumberList from '../../svg/SVGAnimatedNumberList.js'; + +/** + * SVGComponentTransferFunctionElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGComponentTransferFunctionElement + */ +export default class SVGComponentTransferFunctionElement extends SVGElement { + // Static properties + public static SVG_FECOMPONENTTRANSFER_TYPE_UNKNOWN = 0; + public static SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY = 1; + public static SVG_FECOMPONENTTRANSFER_TYPE_TABLE = 2; + public static SVG_FECOMPONENTTRANSFER_TYPE_DISCRETE = 3; + public static SVG_FECOMPONENTTRANSFER_TYPE_LINEAR = 4; + public static SVG_FECOMPONENTTRANSFER_TYPE_GAMMA = 5; + + // Internal properties + public [PropertySymbol.type]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.tableValues]: SVGAnimatedNumberList | null = null; + public [PropertySymbol.slope]: SVGAnimatedNumber | null = null; + public [PropertySymbol.intercept]: SVGAnimatedNumber | null = null; + public [PropertySymbol.amplitude]: SVGAnimatedNumber | null = null; + public [PropertySymbol.exponent]: SVGAnimatedNumber | null = null; + public [PropertySymbol.offset]: SVGAnimatedNumber | null = null; + + /** + * Returns type. + * + * @returns Type. + */ + public get type(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.type]) { + this[PropertySymbol.type] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('type'), + setAttribute: (value) => this.setAttribute('type', value), + values: ['identity', 'table', 'discrete', 'linear', 'gamma'], + defaultValue: 'identity' + } + ); + } + return this[PropertySymbol.type]; + } + + /** + * Returns table values. + * + * @returns Table values. + */ + public get tableValues(): SVGAnimatedNumberList { + if (!this[PropertySymbol.tableValues]) { + this[PropertySymbol.tableValues] = new SVGAnimatedNumberList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('tableValues'), + setAttribute: (value) => this.setAttribute('tableValues', value) + } + ); + } + return this[PropertySymbol.tableValues]; + } + + /** + * Returns slope. + * + * @returns Slope. + */ + public get slope(): SVGAnimatedNumber { + if (!this[PropertySymbol.slope]) { + this[PropertySymbol.slope] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('slope'), + setAttribute: (value) => this.setAttribute('slope', value), + defaultValue: 1 + } + ); + } + return this[PropertySymbol.slope]; + } + + /** + * Returns intercept. + * + * @returns Intercept. + */ + public get intercept(): SVGAnimatedNumber { + if (!this[PropertySymbol.intercept]) { + this[PropertySymbol.intercept] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('intercept'), + setAttribute: (value) => this.setAttribute('intercept', value) + } + ); + } + return this[PropertySymbol.intercept]; + } + + /** + * Returns amplitude. + * + * @returns Amplitude. + */ + public get amplitude(): SVGAnimatedNumber { + if (!this[PropertySymbol.amplitude]) { + this[PropertySymbol.amplitude] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('amplitude'), + setAttribute: (value) => this.setAttribute('amplitude', value), + defaultValue: 1 + } + ); + } + return this[PropertySymbol.amplitude]; + } + + /** + * Returns exponent. + * + * @returns Exponent. + */ + public get exponent(): SVGAnimatedNumber { + if (!this[PropertySymbol.exponent]) { + this[PropertySymbol.exponent] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('exponent'), + setAttribute: (value) => this.setAttribute('exponent', value), + defaultValue: 1 + } + ); + } + return this[PropertySymbol.exponent]; + } + + /** + * Returns offset. + * + * @returns Offset. + */ + public get offset(): SVGAnimatedNumber { + if (!this[PropertySymbol.offset]) { + this[PropertySymbol.offset] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('offset'), + setAttribute: (value) => this.setAttribute('offset', value) + } + ); + } + return this[PropertySymbol.offset]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-defs-element/SVGDefsElement.ts b/packages/happy-dom/src/nodes/svg-defs-element/SVGDefsElement.ts new file mode 100644 index 000000000..d7534be54 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-defs-element/SVGDefsElement.ts @@ -0,0 +1,8 @@ +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG Defs Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGDefsElement + */ +export default class SVGDefsElement extends SVGGraphicsElement {} diff --git a/packages/happy-dom/src/nodes/svg-desc-element/SVGDescElement.ts b/packages/happy-dom/src/nodes/svg-desc-element/SVGDescElement.ts new file mode 100644 index 000000000..7bfe2f4a4 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-desc-element/SVGDescElement.ts @@ -0,0 +1,8 @@ +import SVGElement from '../svg-element/SVGElement.js'; + +/** + * SVG Desc Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGDescElement + */ +export default class SVGDescElement extends SVGElement {} diff --git a/packages/happy-dom/src/nodes/svg-document/SVGDocument.ts b/packages/happy-dom/src/nodes/svg-document/SVGDocument.ts deleted file mode 100644 index 09ac11db0..000000000 --- a/packages/happy-dom/src/nodes/svg-document/SVGDocument.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Document from '../document/Document.js'; - -/** - * Document. - */ -export default class SVGDocument extends Document {} diff --git a/packages/happy-dom/src/nodes/svg-element/DOMMatrix.ts b/packages/happy-dom/src/nodes/svg-element/DOMMatrix.ts deleted file mode 100644 index 2373d7b6a..000000000 --- a/packages/happy-dom/src/nodes/svg-element/DOMMatrix.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * DOM Matrix. - * - * TODO: Not fully implemented. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix - */ -export default class DOMMatrix { - public readonly is2D = false; - public readonly isIdentity = false; -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGAngle.ts b/packages/happy-dom/src/nodes/svg-element/SVGAngle.ts deleted file mode 100644 index 619debd96..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGAngle.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * SVG angle. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAngle - */ -export default class SVGAngle { - public static SVG_ANGLETYPE_UNKNOWN = 'unknown'; - public static SVG_ANGLETYPE_UNSPECIFIED = 'unspecified'; - public static SVG_ANGLETYPE_DEG = '0deg'; - public static SVG_ANGLETYPE_RAD = '0rad'; - public static SVG_ANGLETYPE_GRAD = '0grad'; - - public unitType = ''; - public value = 0; - public valueInSpecifiedUnits = 0; - public valueAsString = ''; - - /** - * New value specific units. - */ - public newValueSpecifiedUnits(): void {} - - /** - * Convert to specific units. - */ - public convertToSpecifiedUnits(): void {} -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGAnimatedRect.ts b/packages/happy-dom/src/nodes/svg-element/SVGAnimatedRect.ts deleted file mode 100644 index 3b5cbe349..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGAnimatedRect.ts +++ /dev/null @@ -1,11 +0,0 @@ -import SVGRect from './SVGRect.js'; - -/** - * Rect object. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedRect - */ -export default class SVGAnimatedRect { - public baseVal: SVGRect = new SVGRect(); - public animVal: SVGRect = new SVGRect(); -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts index 6c276df54..8af6b0129 100644 --- a/packages/happy-dom/src/nodes/svg-element/SVGElement.ts +++ b/packages/happy-dom/src/nodes/svg-element/SVGElement.ts @@ -1,10 +1,10 @@ import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import Element from '../element/Element.js'; -import SVGSVGElement from './SVGSVGElement.js'; +import SVGSVGElement from '../svg-svg-element/SVGSVGElement.js'; import Event from '../../event/Event.js'; import HTMLElementUtility from '../html-element/HTMLElementUtility.js'; -import DOMStringMap from '../element/DOMStringMap.js'; +import DOMStringMap from '../../dom/DOMStringMap.js'; /** * SVG Element. @@ -27,21 +27,12 @@ export default class SVGElement extends Element { // Private properties #dataset: DOMStringMap | null = null; - /** - * Returns viewport. - * - * @returns SVG rect. - */ - public get viewportElement(): SVGElement { - return null; - } - /** * Returns current translate. * * @returns Element. */ - public get ownerSVGElement(): SVGSVGElement { + public get ownerSVGElement(): SVGSVGElement | null { let parent = this[PropertySymbol.parentNode]; while (parent) { if (parent[PropertySymbol.localName] === 'svg') { @@ -53,13 +44,22 @@ export default class SVGElement extends Element { return null; } + /** + * Returns the SVGElement which established the current viewport. Often the nearest ancestor element. null if the given element is the outermost element. + * + * @returns SVG element. + */ + public get viewportElement(): SVGElement | null { + return this.ownerSVGElement; + } + /** * Returns data set. * * @returns Data set. */ public get dataset(): DOMStringMap { - return (this.#dataset ??= new DOMStringMap(this)); + return (this.#dataset ??= new DOMStringMap(PropertySymbol.illegalConstructor, this)); } /** @@ -69,7 +69,11 @@ export default class SVGElement extends Element { */ public get style(): CSSStyleDeclaration { if (!this[PropertySymbol.style]) { - this[PropertySymbol.style] = new CSSStyleDeclaration(this); + this[PropertySymbol.style] = new CSSStyleDeclaration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { element: this } + ); } return this[PropertySymbol.style]; } diff --git a/packages/happy-dom/src/nodes/svg-element/SVGGraphicsElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGGraphicsElement.ts deleted file mode 100644 index c9ca352ca..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGGraphicsElement.ts +++ /dev/null @@ -1,39 +0,0 @@ -import SVGElement from './SVGElement.js'; -import DOMRect from '../element/DOMRect.js'; -import DOMMatrix from './DOMMatrix.js'; - -/** - * SVG Graphics Element. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement - */ -export default class SVGGraphicsElement extends SVGElement { - public readonly transform = {}; - - /** - * Returns DOM rect. - * - * @returns DOM rect. - */ - public getBBox(): DOMRect { - return new DOMRect(); - } - - /** - * Returns CTM. - * - * @returns CTM. - */ - public getCTM(): DOMMatrix { - return new DOMMatrix(); - } - - /** - * Returns screen CTM. - * - * @returns Screen CTM. - */ - public getScreenCTM(): DOMMatrix { - return new DOMMatrix(); - } -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGLength.ts b/packages/happy-dom/src/nodes/svg-element/SVGLength.ts deleted file mode 100644 index f85709503..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGLength.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * SVG length. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGLength - */ -export default class SVGLength { - public static SVG_LENGTHTYPE_UNKNOWN = 0; - public static SVG_LENGTHTYPE_NUMBER = 1; - public static SVG_LENGTHTYPE_PERCENTAGE = 2; - public static SVG_LENGTHTYPE_EMS = 3; - public static SVG_LENGTHTYPE_EXS = 4; - public static SVG_LENGTHTYPE_PX = 5; - public static SVG_LENGTHTYPE_CM = 6; - public static SVG_LENGTHTYPE_MM = 7; - public static SVG_LENGTHTYPE_IN = 8; - public static SVG_LENGTHTYPE_PT = 9; - public static SVG_LENGTHTYPE_PC = 10; - public unitType = ''; - public value = 0; - public valueInSpecifiedUnits = 0; - public valueAsString = ''; - - /** - * New value specific units. - */ - public newValueSpecifiedUnits(): void {} - - /** - * Convert to specific units. - */ - public convertToSpecifiedUnits(): void {} -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGNumber.ts b/packages/happy-dom/src/nodes/svg-element/SVGNumber.ts deleted file mode 100644 index 5d2a81734..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGNumber.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * SVG number. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGNumber - */ -export default class SVGNumber { - public value = 0; -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGPoint.ts b/packages/happy-dom/src/nodes/svg-element/SVGPoint.ts deleted file mode 100644 index cd6360737..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGPoint.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * SVG point. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPoint - */ -export default class SVGPoint { - public x = 0; - public y = 0; -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGRect.ts b/packages/happy-dom/src/nodes/svg-element/SVGRect.ts deleted file mode 100644 index d06e4df54..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGRect.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Rect object. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGRect - */ -export default class SVGRect { - public x = 0; - public y = 0; - public width = 0; - public height = 0; -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGSVGElement.ts b/packages/happy-dom/src/nodes/svg-element/SVGSVGElement.ts deleted file mode 100644 index 7fa8857ba..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGSVGElement.ts +++ /dev/null @@ -1,338 +0,0 @@ -import SVGGraphicsElement from './SVGGraphicsElement.js'; -import SVGRect from './SVGRect.js'; -import SVGPoint from './SVGPoint.js'; -import SVGLength from './SVGLength.js'; -import SVGAngle from './SVGAngle.js'; -import SVGNumber from './SVGNumber.js'; -import SVGTransform from './SVGTransform.js'; -import SVGAnimatedRect from './SVGAnimatedRect.js'; -import Node from '../node/Node.js'; -import Event from '../../event/Event.js'; -import * as PropertySymbol from '../../PropertySymbol.js'; - -/** - * SVGSVGElement. - */ -export default class SVGSVGElement extends SVGGraphicsElement { - // Public properties - public declare cloneNode: (deep?: boolean) => SVGSVGElement; - - // Events - public onafterprint: (event: Event) => void | null = null; - public onbeforeprint: (event: Event) => void | null = null; - public onbeforeunload: (event: Event) => void | null = null; - public ongamepadconnected: (event: Event) => void | null = null; - public ongamepaddisconnected: (event: Event) => void | null = null; - public onhashchange: (event: Event) => void | null = null; - public onlanguagechange: (event: Event) => void | null = null; - public onmessage: (event: Event) => void | null = null; - public onmessageerror: (event: Event) => void | null = null; - public onoffline: (event: Event) => void | null = null; - public ononline: (event: Event) => void | null = null; - public onpagehide: (event: Event) => void | null = null; - public onpageshow: (event: Event) => void | null = null; - public onpopstate: (event: Event) => void | null = null; - public onrejectionhandled: (event: Event) => void | null = null; - public onstorage: (event: Event) => void | null = null; - public onunhandledrejection: (event: Event) => void | null = null; - public onunload: (event: Event) => void | null = null; - - /** - * Returns preserveAspectRatio. - * - * @returns PreserveAspectRatio. - */ - public get preserveAspectRatio(): string { - return this.getAttributeNS(null, 'preserveAspectRatio') || 'xMidYMid meet'; - } - - /** - * Sets preserveAspectRatio. - * - * @param preserveAspectRatio PreserveAspectRatio. - */ - public set preserveAspectRatio(preserveAspectRatio: string) { - this.setAttributeNS(null, 'preserveAspectRatio', preserveAspectRatio); - } - - /** - * Returns width. - * - * @returns Width. - */ - public get width(): string { - return this.getAttributeNS(null, 'width') || ''; - } - - /** - * Sets width. - * - * @param width Width. - */ - public set width(width: string) { - this.setAttributeNS(null, 'width', width); - } - - /** - * Returns height. - * - * @returns Height. - */ - public get height(): string { - return this.getAttributeNS(null, 'height') || ''; - } - - /** - * Sets height. - * - * @param height Height. - */ - public set height(height: string) { - this.setAttributeNS(null, 'height', height); - } - - /** - * Returns x. - * - * @returns X. - */ - public get x(): string { - return this.getAttributeNS(null, 'x') || ''; - } - - /** - * Sets x. - * - * @param x X. - */ - public set x(x: string) { - this.setAttributeNS(null, 'x', x); - } - - /** - * Returns y. - * - * @returns Y. - */ - public get y(): string { - return this.getAttributeNS(null, 'y') || ''; - } - - /** - * Sets y. - * - * @param y Y. - */ - public set y(y: string) { - this.setAttributeNS(null, 'y', y); - } - - /** - * Returns contentScriptType. - * - * @returns ContentScriptType. - */ - public get contentScriptType(): string { - return this.getAttributeNS(null, 'contentScriptType') || ''; - } - - /** - * Sets contentScriptType. - * - * @param contentScriptType ContentScriptType. - */ - public set contentScriptType(contentScriptType: string) { - this.setAttributeNS(null, 'contentScriptType', contentScriptType); - } - - /** - * Returns currentScale. - * - * @returns CurrentScale. - */ - public get currentScale(): number { - const currentScale = this.getAttributeNS(null, 'currentScale'); - if (currentScale !== null) { - return parseFloat(currentScale); - } - return 1; - } - - /** - * Sets currentScale. - * - * @param currentScale CurrentScale. - */ - public set currentScale(currentScale: number) { - this.setAttributeNS(null, 'currentScale', String(currentScale)); - } - - /** - * Returns viewport. - * - * @returns SVG rect. - */ - public get viewport(): SVGRect { - return new SVGRect(); - } - - /** - * Returns current translate. - * - * @returns SVG point. - */ - public get currentTranslate(): SVGPoint { - return new SVGPoint(); - } - - /** - * Returns view box. - * - * @returns Viewbox. - */ - public get viewBox(): SVGAnimatedRect { - const rect = new SVGAnimatedRect(); - const viewBox = this.getAttribute('viewBox'); - const list = viewBox.split(/\s+/); - rect.baseVal.x = Number(list[0]); - rect.baseVal.y = Number(list[1]); - rect.baseVal.width = Number(list[2]); - rect.baseVal.height = Number(list[3]); - return rect; - } - - /** - * Pauses animation. - */ - public pauseAnimations(): void {} - - /** - * Unpauses animation. - */ - public unpauseAnimations(): void {} - - /** - * Returns "true" if animation is paused. - * - * @returns "true" if animation is paused. - */ - public animationsPaused(): boolean { - return false; - } - - /** - * Returns the current time in seconds relative to the start time for the current SVG document fragment. - * - * @returns Current time. - */ - public getCurrentTime(): number { - return 0; - } - - /** - * Sets current time. - */ - public setCurrentTime(): void {} - - /** - * Returns intersection list. - * - * @returns Intersection list. - */ - public getIntersectionList(): Node[] { - return []; - } - - /** - * Returns enclousure list. - * - * @returns Enclousure list. - */ - public getEnclosureList(): Node[] { - return []; - } - - /** - * Returns true if the rendered content of the given element intersects the supplied rectangle. - * - * @returns Intersection state. - */ - public checkIntersection(): boolean { - return false; - } - - /** - * Returns true if the rendered content of the given element is entirely contained within the supplied rectangle. - * - * @returns Enclousure state. - */ - public checkEnclosure(): boolean { - return false; - } - - /** - * Unselects any selected objects, including any selections of text strings and type-in bars. - */ - public deselectAll(): void {} - - /** - * Returns a number. - * - * @returns Number. - */ - public createSVGNumber(): SVGNumber { - return new SVGNumber(); - } - - /** - * Returns a length. - * - * @returns Length. - */ - public createSVGLength(): SVGLength { - return new SVGLength(); - } - - /** - * Returns a angle. - * - * @returns Angle. - */ - public createSVGAngle(): SVGAngle { - return new SVGAngle(); - } - - /** - * Returns a point. - * - * @returns Point. - */ - public createSVGPoint(): SVGPoint { - return new SVGPoint(); - } - - /** - * Returns a rect. - * - * @returns Rect. - */ - public createSVGRect(): SVGRect { - return new SVGRect(); - } - - /** - * Returns a transform. - * - * @returns Transform. - */ - public createSVGTransform(): SVGTransform { - return new SVGTransform(); - } - - /** - * @override - */ - public override [PropertySymbol.cloneNode](deep = false): SVGSVGElement { - return super[PropertySymbol.cloneNode](deep); - } -} diff --git a/packages/happy-dom/src/nodes/svg-element/SVGTransform.ts b/packages/happy-dom/src/nodes/svg-element/SVGTransform.ts deleted file mode 100644 index 5eaf87bdd..000000000 --- a/packages/happy-dom/src/nodes/svg-element/SVGTransform.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * SVG transform. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTransform - */ -export default class SVGTransform { - public static SVG_TRANSFORM_UNKNOWN = 0; - public static SVG_TRANSFORM_MATRIX = 1; - public static SVG_TRANSFORM_TRANSLATE = 2; - public static SVG_TRANSFORM_SCALE = 3; - public static SVG_TRANSFORM_ROTATE = 4; - public static SVG_TRANSFORM_SKEWX = 5; - public static SVG_TRANSFORM_SKEWY = 6; - - public type = 0; - public angle = 0; - - /** - * Set matrix. - */ - public setMatrix(): void {} - /** - * Set translate. - */ - public setTranslate(): void {} - /** - * Set scale. - */ - public setScale(): void {} - /** - * Set rotate. - */ - public setRotate(): void {} - /** - * Set skew x. - */ - public setSkewX(): void {} - /** - * Set skew y. - */ - public setSkewY(): void {} -} diff --git a/packages/happy-dom/src/nodes/svg-ellipse-element/SVGEllipseElement.ts b/packages/happy-dom/src/nodes/svg-ellipse-element/SVGEllipseElement.ts new file mode 100644 index 000000000..9d6b99d80 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-ellipse-element/SVGEllipseElement.ts @@ -0,0 +1,92 @@ +import SVGGeometryElement from '../svg-geometry-element/SVGGeometryElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVG Ellipse Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGEllipseElement + */ +export default class SVGEllipseElement extends SVGGeometryElement { + // Internal properties + public [PropertySymbol.cx]: SVGAnimatedLength | null = null; + public [PropertySymbol.cy]: SVGAnimatedLength | null = null; + public [PropertySymbol.rx]: SVGAnimatedLength | null = null; + public [PropertySymbol.ry]: SVGAnimatedLength | null = null; + + /** + * Returns cx. + * + * @returns Cx. + */ + public get cx(): SVGAnimatedLength { + if (!this[PropertySymbol.cx]) { + this[PropertySymbol.cx] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('cx'), + setAttribute: (value) => this.setAttribute('cx', value) + } + ); + } + return this[PropertySymbol.cx]; + } + + /** + * Returns cy. + * + * @returns Cy. + */ + public get cy(): SVGAnimatedLength { + if (!this[PropertySymbol.cy]) { + this[PropertySymbol.cy] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('cy'), + setAttribute: (value) => this.setAttribute('cy', value) + } + ); + } + return this[PropertySymbol.cy]; + } + + /** + * Returns rx. + * + * @returns Rx. + */ + public get rx(): SVGAnimatedLength { + if (!this[PropertySymbol.rx]) { + this[PropertySymbol.rx] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('rx'), + setAttribute: (value) => this.setAttribute('rx', value) + } + ); + } + return this[PropertySymbol.rx]; + } + + /** + * Returns ry. + * + * @returns Ry. + */ + public get ry(): SVGAnimatedLength { + if (!this[PropertySymbol.ry]) { + this[PropertySymbol.ry] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('ry'), + setAttribute: (value) => this.setAttribute('ry', value) + } + ); + } + return this[PropertySymbol.ry]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-blend-element/SVGFEBlendElement.ts b/packages/happy-dom/src/nodes/svg-fe-blend-element/SVGFEBlendElement.ts new file mode 100644 index 000000000..d568db283 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-blend-element/SVGFEBlendElement.ts @@ -0,0 +1,192 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; + +/** + * SVG FE Blend Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEBlendElement + */ +export default class SVGFEBlendElement extends SVGElement { + // Static properties + public static SVG_FEBLEND_MODE_UNKNOWN = 0; + public static SVG_FEBLEND_MODE_NORMAL = 1; + public static SVG_FEBLEND_MODE_MULTIPLY = 2; + public static SVG_FEBLEND_MODE_SCREEN = 3; + public static SVG_FEBLEND_MODE_DARKEN = 4; + public static SVG_FEBLEND_MODE_LIGHTEN = 5; + public static SVG_FEBLEND_MODE_OVERLAY = 6; + public static SVG_FEBLEND_MODE_COLOR_DODGE = 7; + public static SVG_FEBLEND_MODE_COLOR_BURN = 8; + public static SVG_FEBLEND_MODE_HARD_LIGHT = 9; + public static SVG_FEBLEND_MODE_SOFT_LIGHT = 10; + public static SVG_FEBLEND_MODE_DIFFERENCE = 11; + public static SVG_FEBLEND_MODE_EXCLUSION = 12; + public static SVG_FEBLEND_MODE_HUE = 13; + public static SVG_FEBLEND_MODE_SATURATION = 14; + public static SVG_FEBLEND_MODE_COLOR = 15; + public static SVG_FEBLEND_MODE_LUMINOSITY = 16; + + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.in2]: SVGAnimatedString | null = null; + public [PropertySymbol.mode]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns in2. + * + * @returns In2. + */ + public get in2(): SVGAnimatedString { + if (!this[PropertySymbol.in2]) { + this[PropertySymbol.in2] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in2'), + setAttribute: (value) => this.setAttribute('in2', value) + } + ); + } + return this[PropertySymbol.in2]; + } + + /** + * Returns mode. + * + * @returns Mode. + */ + public get mode(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.mode]) { + this[PropertySymbol.mode] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('mode'), + setAttribute: (value) => this.setAttribute('mode', value), + values: [ + 'normal', + 'multiply', + 'screen', + 'darken', + 'lighten', + 'overlay', + 'color-dodge', + 'color-burn', + 'hard-light', + 'soft-light', + 'difference', + 'exclusion', + 'hue', + 'saturation', + 'color', + 'luminosity' + ], + defaultValue: 'normal' + } + ); + } + return this[PropertySymbol.mode]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-color-matrix-element/SVGFEColorMatrixElement.ts b/packages/happy-dom/src/nodes/svg-fe-color-matrix-element/SVGFEColorMatrixElement.ts new file mode 100644 index 000000000..83eda98c7 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-color-matrix-element/SVGFEColorMatrixElement.ts @@ -0,0 +1,204 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedNumberList from '../../svg/SVGAnimatedNumberList.js'; + +/** + * SVG FE Color Matrix Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEColorMatrixElement + */ +export default class SVGFEColorMatrixElement extends SVGElement { + // Static properties + public static SVG_FEBLEND_TYPE_UNKNOWN = 0; + public static SVG_FEBLEND_TYPE_MATRIX = 1; + public static SVG_FEBLEND_TYPE_SATURATE = 2; + public static SVG_FEBLEND_TYPE_HUEROTATE = 3; + public static SVG_FEBLEND_TYPE_LUMINANCETOALPHA = 4; + + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.in2]: SVGAnimatedString | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.type]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.values]: SVGAnimatedNumberList | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns in2. + * + * @returns In2. + */ + public get in2(): SVGAnimatedString { + if (!this[PropertySymbol.in2]) { + this[PropertySymbol.in2] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in2'), + setAttribute: (value) => this.setAttribute('in2', value) + } + ); + } + return this[PropertySymbol.in2]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns type. + * + * @returns Type. + */ + public get type(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.type]) { + this[PropertySymbol.type] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('type'), + setAttribute: (value) => this.setAttribute('type', value), + values: ['matrix', 'saturate', 'huerotate', 'luminancetoalpha'], + defaultValue: 'matrix' + } + ); + } + return this[PropertySymbol.type]; + } + + /** + * Returns values. + * + * @returns Values. + */ + public get values(): SVGAnimatedNumberList { + if (!this[PropertySymbol.values]) { + this[PropertySymbol.values] = new SVGAnimatedNumberList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('values'), + setAttribute: (value) => this.setAttribute('values', value) + } + ); + } + return this[PropertySymbol.values]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-component-transfer-element/SVGFEComponentTransferElement.ts b/packages/happy-dom/src/nodes/svg-fe-component-transfer-element/SVGFEComponentTransferElement.ts new file mode 100644 index 000000000..301ba92dd --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-component-transfer-element/SVGFEComponentTransferElement.ts @@ -0,0 +1,133 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; + +/** + * SVGFEComponentTransferElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEComponentTransferElement + */ +export default class SVGFEComponentTransferElement extends SVGElement { + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-composite-element/SVGFECompositeElement.ts b/packages/happy-dom/src/nodes/svg-fe-composite-element/SVGFECompositeElement.ts new file mode 100644 index 000000000..c9940775e --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-composite-element/SVGFECompositeElement.ts @@ -0,0 +1,186 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedNumberList from '../../svg/SVGAnimatedNumberList.js'; + +/** + * SVGFECompositeElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFECompositeElement + */ +export default class SVGFECompositeElement extends SVGElement { + // Static properties + public static SVG_FECOMPOSITE_OPERATOR_UNKNOWN = 0; + public static SVG_FECOMPOSITE_OPERATOR_OVER = 1; + public static SVG_FECOMPOSITE_OPERATOR_IN = 2; + public static SVG_FECOMPOSITE_OPERATOR_OUT = 3; + public static SVG_FECOMPOSITE_OPERATOR_ATOP = 4; + public static SVG_FECOMPOSITE_OPERATOR_XOR = 5; + public static SVG_FECOMPOSITE_OPERATOR_ARITHMETIC = 6; + + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.type]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.values]: SVGAnimatedNumberList | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns type. + * + * @returns Type. + */ + public get type(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.type]) { + this[PropertySymbol.type] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('type'), + setAttribute: (value) => this.setAttribute('type', value), + values: ['over', 'in', 'out', 'atop', 'xor', 'arithmetic'], + defaultValue: 'over' + } + ); + } + return this[PropertySymbol.type]; + } + + /** + * Returns values. + * + * @returns Values. + */ + public get values(): SVGAnimatedNumberList { + if (!this[PropertySymbol.values]) { + this[PropertySymbol.values] = new SVGAnimatedNumberList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('values'), + setAttribute: (value) => this.setAttribute('values', value) + } + ); + } + return this[PropertySymbol.values]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-convolve-matrix-element/SVGFEConvolveMatrixElement.ts b/packages/happy-dom/src/nodes/svg-fe-convolve-matrix-element/SVGFEConvolveMatrixElement.ts new file mode 100644 index 000000000..7cb2de644 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-convolve-matrix-element/SVGFEConvolveMatrixElement.ts @@ -0,0 +1,366 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedNumberList from '../../svg/SVGAnimatedNumberList.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedBoolean from '../../svg/SVGAnimatedBoolean.js'; +import SVGAnimatedInteger from '../../svg/SVGAnimatedInteger.js'; + +/** + * SVGFEConvolveMatrixElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEConvolveMatrixElement + */ +export default class SVGFEConvolveMatrixElement extends SVGElement { + // Static properties + public static SVG_EDGEMODE_UNKNOWN = 0; + public static SVG_EDGEMODE_DUPLICATE = 1; + public static SVG_EDGEMODE_WRAP = 2; + public static SVG_EDGEMODE_NONE = 3; + + // Internal properties + public [PropertySymbol.bias]: SVGAnimatedNumber | null = null; + public [PropertySymbol.divisor]: SVGAnimatedNumber | null = null; + public [PropertySymbol.edgeMode]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.kernelMatrix]: SVGAnimatedNumberList | null = null; + public [PropertySymbol.kernelUnitLengthX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.kernelUnitLengthY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.orderX]: SVGAnimatedInteger | null = null; + public [PropertySymbol.orderY]: SVGAnimatedInteger | null = null; + public [PropertySymbol.preserveAlpha]: SVGAnimatedBoolean | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.targetX]: SVGAnimatedInteger | null = null; + public [PropertySymbol.targetY]: SVGAnimatedInteger | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns bias. + * + * @returns Bias. + */ + public get bias(): SVGAnimatedNumber { + if (!this[PropertySymbol.bias]) { + this[PropertySymbol.bias] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('bias'), + setAttribute: (value) => this.setAttribute('bias', value) + } + ); + } + return this[PropertySymbol.bias]; + } + + /** + * Returns divisor. + * + * @returns Divisor. + */ + public get divisor(): SVGAnimatedNumber { + if (!this[PropertySymbol.divisor]) { + this[PropertySymbol.divisor] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('divisor'), + setAttribute: (value) => this.setAttribute('divisor', value) + } + ); + } + return this[PropertySymbol.divisor]; + } + + /** + * Returns edge mode. + * + * @returns Edge mode. + */ + public get edgeMode(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.edgeMode]) { + this[PropertySymbol.edgeMode] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('edgeMode'), + setAttribute: (value) => this.setAttribute('edgeMode', value), + values: ['duplicate', 'wrap', 'none'], + defaultValue: 'duplicate' + } + ); + } + return this[PropertySymbol.edgeMode]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns kernel matrix. + * + * @returns Kernel matrix. + */ + public get kernelMatrix(): SVGAnimatedNumberList { + if (!this[PropertySymbol.kernelMatrix]) { + this[PropertySymbol.kernelMatrix] = new SVGAnimatedNumberList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('kernelMatrix'), + setAttribute: (value) => this.setAttribute('kernelMatrix', value) + } + ); + } + return this[PropertySymbol.kernelMatrix]; + } + + /** + * Returns kernel unit length x. + * + * @returns Kernel unit length x. + */ + public get kernelUnitLengthX(): SVGAnimatedNumber { + if (!this[PropertySymbol.kernelUnitLengthX]) { + this[PropertySymbol.kernelUnitLengthX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('kernelUnitLengthX'), + setAttribute: (value) => this.setAttribute('kernelUnitLengthX', value) + } + ); + } + return this[PropertySymbol.kernelUnitLengthX]; + } + + /** + * Returns kernel unit length y. + * + * @returns Kernel unit length y. + */ + public get kernelUnitLengthY(): SVGAnimatedNumber { + if (!this[PropertySymbol.kernelUnitLengthY]) { + this[PropertySymbol.kernelUnitLengthY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('kernelUnitLengthY'), + setAttribute: (value) => this.setAttribute('kernelUnitLengthY', value) + } + ); + } + return this[PropertySymbol.kernelUnitLengthY]; + } + + /** + * Returns order x. + * + * @returns Order x. + */ + public get orderX(): SVGAnimatedInteger { + if (!this[PropertySymbol.orderX]) { + this[PropertySymbol.orderX] = new SVGAnimatedInteger( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('orderX'), + setAttribute: (value) => this.setAttribute('orderX', value) + } + ); + } + return this[PropertySymbol.orderX]; + } + + /** + * Returns order y. + * + * @returns Order y. + */ + public get orderY(): SVGAnimatedInteger { + if (!this[PropertySymbol.orderY]) { + this[PropertySymbol.orderY] = new SVGAnimatedInteger( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('orderY'), + setAttribute: (value) => this.setAttribute('orderY', value) + } + ); + } + return this[PropertySymbol.orderY]; + } + + /** + * Returns preserve alpha. + * + * @returns Preserve alpha. + */ + public get preserveAlpha(): SVGAnimatedBoolean { + if (!this[PropertySymbol.preserveAlpha]) { + this[PropertySymbol.preserveAlpha] = new SVGAnimatedBoolean( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('preserveAlpha'), + setAttribute: (value) => this.setAttribute('preserveAlpha', value) + } + ); + } + return this[PropertySymbol.preserveAlpha]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns target x. + * + * @returns Target x. + */ + public get targetX(): SVGAnimatedInteger { + if (!this[PropertySymbol.targetX]) { + this[PropertySymbol.targetX] = new SVGAnimatedInteger( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('targetX'), + setAttribute: (value) => this.setAttribute('targetX', value) + } + ); + } + return this[PropertySymbol.targetX]; + } + + /** + * Returns target y. + * + * @returns Target y. + */ + public get targetY(): SVGAnimatedInteger { + if (!this[PropertySymbol.targetY]) { + this[PropertySymbol.targetY] = new SVGAnimatedInteger( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('targetY'), + setAttribute: (value) => this.setAttribute('targetY', value) + } + ); + } + return this[PropertySymbol.targetY]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-diffuse-lighting-element/SVGFEDiffuseLightingElement.ts b/packages/happy-dom/src/nodes/svg-fe-diffuse-lighting-element/SVGFEDiffuseLightingElement.ts new file mode 100644 index 000000000..0abe4107a --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-diffuse-lighting-element/SVGFEDiffuseLightingElement.ts @@ -0,0 +1,214 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; + +/** + * SVGFEDiffuseLightingElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEDiffuseLightingElement + */ +export default class SVGFEDiffuseLightingElement extends SVGElement { + // Internal properties + public [PropertySymbol.diffuseConstant]: SVGAnimatedNumber | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.kernelUnitLengthX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.kernelUnitLengthY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.surfaceScale]: SVGAnimatedNumber | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns diffuse constant. + * + * @returns Diffuse constant. + */ + public get diffuseConstant(): SVGAnimatedNumber { + if (!this[PropertySymbol.diffuseConstant]) { + this[PropertySymbol.diffuseConstant] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('diffuseConstant'), + setAttribute: (value) => this.setAttribute('diffuseConstant', value) + } + ); + } + return this[PropertySymbol.diffuseConstant]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns kernel unit length x. + * + * @returns Kernel unit length x. + */ + public get kernelUnitLengthX(): SVGAnimatedNumber { + if (!this[PropertySymbol.kernelUnitLengthX]) { + this[PropertySymbol.kernelUnitLengthX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('kernelUnitLengthX'), + setAttribute: (value) => this.setAttribute('kernelUnitLengthX', value) + } + ); + } + return this[PropertySymbol.kernelUnitLengthX]; + } + + /** + * Returns kernel unit length y. + * + * @returns Kernel unit length y. + */ + public get kernelUnitLengthY(): SVGAnimatedNumber { + if (!this[PropertySymbol.kernelUnitLengthY]) { + this[PropertySymbol.kernelUnitLengthY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('kernelUnitLengthY'), + setAttribute: (value) => this.setAttribute('kernelUnitLengthY', value) + } + ); + } + return this[PropertySymbol.kernelUnitLengthY]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns surface scale. + * + * @returns Surface scale. + */ + public get surfaceScale(): SVGAnimatedNumber { + if (!this[PropertySymbol.surfaceScale]) { + this[PropertySymbol.surfaceScale] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('surfaceScale'), + setAttribute: (value) => this.setAttribute('surfaceScale', value) + } + ); + } + return this[PropertySymbol.surfaceScale]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-displacement-map-element/SVGFEDisplacementMapElement.ts b/packages/happy-dom/src/nodes/svg-fe-displacement-map-element/SVGFEDisplacementMapElement.ts new file mode 100644 index 000000000..b3a326895 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-displacement-map-element/SVGFEDisplacementMapElement.ts @@ -0,0 +1,226 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; + +/** + * SVGFEDisplacementMapElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEDisplacementMapElement + */ +export default class SVGFEDisplacementMapElement extends SVGElement { + // Static properties + public static SVG_CHANNEL_UNKNOWN = 0; + public static SVG_CHANNEL_R = 1; + public static SVG_CHANNEL_G = 2; + public static SVG_CHANNEL_B = 3; + public static SVG_CHANNEL_A = 4; + + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.in2]: SVGAnimatedString | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.scale]: SVGAnimatedNumber | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.xChannelSelector]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.yChannelSelector]: SVGAnimatedEnumeration | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns in2. + * + * @returns In2. + */ + public get in2(): SVGAnimatedString { + if (!this[PropertySymbol.in2]) { + this[PropertySymbol.in2] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in2'), + setAttribute: (value) => this.setAttribute('in2', value) + } + ); + } + return this[PropertySymbol.in2]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns scale. + * + * @returns Scale. + */ + public get scale(): SVGAnimatedNumber { + if (!this[PropertySymbol.scale]) { + this[PropertySymbol.scale] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('scale'), + setAttribute: (value) => this.setAttribute('scale', value) + } + ); + } + return this[PropertySymbol.scale]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns x channel selector. + * + * @returns X channel selector. + */ + public get xChannelSelector(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.xChannelSelector]) { + this[PropertySymbol.xChannelSelector] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('xChannelSelector'), + setAttribute: (value) => this.setAttribute('xChannelSelector', value), + values: ['r', 'g', 'b', 'a'], + defaultValue: 'r' + } + ); + } + return this[PropertySymbol.xChannelSelector]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Returns y channel selector. + * + * @returns Y channel selector. + */ + public get yChannelSelector(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.yChannelSelector]) { + this[PropertySymbol.yChannelSelector] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('yChannelSelector'), + setAttribute: (value) => this.setAttribute('yChannelSelector', value), + values: ['r', 'g', 'b', 'a'], + defaultValue: 'r' + } + ); + } + return this[PropertySymbol.yChannelSelector]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-distant-light-element/SVGFEDistantLightElement.ts b/packages/happy-dom/src/nodes/svg-fe-distant-light-element/SVGFEDistantLightElement.ts new file mode 100644 index 000000000..9e55a7a2c --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-distant-light-element/SVGFEDistantLightElement.ts @@ -0,0 +1,52 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; + +/** + * SVGFEDistantLightElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEDistantLightElement + */ +export default class SVGFEDistantLightElement extends SVGElement { + // Internal properties + public [PropertySymbol.azimuth]: SVGAnimatedNumber | null = null; + public [PropertySymbol.elevation]: SVGAnimatedNumber | null = null; + + /** + * Returns azimuth. + * + * @returns Azimuth. + */ + public get azimuth(): SVGAnimatedNumber { + if (!this[PropertySymbol.azimuth]) { + this[PropertySymbol.azimuth] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('azimuth'), + setAttribute: (value) => this.setAttribute('azimuth', value) + } + ); + } + return this[PropertySymbol.azimuth]; + } + + /** + * Returns elevation. + * + * @returns Elevation. + */ + public get elevation(): SVGAnimatedNumber { + if (!this[PropertySymbol.elevation]) { + this[PropertySymbol.elevation] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('elevation'), + setAttribute: (value) => this.setAttribute('elevation', value) + } + ); + } + return this[PropertySymbol.elevation]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-drop-shadow-element/SVGFEDropShadowElement.ts b/packages/happy-dom/src/nodes/svg-fe-drop-shadow-element/SVGFEDropShadowElement.ts new file mode 100644 index 000000000..ff547a792 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-drop-shadow-element/SVGFEDropShadowElement.ts @@ -0,0 +1,227 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVGFEDropShadowElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEDropShadowElement + */ +export default class SVGFEDropShadowElement extends SVGElement { + // Internal properties + public [PropertySymbol.dx]: SVGAnimatedNumber | null = null; + public [PropertySymbol.dy]: SVGAnimatedNumber | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.stdDeviationX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.stdDeviationY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns dx. + * + * @returns Dx. + */ + public get dx(): SVGAnimatedNumber { + if (!this[PropertySymbol.dx]) { + this[PropertySymbol.dx] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('dx'), + setAttribute: (value) => this.setAttribute('dx', value) + } + ); + } + return this[PropertySymbol.dx]; + } + + /** + * Returns dy. + * + * @returns Dy. + */ + public get dy(): SVGAnimatedNumber { + if (!this[PropertySymbol.dy]) { + this[PropertySymbol.dy] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('dy'), + setAttribute: (value) => this.setAttribute('dy', value) + } + ); + } + return this[PropertySymbol.dy]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns stdDeviationX. + * + * @returns StdDeviationX. + */ + public get stdDeviationX(): SVGAnimatedNumber { + if (!this[PropertySymbol.stdDeviationX]) { + this[PropertySymbol.stdDeviationX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('stdDeviationX'), + setAttribute: (value) => this.setAttribute('stdDeviationX', value), + defaultValue: 2 + } + ); + } + return this[PropertySymbol.stdDeviationX]; + } + + /** + * Returns stdDeviationY. + * + * @returns StdDeviationY. + */ + public get stdDeviationY(): SVGAnimatedNumber { + if (!this[PropertySymbol.stdDeviationY]) { + this[PropertySymbol.stdDeviationY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('stdDeviationY'), + setAttribute: (value) => this.setAttribute('stdDeviationY', value), + defaultValue: 2 + } + ); + } + return this[PropertySymbol.stdDeviationY]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Sets stdDeviation. + * + * @param x X. + * @param y Y. + */ + public setStdDeviation(x: number, y: number): void { + this.stdDeviationX.baseVal = x; + this.stdDeviationY.baseVal = y; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-flood-element/SVGFEFloodElement.ts b/packages/happy-dom/src/nodes/svg-fe-flood-element/SVGFEFloodElement.ts new file mode 100644 index 000000000..e4fb5b8fb --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-flood-element/SVGFEFloodElement.ts @@ -0,0 +1,113 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVGFEFloodElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEFloodElement + */ +export default class SVGFEFloodElement extends SVGElement { + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-func-a-element/SVGFEFuncAElement.ts b/packages/happy-dom/src/nodes/svg-fe-func-a-element/SVGFEFuncAElement.ts new file mode 100644 index 000000000..c8b09e38f --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-func-a-element/SVGFEFuncAElement.ts @@ -0,0 +1,8 @@ +import SVGComponentTransferFunctionElement from '../svg-component-transfer-function-element/SVGComponentTransferFunctionElement.js'; + +/** + * SVGFEFuncAElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEFuncAElement + */ +export default class SVGFEFuncAElement extends SVGComponentTransferFunctionElement {} diff --git a/packages/happy-dom/src/nodes/svg-fe-func-b-element/SVGFEFuncBElement.ts b/packages/happy-dom/src/nodes/svg-fe-func-b-element/SVGFEFuncBElement.ts new file mode 100644 index 000000000..c101c3e66 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-func-b-element/SVGFEFuncBElement.ts @@ -0,0 +1,8 @@ +import SVGComponentTransferFunctionElement from '../svg-component-transfer-function-element/SVGComponentTransferFunctionElement.js'; + +/** + * SVGFEFuncBElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEFuncBElement + */ +export default class SVGFEFuncBElement extends SVGComponentTransferFunctionElement {} diff --git a/packages/happy-dom/src/nodes/svg-fe-func-g-element/SVGFEFuncGElement.ts b/packages/happy-dom/src/nodes/svg-fe-func-g-element/SVGFEFuncGElement.ts new file mode 100644 index 000000000..57cec11ad --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-func-g-element/SVGFEFuncGElement.ts @@ -0,0 +1,8 @@ +import SVGComponentTransferFunctionElement from '../svg-component-transfer-function-element/SVGComponentTransferFunctionElement.js'; + +/** + * SVGFEFuncGElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEFuncGElement + */ +export default class SVGFEFuncGElement extends SVGComponentTransferFunctionElement {} diff --git a/packages/happy-dom/src/nodes/svg-fe-func-r-element/SVGFEFuncRElement.ts b/packages/happy-dom/src/nodes/svg-fe-func-r-element/SVGFEFuncRElement.ts new file mode 100644 index 000000000..a13600396 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-func-r-element/SVGFEFuncRElement.ts @@ -0,0 +1,8 @@ +import SVGComponentTransferFunctionElement from '../svg-component-transfer-function-element/SVGComponentTransferFunctionElement.js'; + +/** + * SVGFEFuncRElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEFuncRElement + */ +export default class SVGFEFuncRElement extends SVGComponentTransferFunctionElement {} diff --git a/packages/happy-dom/src/nodes/svg-fe-gaussian-blur-element/SVGFEGaussianBlurElement.ts b/packages/happy-dom/src/nodes/svg-fe-gaussian-blur-element/SVGFEGaussianBlurElement.ts new file mode 100644 index 000000000..eb2bcd521 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-gaussian-blur-element/SVGFEGaussianBlurElement.ts @@ -0,0 +1,214 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; + +/** + * SVGFEGaussianBlurElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEGaussianBlurElement + */ +export default class SVGFEGaussianBlurElement extends SVGElement { + // Static properties + public static SVG_EDGEMODE_UNKNOWN = 0; + public static SVG_EDGEMODE_DUPLICATE = 1; + public static SVG_EDGEMODE_WRAP = 2; + public static SVG_EDGEMODE_NONE = 3; + + // Internal properties + public [PropertySymbol.edgeMode]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.stdDeviationX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.stdDeviationY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns edge mode. + * + * @returns Edge mode. + */ + public get edgeMode(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.edgeMode]) { + this[PropertySymbol.edgeMode] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('edgeMode'), + setAttribute: (value) => this.setAttribute('edgeMode', value), + values: ['duplicate', 'wrap', 'none'], + defaultValue: 'duplicate' + } + ); + } + return this[PropertySymbol.edgeMode]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns stdDeviationX. + * + * @returns StdDeviationX. + */ + public get stdDeviationX(): SVGAnimatedNumber { + if (!this[PropertySymbol.stdDeviationX]) { + this[PropertySymbol.stdDeviationX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('stdDeviationX') || '2', + setAttribute: (value) => this.setAttribute('stdDeviationX', value) + } + ); + } + return this[PropertySymbol.stdDeviationX]; + } + + /** + * Returns stdDeviationY. + * + * @returns StdDeviationY. + */ + public get stdDeviationY(): SVGAnimatedNumber { + if (!this[PropertySymbol.stdDeviationY]) { + this[PropertySymbol.stdDeviationY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('stdDeviationY') || '2', + setAttribute: (value) => this.setAttribute('stdDeviationY', value) + } + ); + } + return this[PropertySymbol.stdDeviationY]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Sets stdDeviation. + * + * @param x X. + * @param y Y. + */ + public setStdDeviation(x: number, y: number): void { + this.stdDeviationX.baseVal = x; + this.stdDeviationY.baseVal = y; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-image-element/SVGFEImageElement.ts b/packages/happy-dom/src/nodes/svg-fe-image-element/SVGFEImageElement.ts new file mode 100644 index 000000000..d33020ec6 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-image-element/SVGFEImageElement.ts @@ -0,0 +1,173 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedPreserveAspectRatio from '../../svg/SVGAnimatedPreserveAspectRatio.js'; + +/** + * SVGFEImageElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEImageElement + */ +export default class SVGFEImageElement extends SVGElement { + // Internal properties + public [PropertySymbol.crossOrigin]: SVGAnimatedString | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.href]: SVGAnimatedString | null = null; + public [PropertySymbol.preserveAspectRatio]: SVGAnimatedPreserveAspectRatio | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns "crossorigin" attribute. + * + * @returns Cross origin. + */ + public get crossOrigin(): string { + return this.getAttribute('crossorigin'); + } + + /** + * Sets "crossorigin" attribute. + * + * @param value Cross origin. + */ + public set crossOrigin(value: string) { + this.setAttribute('crossorigin', value); + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns preserveAspectRatio. + * + * @returns PreserveAspectRatio. + */ + public get preserveAspectRatio(): SVGAnimatedPreserveAspectRatio { + if (!this[PropertySymbol.preserveAspectRatio]) { + this[PropertySymbol.preserveAspectRatio] = new SVGAnimatedPreserveAspectRatio( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('preserveAspectRatio'), + setAttribute: (value) => this.setAttribute('preserveAspectRatio', value) + } + ); + } + return this[PropertySymbol.preserveAspectRatio]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-merge-element/SVGFEMergeElement.ts b/packages/happy-dom/src/nodes/svg-fe-merge-element/SVGFEMergeElement.ts new file mode 100644 index 000000000..08b7baea5 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-merge-element/SVGFEMergeElement.ts @@ -0,0 +1,113 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVGFEMergeElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEMergeElement + */ +export default class SVGFEMergeElement extends SVGElement { + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-merge-node-element/SVGFEMergeNodeElement.ts b/packages/happy-dom/src/nodes/svg-fe-merge-node-element/SVGFEMergeNodeElement.ts new file mode 100644 index 000000000..f3ecc57d7 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-merge-node-element/SVGFEMergeNodeElement.ts @@ -0,0 +1,32 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; + +/** + * SVGFEMergeNodeElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEMergeNodeElement + */ +export default class SVGFEMergeNodeElement extends SVGElement { + // Internal properties + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-morphology-element/SVGFEMorphologyElement.ts b/packages/happy-dom/src/nodes/svg-fe-morphology-element/SVGFEMorphologyElement.ts new file mode 100644 index 000000000..ff2c50557 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-morphology-element/SVGFEMorphologyElement.ts @@ -0,0 +1,202 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; + +/** + * SVGFEMorphologyElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEMorphologyElement + */ +export default class SVGFEMorphologyElement extends SVGElement { + // Static properties + public static readonly SVG_MORPHOLOGY_OPERATOR_UNKNOWN = 0; + public static readonly SVG_MORPHOLOGY_OPERATOR_ERODE = 1; + public static readonly SVG_MORPHOLOGY_OPERATOR_DILATE = 2; + + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.operator]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.radiusX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.radiusY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns operator. + * + * @returns Operator. + */ + public get operator(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.operator]) { + this[PropertySymbol.operator] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('operator'), + setAttribute: (value) => this.setAttribute('operator', value), + values: ['erode', 'dilate'], + defaultValue: 'erode' + } + ); + } + return this[PropertySymbol.operator]; + } + + /** + * Returns radiusX. + * + * @returns RadiusX. + */ + public get radiusX(): SVGAnimatedNumber { + if (!this[PropertySymbol.radiusX]) { + this[PropertySymbol.radiusX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('radiusX'), + setAttribute: (value) => this.setAttribute('radiusX', value) + } + ); + } + return this[PropertySymbol.radiusX]; + } + + /** + * Returns radiusY. + * + * @returns RadiusY. + */ + public get radiusY(): SVGAnimatedNumber { + if (!this[PropertySymbol.radiusY]) { + this[PropertySymbol.radiusY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('radiusY'), + setAttribute: (value) => this.setAttribute('radiusY', value) + } + ); + } + return this[PropertySymbol.radiusY]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-offset-element/SVGFEOffsetElement.ts b/packages/happy-dom/src/nodes/svg-fe-offset-element/SVGFEOffsetElement.ts new file mode 100644 index 000000000..39c77ebb4 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-offset-element/SVGFEOffsetElement.ts @@ -0,0 +1,174 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; + +/** + * SVGFEOffsetElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEOffsetElement + */ +export default class SVGFEOffsetElement extends SVGElement { + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.dx]: SVGAnimatedNumber | null = null; + public [PropertySymbol.dy]: SVGAnimatedNumber | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns dx. + * + * @returns Dx. + */ + public get dx(): SVGAnimatedNumber { + if (!this[PropertySymbol.dx]) { + this[PropertySymbol.dx] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('dx'), + setAttribute: (value) => this.setAttribute('dx', value) + } + ); + } + return this[PropertySymbol.dx]; + } + + /** + * Returns dy. + * + * @returns Dy. + */ + public get dy(): SVGAnimatedNumber { + if (!this[PropertySymbol.dy]) { + this[PropertySymbol.dy] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('dy'), + setAttribute: (value) => this.setAttribute('dy', value) + } + ); + } + return this[PropertySymbol.dy]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-point-light-element/SVGFEPointLightElement.ts b/packages/happy-dom/src/nodes/svg-fe-point-light-element/SVGFEPointLightElement.ts new file mode 100644 index 000000000..fbf775c47 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-point-light-element/SVGFEPointLightElement.ts @@ -0,0 +1,72 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; + +/** + * SVGFEPointLightElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFEPointLightElement + */ +export default class SVGFEPointLightElement extends SVGElement { + // Internal properties + public [PropertySymbol.x]: SVGAnimatedNumber | null = null; + public [PropertySymbol.y]: SVGAnimatedNumber | null = null; + public [PropertySymbol.z]: SVGAnimatedNumber | null = null; + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedNumber { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedNumber { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Returns z. + * + * @returns Z. + */ + public get z(): SVGAnimatedNumber { + if (!this[PropertySymbol.z]) { + this[PropertySymbol.z] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('z'), + setAttribute: (value) => this.setAttribute('z', value) + } + ); + } + return this[PropertySymbol.z]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-specular-lighting-element/SVGFESpecularLightingElement.ts b/packages/happy-dom/src/nodes/svg-fe-specular-lighting-element/SVGFESpecularLightingElement.ts new file mode 100644 index 000000000..1c17023c1 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-specular-lighting-element/SVGFESpecularLightingElement.ts @@ -0,0 +1,237 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; + +/** + * SVGFESpecularLightingElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFESpecularLightingElement + */ +export default class SVGFESpecularLightingElement extends SVGElement { + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.kernelUnitLengthX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.kernelUnitLengthY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.specularConstant]: SVGAnimatedNumber | null = null; + public [PropertySymbol.specularExponent]: SVGAnimatedNumber | null = null; + public [PropertySymbol.surfaceScale]: SVGAnimatedNumber | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns kernelUnitLengthX. + * + * @returns KernelUnitLengthX. + */ + public get kernelUnitLengthX(): SVGAnimatedNumber { + if (!this[PropertySymbol.kernelUnitLengthX]) { + this[PropertySymbol.kernelUnitLengthX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('kernelUnitLengthX'), + setAttribute: (value) => this.setAttribute('kernelUnitLengthX', value) + } + ); + } + return this[PropertySymbol.kernelUnitLengthX]; + } + + /** + * Returns kernelUnitLengthY. + * + * @returns KernelUnitLengthY. + */ + public get kernelUnitLengthY(): SVGAnimatedNumber { + if (!this[PropertySymbol.kernelUnitLengthY]) { + this[PropertySymbol.kernelUnitLengthY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('kernelUnitLengthY'), + setAttribute: (value) => this.setAttribute('kernelUnitLengthY', value) + } + ); + } + return this[PropertySymbol.kernelUnitLengthY]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns specularConstant. + * + * @returns SpecularConstant. + */ + public get specularConstant(): SVGAnimatedNumber { + if (!this[PropertySymbol.specularConstant]) { + this[PropertySymbol.specularConstant] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('specularConstant'), + setAttribute: (value) => this.setAttribute('specularConstant', value), + defaultValue: 1 + } + ); + } + return this[PropertySymbol.specularConstant]; + } + + /** + * Returns specularExponent. + * + * @returns SpecularExponent. + */ + public get specularExponent(): SVGAnimatedNumber { + if (!this[PropertySymbol.specularExponent]) { + this[PropertySymbol.specularExponent] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('specularExponent'), + setAttribute: (value) => this.setAttribute('specularExponent', value), + defaultValue: 1 + } + ); + } + return this[PropertySymbol.specularExponent]; + } + + /** + * Returns surfaceScale. + * + * @returns SurfaceScale. + */ + public get surfaceScale(): SVGAnimatedNumber { + if (!this[PropertySymbol.surfaceScale]) { + this[PropertySymbol.surfaceScale] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('surfaceScale'), + setAttribute: (value) => this.setAttribute('surfaceScale', value), + defaultValue: 1 + } + ); + } + return this[PropertySymbol.surfaceScale]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-spot-light-element/SVGFESpotLightElement.ts b/packages/happy-dom/src/nodes/svg-fe-spot-light-element/SVGFESpotLightElement.ts new file mode 100644 index 000000000..aa3202960 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-spot-light-element/SVGFESpotLightElement.ts @@ -0,0 +1,173 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; + +/** + * SVGFESpotLightElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFESpotLightElement + */ +export default class SVGFESpotLightElement extends SVGElement { + // Internal properties + public [PropertySymbol.x]: SVGAnimatedNumber | null = null; + public [PropertySymbol.y]: SVGAnimatedNumber | null = null; + public [PropertySymbol.z]: SVGAnimatedNumber | null = null; + public [PropertySymbol.pointsAtX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.pointsAtY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.pointsAtZ]: SVGAnimatedNumber | null = null; + public [PropertySymbol.specularExponent]: SVGAnimatedNumber | null = null; + public [PropertySymbol.limitingConeAngle]: SVGAnimatedNumber | null = null; + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedNumber { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedNumber { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Returns z. + * + * @returns Z. + */ + public get z(): SVGAnimatedNumber { + if (!this[PropertySymbol.z]) { + this[PropertySymbol.z] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('z'), + setAttribute: (value) => this.setAttribute('z', value) + } + ); + } + return this[PropertySymbol.z]; + } + + /** + * Returns pointsAtX. + * + * @returns PointsAtX. + */ + public get pointsAtX(): SVGAnimatedNumber { + if (!this[PropertySymbol.pointsAtX]) { + this[PropertySymbol.pointsAtX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('pointsAtX'), + setAttribute: (value) => this.setAttribute('pointsAtX', value) + } + ); + } + return this[PropertySymbol.pointsAtX]; + } + + /** + * Returns pointsAtY. + * + * @returns PointsAtY. + */ + public get pointsAtY(): SVGAnimatedNumber { + if (!this[PropertySymbol.pointsAtY]) { + this[PropertySymbol.pointsAtY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('pointsAtY'), + setAttribute: (value) => this.setAttribute('pointsAtY', value) + } + ); + } + return this[PropertySymbol.pointsAtY]; + } + + /** + * Returns pointsAtZ. + * + * @returns PointsAtZ. + */ + public get pointsAtZ(): SVGAnimatedNumber { + if (!this[PropertySymbol.pointsAtZ]) { + this[PropertySymbol.pointsAtZ] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('pointsAtZ'), + setAttribute: (value) => this.setAttribute('pointsAtZ', value) + } + ); + } + return this[PropertySymbol.pointsAtZ]; + } + + /** + * Returns specularExponent. + * + * @returns SpecularExponent. + */ + public get specularExponent(): SVGAnimatedNumber { + if (!this[PropertySymbol.specularExponent]) { + this[PropertySymbol.specularExponent] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('specularExponent'), + setAttribute: (value) => this.setAttribute('specularExponent', value), + defaultValue: 1 + } + ); + } + return this[PropertySymbol.specularExponent]; + } + + /** + * Returns limitingConeAngle. + * + * @returns LimitingConeAngle. + */ + public get limitingConeAngle(): SVGAnimatedNumber { + if (!this[PropertySymbol.limitingConeAngle]) { + this[PropertySymbol.limitingConeAngle] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('limitingConeAngle'), + setAttribute: (value) => this.setAttribute('limitingConeAngle', value) + } + ); + } + return this[PropertySymbol.limitingConeAngle]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-tile-element/SVGFETileElement.ts b/packages/happy-dom/src/nodes/svg-fe-tile-element/SVGFETileElement.ts new file mode 100644 index 000000000..6ed548b93 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-tile-element/SVGFETileElement.ts @@ -0,0 +1,133 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; + +/** + * SVGFETileElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFETileElement + */ +export default class SVGFETileElement extends SVGElement { + // Internal properties + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.in1]: SVGAnimatedString | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns in1. + * + * @returns In1. + */ + public get in1(): SVGAnimatedString { + if (!this[PropertySymbol.in1]) { + this[PropertySymbol.in1] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('in'), + setAttribute: (value) => this.setAttribute('in', value) + } + ); + } + return this[PropertySymbol.in1]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-fe-turbulence-element/SVGFETurbulenceElement.ts b/packages/happy-dom/src/nodes/svg-fe-turbulence-element/SVGFETurbulenceElement.ts new file mode 100644 index 000000000..253fa21eb --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-fe-turbulence-element/SVGFETurbulenceElement.ts @@ -0,0 +1,249 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGAnimatedInteger from '../../svg/SVGAnimatedInteger.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; + +/** + * SVGFETurbulenceElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFETurbulenceElement + */ +export default class SVGFETurbulenceElement extends SVGElement { + // Static properties + public static readonly SVG_TURBULENCE_TYPE_UNKNOWN = 0; + public static readonly SVG_TURBULENCE_TYPE_FRACTALNOISE = 1; + public static readonly SVG_TURBULENCE_TYPE_TURBULENCE = 2; + + public static readonly SVG_STITCHTYPE_UNKNOWN = 0; + public static readonly SVG_STITCHTYPE_STITCH = 1; + public static readonly SVG_STITCHTYPE_NOSTITCH = 2; + + // Internal properties + public [PropertySymbol.baseFrequencyX]: SVGAnimatedNumber | null = null; + public [PropertySymbol.baseFrequencyY]: SVGAnimatedNumber | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.numOctaves]: SVGAnimatedInteger | null = null; + public [PropertySymbol.result]: SVGAnimatedString | null = null; + public [PropertySymbol.seed]: SVGAnimatedNumber | null = null; + public [PropertySymbol.stitchTiles]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.type]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + + /** + * Returns baseFrequencyX. + * + * @returns Base frequency x. + */ + public get baseFrequencyX(): SVGAnimatedNumber { + if (!this[PropertySymbol.baseFrequencyX]) { + this[PropertySymbol.baseFrequencyX] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('baseFrequencyX'), + setAttribute: (value) => this.setAttribute('baseFrequencyX', value) + } + ); + } + return this[PropertySymbol.baseFrequencyX]; + } + + /** + * Returns baseFrequencyY. + * + * @returns Base frequency y. + */ + public get baseFrequencyY(): SVGAnimatedNumber { + if (!this[PropertySymbol.baseFrequencyY]) { + this[PropertySymbol.baseFrequencyY] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('baseFrequencyY'), + setAttribute: (value) => this.setAttribute('baseFrequencyY', value) + } + ); + } + return this[PropertySymbol.baseFrequencyY]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns numOctaves. + * + * @returns Num octaves. + */ + public get numOctaves(): SVGAnimatedInteger { + if (!this[PropertySymbol.numOctaves]) { + this[PropertySymbol.numOctaves] = new SVGAnimatedInteger( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('numOctaves'), + setAttribute: (value) => this.setAttribute('numOctaves', value) + } + ); + } + return this[PropertySymbol.numOctaves]; + } + + /** + * Returns result. + * + * @returns Result. + */ + public get result(): SVGAnimatedString { + if (!this[PropertySymbol.result]) { + this[PropertySymbol.result] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('result'), + setAttribute: (value) => this.setAttribute('result', value) + } + ); + } + return this[PropertySymbol.result]; + } + + /** + * Returns seed. + * + * @returns Seed. + */ + public get seed(): SVGAnimatedNumber { + if (!this[PropertySymbol.seed]) { + this[PropertySymbol.seed] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('seed'), + setAttribute: (value) => this.setAttribute('seed', value) + } + ); + } + return this[PropertySymbol.seed]; + } + + /** + * Returns stitchTiles. + * + * @returns Stitch tiles. + */ + public get stitchTiles(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.stitchTiles]) { + this[PropertySymbol.stitchTiles] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('stitchTiles'), + setAttribute: (value) => this.setAttribute('stitchTiles', value), + values: ['stitch', 'noStitch'], + defaultValue: 'stitch' + } + ); + } + return this[PropertySymbol.stitchTiles]; + } + + /** + * Returns type. + * + * @returns Type. + */ + public get type(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.type]) { + this[PropertySymbol.type] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('type'), + setAttribute: (value) => this.setAttribute('type', value), + values: ['fractalNoise', 'turbulence'], + defaultValue: 'turbulence' + } + ); + } + return this[PropertySymbol.type]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-filter-element/SVGFilterElement.ts b/packages/happy-dom/src/nodes/svg-filter-element/SVGFilterElement.ts new file mode 100644 index 000000000..13812a46b --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-filter-element/SVGFilterElement.ts @@ -0,0 +1,158 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; + +/** + * SVG Filter Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGFilterElement + */ +export default class SVGFilterElement extends SVGElement { + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + public [PropertySymbol.filterUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.primitiveUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns filter units. + * + * @returns Filter units. + */ + public get filterUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.filterUnits]) { + this[PropertySymbol.filterUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('filterUnits'), + setAttribute: (value) => this.setAttribute('filterUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'userSpaceOnUse' + } + ); + } + return this[PropertySymbol.filterUnits]; + } + + /** + * Returns primitive units. + * + * @returns Primitive units. + */ + public get primitiveUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.primitiveUnits]) { + this[PropertySymbol.primitiveUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('primitiveUnits'), + setAttribute: (value) => this.setAttribute('primitiveUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'userSpaceOnUse' + } + ); + } + return this[PropertySymbol.primitiveUnits]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-foreign-object-element/SVGForeignObjectElement.ts b/packages/happy-dom/src/nodes/svg-foreign-object-element/SVGForeignObjectElement.ts new file mode 100644 index 000000000..8f3dc73ec --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-foreign-object-element/SVGForeignObjectElement.ts @@ -0,0 +1,92 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG ForeignObject Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGForeignObjectElement + */ +export default class SVGForeignObjectElement extends SVGGraphicsElement { + // Internal properties + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-g-element/SVGGElement.ts b/packages/happy-dom/src/nodes/svg-g-element/SVGGElement.ts new file mode 100644 index 000000000..8e315f88a --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-g-element/SVGGElement.ts @@ -0,0 +1,8 @@ +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG G Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGGElement + */ +export default class SVGGElement extends SVGGraphicsElement {} diff --git a/packages/happy-dom/src/nodes/svg-geometry-element/SVGGeometryElement.ts b/packages/happy-dom/src/nodes/svg-geometry-element/SVGGeometryElement.ts new file mode 100644 index 000000000..0af5b3e3b --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-geometry-element/SVGGeometryElement.ts @@ -0,0 +1,94 @@ +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGPoint from '../../svg/SVGPoint.js'; + +/** + * SVG Geometry Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGGeometryElement + */ +export default class SVGGeometryElement extends SVGGraphicsElement { + // Internal properties + public [PropertySymbol.pathLength]: SVGAnimatedNumber | null = null; + + /** + * Returns path length. + * + * @returns Path length. + */ + public get pathLength(): SVGAnimatedNumber { + if (!this[PropertySymbol.pathLength]) { + this[PropertySymbol.pathLength] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('pathLength'), + setAttribute: (value) => this.setAttribute('pathLength', value) + } + ); + } + return this[PropertySymbol.pathLength]!; + } + + /** + * Returns true if the point is in the fill of the element. + * + * Not implemented yet. + * + * @param point Point. + * @returns True if the point is in the fill of the element. + */ + public isPointInFill(point: SVGPoint): boolean { + if (!(point instanceof SVGPoint)) { + throw new TypeError( + `Failed to execute 'isPointInFill' on 'SVGGeometryElement': parameter 1 is not of type 'SVGPoint'.` + ); + } + // TODO: Implement isPointInFill() + return false; + } + + /** + * Returns true if the point is in the stroke of the element. + * + * Not implemented yet. + * + * @param point Point. + * @returns True if the point is in the stroke of the element. + */ + public isPointInStroke(point: SVGPoint): boolean { + if (!(point instanceof SVGPoint)) { + throw new TypeError( + `Failed to execute 'isPointInFill' on 'SVGGeometryElement': parameter 1 is not of type 'SVGPoint'.` + ); + } + // TODO: Implement isPointInStroke() + return false; + } + + /** + * Returns total length. + * + * Not implemented yet. + * + * @returns Total length. + */ + public getTotalLength(): number { + // TODO: Implement getTotalLength() + return 0; + } + + /** + * Returns point at length. + * + * Not implemented yet. + * + * @param _distance Distance. + * @returns Point at length. + */ + public getPointAtLength(_distance: number): SVGPoint { + // TODO: Implement getPointAtLength() + return new SVGPoint(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } +} diff --git a/packages/happy-dom/src/nodes/svg-gradient-element/SVGGradientElement.ts b/packages/happy-dom/src/nodes/svg-gradient-element/SVGGradientElement.ts new file mode 100644 index 000000000..8c6941dcc --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-gradient-element/SVGGradientElement.ts @@ -0,0 +1,104 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedTransformList from '../../svg/SVGAnimatedTransformList.js'; +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG Gradient Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGGradientElement + */ +export default class SVGGradientElement extends SVGGraphicsElement { + // Public static properties + public static readonly SVG_SPREADMETHOD_UNKNOWN = 0; + public static readonly SVG_SPREADMETHOD_PAD = 1; + public static readonly SVG_SPREADMETHOD_REFLECT = 2; + public static readonly SVG_SPREADMETHOD_REPEAT = 3; + + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + public [PropertySymbol.gradientUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.gradientTransform]: SVGAnimatedTransformList | null = null; + public [PropertySymbol.spreadMethod]: SVGAnimatedEnumeration | null = null; + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns gradient units. + * + * @returns Gradient units. + */ + public get gradientUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.gradientUnits]) { + this[PropertySymbol.gradientUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('gradientUnits'), + setAttribute: (value) => this.setAttribute('gradientUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'objectBoundingBox' + } + ); + } + return this[PropertySymbol.gradientUnits]; + } + + /** + * Returns gradient transform. + * + * @returns Gradient transform. + */ + public get gradientTransform(): SVGAnimatedTransformList { + if (!this[PropertySymbol.gradientTransform]) { + this[PropertySymbol.gradientTransform] = new SVGAnimatedTransformList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('gradientTransform'), + setAttribute: (value) => this.setAttribute('gradientTransform', value) + } + ); + } + return this[PropertySymbol.gradientTransform]; + } + + /** + * Returns spread method. + * + * @returns Spread method. + */ + public get spreadMethod(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.spreadMethod]) { + this[PropertySymbol.spreadMethod] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('spreadMethod'), + setAttribute: (value) => this.setAttribute('spreadMethod', value), + values: ['pad', 'reflect', 'repeat'], + defaultValue: 'pad' + } + ); + } + return this[PropertySymbol.spreadMethod]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-graphics-element/SVGGraphicsElement.ts b/packages/happy-dom/src/nodes/svg-graphics-element/SVGGraphicsElement.ts new file mode 100644 index 000000000..cc3de25c1 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-graphics-element/SVGGraphicsElement.ts @@ -0,0 +1,108 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import DOMRect from '../../dom/DOMRect.js'; +import DOMMatrix from '../../dom/dom-matrix/DOMMatrix.js'; +import SVGStringList from '../../svg/SVGStringList.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedTransformList from '../../svg/SVGAnimatedTransformList.js'; +import Event from '../../event/Event.js'; + +/** + * SVG Graphics Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement + */ +export default class SVGGraphicsElement extends SVGElement { + // Internal properties + public [PropertySymbol.requiredExtensions]: SVGStringList | null = null; + public [PropertySymbol.systemLanguage]: SVGStringList | null = null; + public [PropertySymbol.transform]: SVGAnimatedTransformList | null = null; + + // Events + public oncopy: (event: Event) => void | null = null; + public oncut: (event: Event) => void | null = null; + public onpaste: (event: Event) => void | null = null; + + /** + * Returns required extensions. + * + * @returns Required extensions. + */ + public get requiredExtensions(): SVGStringList { + if (!this[PropertySymbol.requiredExtensions]) { + this[PropertySymbol.requiredExtensions] = new SVGStringList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('requiredExtensions'), + setAttribute: (value) => this.setAttribute('requiredExtensions', value) + } + ); + } + return this[PropertySymbol.requiredExtensions]; + } + + /** + * Returns system language. + * + * @returns System language. + */ + public get systemLanguage(): SVGStringList { + if (!this[PropertySymbol.systemLanguage]) { + this[PropertySymbol.systemLanguage] = new SVGStringList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('systemLanguage'), + setAttribute: (value) => this.setAttribute('systemLanguage', value) + } + ); + } + return this[PropertySymbol.systemLanguage]; + } + + /** + * Returns transform. + * + * @returns Transform. + */ + public get transform(): SVGAnimatedTransformList { + if (!this[PropertySymbol.transform]) { + this[PropertySymbol.transform] = new SVGAnimatedTransformList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('transform'), + setAttribute: (value) => this.setAttribute('transform', value) + } + ); + } + return this[PropertySymbol.transform]; + } + + /** + * Returns DOM rect. + * + * @returns DOM rect. + */ + public getBBox(): DOMRect { + return new DOMRect(); + } + + /** + * Returns CTM. + * + * @returns CTM. + */ + public getCTM(): DOMMatrix { + return new DOMMatrix(); + } + + /** + * Returns screen CTM. + * + * @returns Screen CTM. + */ + public getScreenCTM(): DOMMatrix { + return new DOMMatrix(); + } +} diff --git a/packages/happy-dom/src/nodes/svg-image-element/SVGImageElement.ts b/packages/happy-dom/src/nodes/svg-image-element/SVGImageElement.ts new file mode 100644 index 000000000..65b1ca448 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-image-element/SVGImageElement.ts @@ -0,0 +1,188 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedPreserveAspectRatio from '../../svg/SVGAnimatedPreserveAspectRatio.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG Image Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGImageElement + */ +export default class SVGImageElement extends SVGGraphicsElement { + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + public [PropertySymbol.preserveAspectRatio]: SVGAnimatedPreserveAspectRatio | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + + /** + * Returns "crossorigin" attribute. + * + * @returns Cross origin. + */ + public get crossOrigin(): string { + return this.getAttribute('crossorigin'); + } + + /** + * Sets "crossorigin" attribute. + * + * @param value Cross origin. + */ + public set crossOrigin(value: string) { + this.setAttribute('crossorigin', value); + } + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns decoding. + * + * @returns Decoding. + */ + public get decoding(): string { + const value = this.getAttribute('decoding'); + switch (value) { + case 'sync': + case 'async': + case 'auto': + return value; + default: + return 'auto'; + } + } + + /** + * Sets decoding. + * + * @param value Decoding. + */ + public set decoding(value: string) { + this.setAttribute('decoding', value); + } + + /** + * Returns preserve aspect ratio. + * + * @returns Preserve aspect ratio. + */ + public get preserveAspectRatio(): SVGAnimatedPreserveAspectRatio { + if (!this[PropertySymbol.preserveAspectRatio]) { + this[PropertySymbol.preserveAspectRatio] = new SVGAnimatedPreserveAspectRatio( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('preserveAspectRatio'), + setAttribute: (value) => this.setAttribute('preserveAspectRatio', value) + } + ); + } + return this[PropertySymbol.preserveAspectRatio]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Decodes the image. + * + * @returns Promise. + */ + public decode(): Promise { + // TODO: Implement decode() + return Promise.resolve(); + } +} diff --git a/packages/happy-dom/src/nodes/svg-line-element/SVGLineElement.ts b/packages/happy-dom/src/nodes/svg-line-element/SVGLineElement.ts new file mode 100644 index 000000000..748c11614 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-line-element/SVGLineElement.ts @@ -0,0 +1,92 @@ +import SVGGeometryElement from '../svg-geometry-element/SVGGeometryElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVG Line Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGLineElement + */ +export default class SVGLineElement extends SVGGeometryElement { + // Internal properties + public [PropertySymbol.x1]: SVGAnimatedLength | null = null; + public [PropertySymbol.y1]: SVGAnimatedLength | null = null; + public [PropertySymbol.x2]: SVGAnimatedLength | null = null; + public [PropertySymbol.y2]: SVGAnimatedLength | null = null; + + /** + * Returns x1 position. + * + * @returns X1 position. + */ + public get x1(): SVGAnimatedLength { + if (!this[PropertySymbol.x1]) { + this[PropertySymbol.x1] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x1'), + setAttribute: (value) => this.setAttribute('x1', value) + } + ); + } + return this[PropertySymbol.x1]; + } + + /** + * Returns y1 position. + * + * @returns Y1 position. + */ + public get y1(): SVGAnimatedLength { + if (!this[PropertySymbol.y1]) { + this[PropertySymbol.y1] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y1'), + setAttribute: (value) => this.setAttribute('y1', value) + } + ); + } + return this[PropertySymbol.y1]; + } + + /** + * Returns x2 position. + * + * @returns X2 position. + */ + public get x2(): SVGAnimatedLength { + if (!this[PropertySymbol.x2]) { + this[PropertySymbol.x2] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x2'), + setAttribute: (value) => this.setAttribute('x2', value) + } + ); + } + return this[PropertySymbol.x2]; + } + + /** + * Returns y2 position. + * + * @returns Y2 position. + */ + public get y2(): SVGAnimatedLength { + if (!this[PropertySymbol.y2]) { + this[PropertySymbol.y2] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y2'), + setAttribute: (value) => this.setAttribute('y2', value) + } + ); + } + return this[PropertySymbol.y2]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-linear-gradient-element/SVGLinearGradientElement.ts b/packages/happy-dom/src/nodes/svg-linear-gradient-element/SVGLinearGradientElement.ts new file mode 100644 index 000000000..7c20f0538 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-linear-gradient-element/SVGLinearGradientElement.ts @@ -0,0 +1,92 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGGradientElement from '../svg-gradient-element/SVGGradientElement.js'; + +/** + * SVG LinearGradient Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGLinearGradientElement + */ +export default class SVGLinearGradientElement extends SVGGradientElement { + // Internal properties + public [PropertySymbol.x1]: SVGAnimatedLength | null = null; + public [PropertySymbol.y1]: SVGAnimatedLength | null = null; + public [PropertySymbol.x2]: SVGAnimatedLength | null = null; + public [PropertySymbol.y2]: SVGAnimatedLength | null = null; + + /** + * Returns x1 position. + * + * @returns X1 position. + */ + public get x1(): SVGAnimatedLength { + if (!this[PropertySymbol.x1]) { + this[PropertySymbol.x1] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x1'), + setAttribute: (value) => this.setAttribute('x1', value) + } + ); + } + return this[PropertySymbol.x1]; + } + + /** + * Returns y1 position. + * + * @returns Y1 position. + */ + public get y1(): SVGAnimatedLength { + if (!this[PropertySymbol.y1]) { + this[PropertySymbol.y1] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y1'), + setAttribute: (value) => this.setAttribute('y1', value) + } + ); + } + return this[PropertySymbol.y1]; + } + + /** + * Returns x2 position. + * + * @returns X2 position. + */ + public get x2(): SVGAnimatedLength { + if (!this[PropertySymbol.x2]) { + this[PropertySymbol.x2] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x2'), + setAttribute: (value) => this.setAttribute('x2', value) + } + ); + } + return this[PropertySymbol.x2]; + } + + /** + * Returns y2 position. + * + * @returns Y2 position. + */ + public get y2(): SVGAnimatedLength { + if (!this[PropertySymbol.y2]) { + this[PropertySymbol.y2] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y2'), + setAttribute: (value) => this.setAttribute('y2', value) + } + ); + } + return this[PropertySymbol.y2]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-m-path-element/SVGMPathElement.ts b/packages/happy-dom/src/nodes/svg-m-path-element/SVGMPathElement.ts new file mode 100644 index 000000000..16270eeab --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-m-path-element/SVGMPathElement.ts @@ -0,0 +1,32 @@ +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; + +/** + * SVG MPath Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGMPathElement + */ +export default class SVGMPathElement extends SVGElement { + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-marker-element/SVGMarkerElement.ts b/packages/happy-dom/src/nodes/svg-marker-element/SVGMarkerElement.ts new file mode 100644 index 000000000..8f5375c2e --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-marker-element/SVGMarkerElement.ts @@ -0,0 +1,230 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedAngle from '../../svg/SVGAnimatedAngle.js'; +import SVGAnimatedRect from '../../svg/SVGAnimatedRect.js'; +import SVGAnimatedPreserveAspectRatio from '../../svg/SVGAnimatedPreserveAspectRatio.js'; +import SVGAngle from '../../svg/SVGAngle.js'; +import SVGElement from '../svg-element/SVGElement.js'; + +/** + * SVG Rect Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGMarkerElement + */ +export default class SVGMarkerElement extends SVGElement { + // Static properties + public static readonly SVG_MARKER_ORIENT_UNKNOWN = 0; + public static readonly SVG_MARKER_ORIENT_AUTO = 1; + public static readonly SVG_MARKER_ORIENT_ANGLE = 2; + public static readonly SVG_MARKERUNITS_UNKNOWN = 0; + public static readonly SVG_MARKERUNITS_USERSPACEONUSE = 1; + public static readonly SVG_MARKERUNITS_STROKEWIDTH = 2; + + // Public properties + public readonly SVG_MARKER_ORIENT_UNKNOWN = 0; + public readonly SVG_MARKER_ORIENT_AUTO = 1; + public readonly SVG_MARKER_ORIENT_ANGLE = 2; + + // Internal properties + public [PropertySymbol.markerUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.markerWidth]: SVGAnimatedLength | null = null; + public [PropertySymbol.markerHeight]: SVGAnimatedLength | null = null; + public [PropertySymbol.orientType]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.orientAngle]: SVGAnimatedAngle | null = null; + public [PropertySymbol.refX]: SVGAnimatedLength | null = null; + public [PropertySymbol.refY]: SVGAnimatedLength | null = null; + public [PropertySymbol.viewBox]: SVGAnimatedRect | null = null; + public [PropertySymbol.preserveAspectRatio]: SVGAnimatedPreserveAspectRatio | null = null; + + /** + * Returns marker units. + * + * @returns Marker units. + */ + public get markerUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.markerUnits]) { + this[PropertySymbol.markerUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('markerUnits'), + setAttribute: (value) => this.setAttribute('markerUnits', value), + values: ['userSpaceOnUse', 'strokeWidth'], + defaultValue: 'strokeWidth' + } + ); + } + return this[PropertySymbol.markerUnits]; + } + + /** + * Returns marker width. + * + * @returns Marker width. + */ + public get markerWidth(): SVGAnimatedLength { + if (!this[PropertySymbol.markerWidth]) { + this[PropertySymbol.markerWidth] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('markerWidth'), + setAttribute: (value) => this.setAttribute('markerWidth', value) + } + ); + } + return this[PropertySymbol.markerWidth]; + } + + /** + * Returns marker height. + * + * @returns Marker height. + */ + public get markerHeight(): SVGAnimatedLength { + if (!this[PropertySymbol.markerHeight]) { + this[PropertySymbol.markerHeight] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('markerHeight'), + setAttribute: (value) => this.setAttribute('markerHeight', value) + } + ); + } + return this[PropertySymbol.markerHeight]; + } + + /** + * Returns orient type. + * + * @returns Orient type. + */ + public get orientType(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.orientType]) { + this[PropertySymbol.orientType] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('orient'), + setAttribute: (value) => this.setAttribute('orient', value), + values: ['auto', null], + defaultValue: 'auto' + } + ); + } + return this[PropertySymbol.orientType]; + } + + /** + * Returns orient angle. + * + * @returns Orient angle. + */ + public get orientAngle(): SVGAnimatedAngle { + if (!this[PropertySymbol.orientAngle]) { + this[PropertySymbol.orientAngle] = new SVGAnimatedAngle( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('orient'), + setAttribute: (value) => this.setAttribute('orient', value) + } + ); + } + return this[PropertySymbol.orientAngle]; + } + + /** + * Returns ref x. + * + * @returns Ref x. + */ + public get refX(): SVGAnimatedLength { + if (!this[PropertySymbol.refX]) { + this[PropertySymbol.refX] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('refX'), + setAttribute: (value) => this.setAttribute('refX', value) + } + ); + } + return this[PropertySymbol.refX]; + } + + /** + * Returns ref y. + * + * @returns Ref y. + */ + public get refY(): SVGAnimatedLength { + if (!this[PropertySymbol.refY]) { + this[PropertySymbol.refY] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('refY'), + setAttribute: (value) => this.setAttribute('refY', value) + } + ); + } + return this[PropertySymbol.refY]; + } + + /** + * Returns view box. + * + * @returns View box. + */ + public get viewBox(): SVGAnimatedRect { + if (!this[PropertySymbol.viewBox]) { + this[PropertySymbol.viewBox] = new SVGAnimatedRect( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('viewBox'), + setAttribute: (value) => this.setAttribute('viewBox', value) + } + ); + } + return this[PropertySymbol.viewBox]; + } + + /** + * Returns preserve aspect ratio. + * + * @returns Preserve aspect ratio. + */ + public get preserveAspectRatio(): SVGAnimatedPreserveAspectRatio { + if (!this[PropertySymbol.preserveAspectRatio]) { + this[PropertySymbol.preserveAspectRatio] = new SVGAnimatedPreserveAspectRatio( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('preserveAspectRatio'), + setAttribute: (value) => this.setAttribute('preserveAspectRatio', value) + } + ); + } + return this[PropertySymbol.preserveAspectRatio]; + } + + /** + * Sets the value of the orient attribute to auto. + */ + public setOrientToAuto(): void { + this.setAttribute('orient', 'auto'); + } + + /** + * Sets the value of the orient attribute to an angle. + * + * @param angle Angle. + */ + public setOrientToAngle(angle: SVGAngle): void { + this.setAttribute('orient', angle.valueAsString); + } +} diff --git a/packages/happy-dom/src/nodes/svg-mask-element/SVGMaskElement.ts b/packages/happy-dom/src/nodes/svg-mask-element/SVGMaskElement.ts new file mode 100644 index 000000000..546fc5c6c --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-mask-element/SVGMaskElement.ts @@ -0,0 +1,137 @@ +import SVGElement from '../svg-element/SVGElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVG Mask Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGMaskElement + */ +export default class SVGMaskElement extends SVGElement { + // Internal properties + public [PropertySymbol.maskUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.maskContentUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + + /** + * Returns mask units. + * + * @returns Mask units. + */ + public get maskUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.maskUnits]) { + this[PropertySymbol.maskUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('maskUnits'), + setAttribute: (value) => this.setAttribute('maskUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'userSpaceOnUse' + } + ); + } + return this[PropertySymbol.maskUnits]; + } + + /** + * Returns mask content units. + * + * @returns Mask content units. + */ + public get maskContentUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.maskContentUnits]) { + this[PropertySymbol.maskContentUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('maskContentUnits'), + setAttribute: (value) => this.setAttribute('maskContentUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'userSpaceOnUse' + } + ); + } + return this[PropertySymbol.maskContentUnits]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-metadata-element/SVGMetadataElement.ts b/packages/happy-dom/src/nodes/svg-metadata-element/SVGMetadataElement.ts new file mode 100644 index 000000000..ccc7f58d3 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-metadata-element/SVGMetadataElement.ts @@ -0,0 +1,8 @@ +import SVGElement from '../svg-element/SVGElement.js'; + +/** + * SVG Metadata Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGMetadataElement + */ +export default class SVGMetadataElement extends SVGElement {} diff --git a/packages/happy-dom/src/nodes/svg-path-element/SVGPathElement.ts b/packages/happy-dom/src/nodes/svg-path-element/SVGPathElement.ts new file mode 100644 index 000000000..26bf1d330 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-path-element/SVGPathElement.ts @@ -0,0 +1,8 @@ +import SVGGeometryElement from '../svg-geometry-element/SVGGeometryElement.js'; + +/** + * SVG Path Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement + */ +export default class SVGPathElement extends SVGGeometryElement {} diff --git a/packages/happy-dom/src/nodes/svg-pattern-element/SVGPatternElement.ts b/packages/happy-dom/src/nodes/svg-pattern-element/SVGPatternElement.ts new file mode 100644 index 000000000..f70b464f7 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-pattern-element/SVGPatternElement.ts @@ -0,0 +1,179 @@ +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGElement from '../svg-element/SVGElement.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedTransformList from '../../svg/SVGAnimatedTransformList.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVG Pattern Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPatternElement + */ +export default class SVGPatternElement extends SVGElement { + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + public [PropertySymbol.patternUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.patternContentUnits]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.patternTransform]: SVGAnimatedTransformList | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns pattern units. + * + * @returns Pattern units. + */ + public get patternUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.patternUnits]) { + this[PropertySymbol.patternUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('patternUnits'), + setAttribute: (value) => this.setAttribute('patternUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'objectBoundingBox' + } + ); + } + return this[PropertySymbol.patternUnits]; + } + + /** + * Returns pattern content units. + * + * @returns Pattern content units. + */ + public get patternContentUnits(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.patternContentUnits]) { + this[PropertySymbol.patternContentUnits] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('patternContentUnits'), + setAttribute: (value) => this.setAttribute('patternContentUnits', value), + values: ['userSpaceOnUse', 'objectBoundingBox'], + defaultValue: 'userSpaceOnUse' + } + ); + } + return this[PropertySymbol.patternContentUnits]; + } + + /** + * Returns pattern transform. + * + * @returns Pattern transform. + */ + public get patternTransform(): SVGAnimatedTransformList { + if (!this[PropertySymbol.patternTransform]) { + this[PropertySymbol.patternTransform] = new SVGAnimatedTransformList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('patternTransform'), + setAttribute: (value) => this.setAttribute('patternTransform', value) + } + ); + } + return this[PropertySymbol.patternTransform]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-polygon-element/SVGPolygonElement.ts b/packages/happy-dom/src/nodes/svg-polygon-element/SVGPolygonElement.ts new file mode 100644 index 000000000..09ac14f21 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-polygon-element/SVGPolygonElement.ts @@ -0,0 +1,53 @@ +import SVGGeometryElement from '../svg-geometry-element/SVGGeometryElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGPointList from '../../svg/SVGPointList.js'; + +/** + * SVG Polygon Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPolygonElement + */ +export default class SVGPolygonElement extends SVGGeometryElement { + // Internal properties + public [PropertySymbol.animatedPoints]: SVGPointList | null = null; + public [PropertySymbol.points]: SVGPointList | null = null; + + /** + * Returns animated points. + * + * @returns Animated points. + */ + public get animatedPoints(): SVGPointList { + if (!this[PropertySymbol.animatedPoints]) { + this[PropertySymbol.animatedPoints] = new SVGPointList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: () => this.getAttribute('points'), + setAttribute: () => {} + } + ); + } + return this[PropertySymbol.animatedPoints]; + } + + /** + * Returns points. + * + * @returns Points. + */ + public get points(): SVGPointList { + if (!this[PropertySymbol.points]) { + this[PropertySymbol.points] = new SVGPointList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('points'), + setAttribute: (value) => this.setAttribute('points', value) + } + ); + } + return this[PropertySymbol.points]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-polyline-element/SVGPolylineElement.ts b/packages/happy-dom/src/nodes/svg-polyline-element/SVGPolylineElement.ts new file mode 100644 index 000000000..e96d10bd7 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-polyline-element/SVGPolylineElement.ts @@ -0,0 +1,53 @@ +import SVGGeometryElement from '../svg-geometry-element/SVGGeometryElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGPointList from '../../svg/SVGPointList.js'; + +/** + * SVG Polyline Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPolylineElement + */ +export default class SVGPolylineElement extends SVGGeometryElement { + // Internal properties + public [PropertySymbol.animatedPoints]: SVGPointList | null = null; + public [PropertySymbol.points]: SVGPointList | null = null; + + /** + * Returns animated points. + * + * @returns Animated points. + */ + public get animatedPoints(): SVGPointList { + if (!this[PropertySymbol.animatedPoints]) { + this[PropertySymbol.animatedPoints] = new SVGPointList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: () => this.getAttribute('points'), + setAttribute: () => {} + } + ); + } + return this[PropertySymbol.animatedPoints]; + } + + /** + * Returns points. + * + * @returns Points. + */ + public get points(): SVGPointList { + if (!this[PropertySymbol.points]) { + this[PropertySymbol.points] = new SVGPointList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('points'), + setAttribute: (value) => this.setAttribute('points', value) + } + ); + } + return this[PropertySymbol.points]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-radial-gradient-element/SVGRadialGradientElement.ts b/packages/happy-dom/src/nodes/svg-radial-gradient-element/SVGRadialGradientElement.ts new file mode 100644 index 000000000..1ab20e65d --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-radial-gradient-element/SVGRadialGradientElement.ts @@ -0,0 +1,112 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGGradientElement from '../svg-gradient-element/SVGGradientElement.js'; + +/** + * SVG Radial Gradient Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGRadialGradientElement + */ +export default class SVGRadialGradientElement extends SVGGradientElement { + // Internal properties + public [PropertySymbol.cx]: SVGAnimatedLength | null = null; + public [PropertySymbol.cy]: SVGAnimatedLength | null = null; + public [PropertySymbol.r]: SVGAnimatedLength | null = null; + public [PropertySymbol.fx]: SVGAnimatedLength | null = null; + public [PropertySymbol.fy]: SVGAnimatedLength | null = null; + + /** + * Returns cx. + * + * @returns Cx. + */ + public get cx(): SVGAnimatedLength { + if (!this[PropertySymbol.cx]) { + this[PropertySymbol.cx] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('cx'), + setAttribute: (value) => this.setAttribute('cx', value) + } + ); + } + return this[PropertySymbol.cx]; + } + + /** + * Returns cy. + * + * @returns Cy. + */ + public get cy(): SVGAnimatedLength { + if (!this[PropertySymbol.cy]) { + this[PropertySymbol.cy] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('cy'), + setAttribute: (value) => this.setAttribute('cy', value) + } + ); + } + return this[PropertySymbol.cy]; + } + + /** + * Returns r. + * + * @returns R. + */ + public get r(): SVGAnimatedLength { + if (!this[PropertySymbol.r]) { + this[PropertySymbol.r] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('r'), + setAttribute: (value) => this.setAttribute('r', value) + } + ); + } + return this[PropertySymbol.r]; + } + + /** + * Returns fx. + * + * @returns Fx. + */ + public get fx(): SVGAnimatedLength { + if (!this[PropertySymbol.fx]) { + this[PropertySymbol.fx] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('fx'), + setAttribute: (value) => this.setAttribute('fx', value) + } + ); + } + return this[PropertySymbol.fx]; + } + + /** + * Returns fy. + * + * @returns Fy. + */ + public get fy(): SVGAnimatedLength { + if (!this[PropertySymbol.fy]) { + this[PropertySymbol.fy] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('fy'), + setAttribute: (value) => this.setAttribute('fy', value) + } + ); + } + return this[PropertySymbol.fy]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-rect-element/SVGRectElement.ts b/packages/happy-dom/src/nodes/svg-rect-element/SVGRectElement.ts new file mode 100644 index 000000000..107d829c5 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-rect-element/SVGRectElement.ts @@ -0,0 +1,132 @@ +import SVGGeometryElement from '../svg-geometry-element/SVGGeometryElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; + +/** + * SVG Rect Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGRectElement + */ +export default class SVGRectElement extends SVGGeometryElement { + // Internal properties + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.rx]: SVGAnimatedLength | null = null; + public [PropertySymbol.ry]: SVGAnimatedLength | null = null; + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns rx. + * + * @returns Rx. + */ + public get rx(): SVGAnimatedLength { + if (!this[PropertySymbol.rx]) { + this[PropertySymbol.rx] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('rx'), + setAttribute: (value) => this.setAttribute('rx', value) + } + ); + } + return this[PropertySymbol.rx]; + } + + /** + * Returns ry. + * + * @returns Ry. + */ + public get ry(): SVGAnimatedLength { + if (!this[PropertySymbol.ry]) { + this[PropertySymbol.ry] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('ry'), + setAttribute: (value) => this.setAttribute('ry', value) + } + ); + } + return this[PropertySymbol.ry]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-script-element/SVGScriptElement.ts b/packages/happy-dom/src/nodes/svg-script-element/SVGScriptElement.ts new file mode 100644 index 000000000..7c4a5be6f --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-script-element/SVGScriptElement.ts @@ -0,0 +1,50 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG Script Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGScriptElement + */ +export default class SVGScriptElement extends SVGGraphicsElement { + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns type. + * + * @returns Type. + */ + public get type(): string { + return this.getAttribute('type') || ''; + } + + /** + * Sets type. + * + * @param value New value. + */ + public set type(value: string) { + this.setAttribute('type', value); + } +} diff --git a/packages/happy-dom/src/nodes/svg-set-element/SVGSetElement.ts b/packages/happy-dom/src/nodes/svg-set-element/SVGSetElement.ts new file mode 100644 index 000000000..5ddfbea17 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-set-element/SVGSetElement.ts @@ -0,0 +1,8 @@ +import SVGAnimationElement from '../svg-animation-element/SVGAnimationElement.js'; + +/** + * SVG Set Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGSetElement + */ +export default class SVGSetElement extends SVGAnimationElement {} diff --git a/packages/happy-dom/src/nodes/svg-stop-element/SVGStopElement.ts b/packages/happy-dom/src/nodes/svg-stop-element/SVGStopElement.ts new file mode 100644 index 000000000..c5772683d --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-stop-element/SVGStopElement.ts @@ -0,0 +1,32 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedNumber from '../../svg/SVGAnimatedNumber.js'; +import SVGElement from '../svg-element/SVGElement.js'; + +/** + * SVG Stop Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGStopElement + */ +export default class SVGStopElement extends SVGElement { + // Internal properties + public [PropertySymbol.offset]: SVGAnimatedNumber | null = null; + + /** + * Returns offset. + * + * @returns Offset. + */ + public get offset(): SVGAnimatedNumber { + if (!this[PropertySymbol.offset]) { + this[PropertySymbol.offset] = new SVGAnimatedNumber( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('offset'), + setAttribute: (value) => this.setAttribute('offset', value) + } + ); + } + return this[PropertySymbol.offset]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-style-element/SVGStyleElement.ts b/packages/happy-dom/src/nodes/svg-style-element/SVGStyleElement.ts new file mode 100644 index 000000000..2c6517f56 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-style-element/SVGStyleElement.ts @@ -0,0 +1,122 @@ +import CSSStyleSheet from '../../css/CSSStyleSheet.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGElement from '../svg-element/SVGElement.js'; + +/** + * SVG Style Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGStyleElement + */ +export default class SVGStyleElement extends SVGElement { + // Internal properties + public [PropertySymbol.sheet]: CSSStyleSheet | null = null; + public [PropertySymbol.styleNode] = this; + public [PropertySymbol.disabled] = false; + + /** + * Returns media. + * + * @returns Media. + */ + public get media(): string { + return this.getAttribute('media') || 'all'; + } + + /** + * Sets media. + * + * @param media Media. + */ + public set media(media: string) { + this.setAttribute('media', media); + } + + /** + * Returns type. + * + * @deprecated + * @returns Type. + */ + public get type(): string { + return this.getAttribute('type') || 'text/css'; + } + + /** + * Sets type. + * + * @deprecated + * @param type Type. + */ + public set type(type: string) { + this.setAttribute('type', type); + } + + /** + * Returns title. + * + * @returns Title. + */ + public get title(): string { + return this.getAttribute('title') || ''; + } + + /** + * Sets title. + * + * @param title Title. + */ + public set title(title: string) { + this.setAttribute('title', title); + } + + /** + * Returns disabled. + * + * @returns Disabled. + */ + public get disabled(): boolean { + return this[PropertySymbol.disabled]; + } + + /** + * Sets disabled. + * + * @param disabled Disabled. + */ + public set disabled(disabled: boolean) { + this[PropertySymbol.disabled] = Boolean(disabled); + } + + /** + * Returns style. + * + * @returns Style. + */ + public get sheet(): CSSStyleSheet { + if (!this[PropertySymbol.isConnected]) { + return null; + } + if (!this[PropertySymbol.sheet]) { + this[PropertySymbol.sheet] = new CSSStyleSheet(); + this[PropertySymbol.sheet].replaceSync(this.textContent); + } + return this[PropertySymbol.sheet]; + } + + /** + * @override + */ + public override [PropertySymbol.disconnectedFromDocument](): void { + super[PropertySymbol.disconnectedFromDocument](); + this[PropertySymbol.sheet] = null; + } + + /** + * Updates the CSSStyleSheet with the text content. + */ + public [PropertySymbol.updateSheet](): void { + if (this[PropertySymbol.sheet]) { + this[PropertySymbol.sheet].replaceSync(this.textContent); + } + } +} diff --git a/packages/happy-dom/src/nodes/svg-svg-element/SVGSVGElement.ts b/packages/happy-dom/src/nodes/svg-svg-element/SVGSVGElement.ts new file mode 100644 index 000000000..a74c7ff66 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-svg-element/SVGSVGElement.ts @@ -0,0 +1,470 @@ +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; +import SVGRect from '../../svg/SVGRect.js'; +import SVGPoint from '../../svg/SVGPoint.js'; +import SVGLength from '../../svg/SVGLength.js'; +import SVGAngle from '../../svg/SVGAngle.js'; +import SVGNumber from '../../svg/SVGNumber.js'; +import SVGTransform from '../../svg/SVGTransform.js'; +import SVGAnimatedRect from '../../svg/SVGAnimatedRect.js'; +import Event from '../../event/Event.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedPreserveAspectRatio from '../../svg/SVGAnimatedPreserveAspectRatio.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import Element from '../element/Element.js'; +import NodeList from '../node/NodeList.js'; +import SVGElement from '../svg-element/SVGElement.js'; +import SVGMatrix from '../../svg/SVGMatrix.js'; +import HTMLCollection from '../element/HTMLCollection.js'; +import ParentNodeUtility from '../parent-node/ParentNodeUtility.js'; +import IHTMLElementTagNameMap from '../../config/IHTMLElementTagNameMap.js'; +import ISVGElementTagNameMap from '../../config/ISVGElementTagNameMap.js'; + +/** + * SVGSVGElement. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGSVGElement + */ +export default class SVGSVGElement extends SVGGraphicsElement { + // Internal properties + public [PropertySymbol.preserveAspectRatio]: SVGAnimatedPreserveAspectRatio | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + public [PropertySymbol.currentScale]: number = 1; + public [PropertySymbol.viewBox]: SVGAnimatedRect | null = null; + + // Public properties + public declare cloneNode: (deep?: boolean) => SVGSVGElement; + + // Events + public onafterprint: (event: Event) => void | null = null; + public onbeforeprint: (event: Event) => void | null = null; + public onbeforeunload: (event: Event) => void | null = null; + public ongamepadconnected: (event: Event) => void | null = null; + public ongamepaddisconnected: (event: Event) => void | null = null; + public onhashchange: (event: Event) => void | null = null; + public onlanguagechange: (event: Event) => void | null = null; + public onmessage: (event: Event) => void | null = null; + public onmessageerror: (event: Event) => void | null = null; + public onoffline: (event: Event) => void | null = null; + public ononline: (event: Event) => void | null = null; + public onpagehide: (event: Event) => void | null = null; + public onpageshow: (event: Event) => void | null = null; + public onpopstate: (event: Event) => void | null = null; + public onrejectionhandled: (event: Event) => void | null = null; + public onstorage: (event: Event) => void | null = null; + public onunhandledrejection: (event: Event) => void | null = null; + public onunload: (event: Event) => void | null = null; + + /** + * Returns preserve aspect ratio. + * + * @returns Preserve aspect ratio. + */ + public get preserveAspectRatio(): SVGAnimatedPreserveAspectRatio { + if (!this[PropertySymbol.preserveAspectRatio]) { + this[PropertySymbol.preserveAspectRatio] = new SVGAnimatedPreserveAspectRatio( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('preserveAspectRatio'), + setAttribute: (value) => this.setAttribute('preserveAspectRatio', value) + } + ); + } + return this[PropertySymbol.preserveAspectRatio]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Returns currentScale. + * + * @returns CurrentScale. + */ + public get currentScale(): number { + return this[PropertySymbol.currentScale]; + } + + /** + * Sets currentScale. + * + * @param currentScale CurrentScale. + */ + public set currentScale(currentScale: number) { + const parsed = + typeof currentScale !== 'number' ? parseFloat(String(currentScale)) : currentScale; + if (isNaN(parsed)) { + throw this[PropertySymbol.window].TypeError( + `Failed to set the 'currentScale' property on 'SVGSVGElement': The provided float value is non-finite.` + ); + } + if (parsed < 1) { + return; + } + this[PropertySymbol.currentScale] = parsed; + } + + /** + * Returns current translate. + * + * @returns SVG point. + */ + public get currentTranslate(): SVGPoint { + return new SVGPoint(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns view box. + * + * @returns View box. + */ + public get viewBox(): SVGAnimatedRect { + if (!this[PropertySymbol.viewBox]) { + this[PropertySymbol.viewBox] = new SVGAnimatedRect( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('viewBox'), + setAttribute: (value) => this.setAttribute('viewBox', value) + } + ); + } + return this[PropertySymbol.viewBox]; + } + + /** + * Pauses animation. + */ + public pauseAnimations(): void {} + + /** + * Unpauses animation. + */ + public unpauseAnimations(): void {} + + /** + * Returns "true" if animation is paused. + * + * @returns "true" if animation is paused. + */ + public animationsPaused(): boolean { + return false; + } + + /** + * Returns the current time in seconds relative to the start time for the current SVG document fragment. + * + * @returns Current time in seconds. + */ + public getCurrentTime(): number { + return 0; + } + + /** + * Sets current time. + * + * @param _seconds Seconds. + */ + public setCurrentTime(_seconds: number): void {} + + /** + * Returns intersection list. + * + * @param _rect SVG Rect. + * @param _element SVG Element. + * @returns Intersection list. + */ + public getIntersectionList(_rect: SVGRect, _element: SVGElement): NodeList { + return new NodeList(PropertySymbol.illegalConstructor, []); + } + + /** + * Returns enclousure list. + * + * @param _rect SVG Rect. + * @param _element SVG Element. + * @returns Enclousure list. + */ + public getEnclosureList(_rect: SVGRect, _element: SVGElement): NodeList { + return new NodeList(PropertySymbol.illegalConstructor, []); + } + + /** + * Returns true if the rendered content of the given element intersects the supplied rectangle. + * + * @param _element SVG Element. + * @param _rect SVG Rect. + * @returns Intersection state. + */ + public checkIntersection(_element: SVGElement, _rect: SVGRect): boolean { + return false; + } + + /** + * Returns true if the rendered content of the given element is entirely contained within the supplied rectangle. + * + * @param _element SVG Element. + * @param _rect SVG Rect. + * @returns Enclousure state. + */ + public checkEnclosure(_element: SVGElement, _rect: SVGRect): boolean { + return false; + } + + /** + * Unselects any selected objects, including any selections of text strings and type-in bars. + */ + public deselectAll(): void {} + + /** + * Returns a number. + * + * @returns Number. + */ + public createSVGNumber(): SVGNumber { + return new SVGNumber(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a length. + * + * @returns Length. + */ + public createSVGLength(): SVGLength { + return new SVGLength(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a angle. + * + * @returns Angle. + */ + public createSVGAngle(): SVGAngle { + return new SVGAngle(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a point. + * + * @returns Point. + */ + public createSVGPoint(): SVGPoint { + return new SVGPoint(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a matrix. + * + * @returns Matrix. + */ + public createSVGMatrix(): SVGMatrix { + return new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a rect. + * + * @returns Rect. + */ + public createSVGRect(): SVGRect { + return new SVGRect(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a transform. + * + * @returns Transform. + */ + public createSVGTransform(): SVGTransform { + return new SVGTransform(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a transform from a matrix. + * + * @param matrix Matrix. + */ + public createSVGTransformFromMatrix(matrix: SVGMatrix): SVGTransform { + const transform = new SVGTransform( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window] + ); + transform.setMatrix(matrix); + return transform; + } + + /** + * Returns an elements by class name. + * + * @param className Tag name. + * @returns Matching element. + */ + public getElementsByClassName(className: string): HTMLCollection { + return ParentNodeUtility.getElementsByClassName(this, className); + } + + /** + * Returns an elements by tag name. + * + * @param tagName Tag name. + * @returns Matching element. + */ + public getElementsByTagName( + tagName: K + ): HTMLCollection; + + /** + * Returns an elements by tag name. + * + * @param tagName Tag name. + * @returns Matching element. + */ + public getElementsByTagName(tagName: string): HTMLCollection; + + /** + * Returns an elements by tag name. + * + * @param tagName Tag name. + * @returns Matching element. + */ + public getElementsByTagName(tagName: string): HTMLCollection { + return ParentNodeUtility.getElementsByTagName(this, tagName); + } + + /** + * Returns an elements by tag name and namespace. + * + * @param namespaceURI Namespace URI. + * @param tagName Tag name. + * @returns Matching element. + */ + public getElementsByTagNameNS( + namespaceURI: 'http://www.w3.org/1999/xhtml', + tagName: K + ): HTMLCollection; + + /** + * Returns an elements by tag name and namespace. + * + * @param namespaceURI Namespace URI. + * @param tagName Tag name. + * @returns Matching element. + */ + public getElementsByTagNameNS( + namespaceURI: 'http://www.w3.org/2000/svg', + tagName: K + ): HTMLCollection; + + /** + * Returns an elements by tag name and namespace. + * + * @param namespaceURI Namespace URI. + * @param tagName Tag name. + * @returns Matching element. + */ + public getElementsByTagNameNS(namespaceURI: string, tagName: string): HTMLCollection; + + /** + * Returns an elements by tag name and namespace. + * + * @param namespaceURI Namespace URI. + * @param tagName Tag name. + * @returns Matching element. + */ + public getElementsByTagNameNS(namespaceURI: string, tagName: string): HTMLCollection { + return ParentNodeUtility.getElementsByTagNameNS(this, namespaceURI, tagName); + } + + /** + * Returns an element by ID. + * + * @param id ID. + * @returns Matching element. + */ + public getElementById(id: string): Element | null { + return ParentNodeUtility.getElementById(this, id); + } + + /** + * @override + */ + public override [PropertySymbol.cloneNode](deep = false): SVGSVGElement { + return super[PropertySymbol.cloneNode](deep); + } +} diff --git a/packages/happy-dom/src/nodes/svg-switch-element/SVGSwitchElement.ts b/packages/happy-dom/src/nodes/svg-switch-element/SVGSwitchElement.ts new file mode 100644 index 000000000..7d08d5d34 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-switch-element/SVGSwitchElement.ts @@ -0,0 +1,8 @@ +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG Switch Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGSwitchElement + */ +export default class SVGSwitchElement extends SVGGraphicsElement {} diff --git a/packages/happy-dom/src/nodes/svg-symbol-element/SVGSymbolElement.ts b/packages/happy-dom/src/nodes/svg-symbol-element/SVGSymbolElement.ts new file mode 100644 index 000000000..87f425986 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-symbol-element/SVGSymbolElement.ts @@ -0,0 +1,8 @@ +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG Symbol Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGSymbolElement + */ +export default class SVGSymbolElement extends SVGGraphicsElement {} diff --git a/packages/happy-dom/src/nodes/svg-t-span-element/SVGTSpanElement.ts b/packages/happy-dom/src/nodes/svg-t-span-element/SVGTSpanElement.ts new file mode 100644 index 000000000..3b290e015 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-t-span-element/SVGTSpanElement.ts @@ -0,0 +1,8 @@ +import SVGTextPositioningElement from '../svg-text-positioning-element/SVGTextPositioningElement.js'; + +/** + * SVG T-Span Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTSpanElement + */ +export default class SVGTSpanElement extends SVGTextPositioningElement {} diff --git a/packages/happy-dom/src/nodes/svg-text-content-element/SVGTextContentElement.ts b/packages/happy-dom/src/nodes/svg-text-content-element/SVGTextContentElement.ts new file mode 100644 index 000000000..bef92d063 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-text-content-element/SVGTextContentElement.ts @@ -0,0 +1,143 @@ +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; +import SVGPoint from '../../svg/SVGPoint.js'; +import SVGRect from '../../svg/SVGRect.js'; + +/** + * SVG Text Content Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTextContentElement + */ +export default class SVGTextContentElement extends SVGGraphicsElement { + // Public static properties + public static readonly LENGTHADJUST_UNKNOWN = 0; + public static readonly LENGTHADJUST_SPACING = 1; + public static readonly LENGTHADJUST_SPACINGANDGLYPHS = 2; + + // Internal properties + public [PropertySymbol.textLength]: SVGAnimatedLength | null = null; + public [PropertySymbol.lengthAdjust]: SVGAnimatedEnumeration | null = null; + + /** + * Returns textLength. + * + * @returns Text length. + */ + public get textLength(): SVGAnimatedLength { + if (!this[PropertySymbol.textLength]) { + this[PropertySymbol.textLength] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('textLength'), + setAttribute: (value) => this.setAttribute('textLength', value) + } + ); + } + return this[PropertySymbol.textLength]; + } + + /** + * Returns lengthAdjust. + * + * @returns Length adjust. + */ + public get lengthAdjust(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.lengthAdjust]) { + this[PropertySymbol.lengthAdjust] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('lengthAdjust'), + setAttribute: (value) => this.setAttribute('lengthAdjust', value), + values: ['spacing', 'spacingAndGlyphs'], + defaultValue: 'spacing' + } + ); + } + return this[PropertySymbol.lengthAdjust]; + } + + /** + * Returns the number of characters available for rendering. + * + * @returns Number of characters. + */ + public getNumberOfChars(): number { + // TODO: Implement. + return 0; + } + + /** + * Returns a float representing the computed length for the text within the element. + * + * @returns Computed text length. + */ + public getComputedTextLength(): number { + // TODO: Implement. + return 0; + } + + /** + * Returns a float representing the computed length of the formatted text advance distance for a substring of text within the element. Note that this method only accounts for the widths of the glyphs in the substring and any extra spacing inserted by the CSS 'letter-spacing' and 'word-spacing' properties. Visual spacing adjustments made by the 'x' attribute is ignored. + * + * @param _charnum The index of the first character in the substring. + * @param _nchars The number of characters in the substring. + */ + public getSubStringLength(_charnum: number, _nchars: number): number { + // TODO: Implement. + return 0; + } + + /** + * Returns a SVGPoint representing the position of a typographic character after text layout has been performed. + * + * @param _charnum The index of the character. + */ + public getStartPositionOfChar(_charnum: number): SVGPoint { + // TODO: Implement. + return new SVGPoint(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a SVGPoint representing the trailing position of a typographic character after text layout has been performed. + * + * @param _charnum The index of the character. + */ + public getEndPositionOfChar(_charnum: number): SVGPoint { + // TODO: Implement. + return new SVGPoint(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a SVGRect representing the computed tight bounding box of the glyph cell that corresponds to a given typographic character. + * + * @param _charnum The index of the character. + */ + public getExtentOfChar(_charnum: number): SVGRect { + // TODO: Implement. + return new SVGRect(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + } + + /** + * Returns a float representing the rotation of typographic character. + * + * @param _charnum The index of the character. + */ + public getRotationOfChar(_charnum: number): number { + // TODO: Implement. + return 0; + } + + /** + * Returns a long representing the character which caused a text glyph to be rendered at a given position in the coordinate system. Because the relationship between characters and glyphs is not one-to-one, only the first character of the relevant typographic character is returned + * + * @param _point The point to be tested. + */ + public getCharNumAtPosition(_point: SVGPoint): number { + // TODO: Implement. + return 0; + } +} diff --git a/packages/happy-dom/src/nodes/svg-text-element/SVGTextElement.ts b/packages/happy-dom/src/nodes/svg-text-element/SVGTextElement.ts new file mode 100644 index 000000000..cac48475b --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-text-element/SVGTextElement.ts @@ -0,0 +1,8 @@ +import SVGTextPositioningElement from '../svg-text-positioning-element/SVGTextPositioningElement.js'; + +/** + * SVG Text Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTextElement + */ +export default class SVGTextElement extends SVGTextPositioningElement {} diff --git a/packages/happy-dom/src/nodes/svg-text-path-element/SVGTextPathElement.ts b/packages/happy-dom/src/nodes/svg-text-path-element/SVGTextPathElement.ts new file mode 100644 index 000000000..a6f2d8fc2 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-text-path-element/SVGTextPathElement.ts @@ -0,0 +1,106 @@ +import SVGTextContentElement from '../svg-text-content-element/SVGTextContentElement.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedEnumeration from '../../svg/SVGAnimatedEnumeration.js'; + +/** + * SVG Text Path Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTextPathElement + */ +export default class SVGTextPathElement extends SVGTextContentElement { + // Public static properties + public static readonly TEXTPATH_METHODTYPE_UNKNOWN = 0; + public static readonly TEXTPATH_METHODTYPE_ALIGN = 1; + public static readonly TEXTPATH_METHODTYPE_STRETCH = 2; + public static readonly TEXTPATH_SPACINGTYPE_UNKNOWN = 0; + public static readonly TEXTPATH_SPACINGTYPE_AUTO = 1; + public static readonly TEXTPATH_SPACINGTYPE_EXACT = 2; + + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + public [PropertySymbol.startOffset]: SVGAnimatedLength | null = null; + public [PropertySymbol.method]: SVGAnimatedEnumeration | null = null; + public [PropertySymbol.spacing]: SVGAnimatedEnumeration | null = null; + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns start offset. + * + * @returns Start offset. + */ + public get startOffset(): SVGAnimatedLength { + if (!this[PropertySymbol.startOffset]) { + this[PropertySymbol.startOffset] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('startOffset'), + setAttribute: (value) => this.setAttribute('startOffset', value) + } + ); + } + return this[PropertySymbol.startOffset]; + } + + /** + * Returns method. + * + * @returns ClipPathUnits. + */ + public get method(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.method]) { + this[PropertySymbol.method] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('method'), + setAttribute: (value) => this.setAttribute('method', value), + values: ['align', 'stretch'], + defaultValue: 'align' + } + ); + } + return this[PropertySymbol.method]; + } + + /** + * Returns spacing. + * + * @returns Spacing. + */ + public get spacing(): SVGAnimatedEnumeration { + if (!this[PropertySymbol.spacing]) { + this[PropertySymbol.spacing] = new SVGAnimatedEnumeration( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('spacing'), + setAttribute: (value) => this.setAttribute('spacing', value), + values: ['auto', 'exact'], + defaultValue: 'exact' + } + ); + } + return this[PropertySymbol.spacing]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-text-positioning-element/SVGTextPositioningElement.ts b/packages/happy-dom/src/nodes/svg-text-positioning-element/SVGTextPositioningElement.ts new file mode 100644 index 000000000..08dbbce40 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-text-positioning-element/SVGTextPositioningElement.ts @@ -0,0 +1,113 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGTextContentElement from '../svg-text-content-element/SVGTextContentElement.js'; +import SVGAnimatedLengthList from '../../svg/SVGAnimatedLengthList.js'; +import SVGAnimatedNumberList from '../../svg/SVGAnimatedNumberList.js'; + +/** + * SVG Text Positioning Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTextPositioningElement + */ +export default class SVGTextPositioningElement extends SVGTextContentElement { + // Internal properties + public [PropertySymbol.x]: SVGAnimatedLengthList | null = null; + public [PropertySymbol.y]: SVGAnimatedLengthList | null = null; + public [PropertySymbol.dx]: SVGAnimatedLengthList | null = null; + public [PropertySymbol.dy]: SVGAnimatedLengthList | null = null; + public [PropertySymbol.rotate]: SVGAnimatedNumberList | null = null; + + /** + * Returns x. + * + * @returns X. + */ + public get x(): SVGAnimatedLengthList { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLengthList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): SVGAnimatedLengthList { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLengthList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Returns dx. + * + * @returns DX. + */ + public get dx(): SVGAnimatedLengthList { + if (!this[PropertySymbol.dx]) { + this[PropertySymbol.dx] = new SVGAnimatedLengthList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('dx'), + setAttribute: (value) => this.setAttribute('dx', value) + } + ); + } + return this[PropertySymbol.dx]; + } + + /** + * Returns dy. + * + * @returns DY. + */ + public get dy(): SVGAnimatedLengthList { + if (!this[PropertySymbol.dy]) { + this[PropertySymbol.dy] = new SVGAnimatedLengthList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('dy'), + setAttribute: (value) => this.setAttribute('dy', value) + } + ); + } + return this[PropertySymbol.dy]; + } + + /** + * Returns rotate. + * + * @returns Rotate. + */ + public get rotate(): SVGAnimatedNumberList { + if (!this[PropertySymbol.rotate]) { + this[PropertySymbol.rotate] = new SVGAnimatedNumberList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('rotate'), + setAttribute: (value) => this.setAttribute('rotate', value) + } + ); + } + return this[PropertySymbol.rotate]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-title-element/SVGTitleElement.ts b/packages/happy-dom/src/nodes/svg-title-element/SVGTitleElement.ts new file mode 100644 index 000000000..9a7bc5356 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-title-element/SVGTitleElement.ts @@ -0,0 +1,8 @@ +import SVGElement from '../svg-element/SVGElement.js'; + +/** + * SVG Title Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTitleElement + */ +export default class SVGTitleElement extends SVGElement {} diff --git a/packages/happy-dom/src/nodes/svg-use-element/SVGUseElement.ts b/packages/happy-dom/src/nodes/svg-use-element/SVGUseElement.ts new file mode 100644 index 000000000..2f234e6f7 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-use-element/SVGUseElement.ts @@ -0,0 +1,113 @@ +import * as PropertySymbol from '../../PropertySymbol.js'; +import SVGAnimatedLength from '../../svg/SVGAnimatedLength.js'; +import SVGAnimatedString from '../../svg/SVGAnimatedString.js'; +import SVGGraphicsElement from '../svg-graphics-element/SVGGraphicsElement.js'; + +/** + * SVG Use Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGUseElement + */ +export default class SVGUseElement extends SVGGraphicsElement { + // Internal properties + public [PropertySymbol.href]: SVGAnimatedString | null = null; + public [PropertySymbol.x]: SVGAnimatedLength | null = null; + public [PropertySymbol.y]: SVGAnimatedLength | null = null; + public [PropertySymbol.width]: SVGAnimatedLength | null = null; + public [PropertySymbol.height]: SVGAnimatedLength | null = null; + + /** + * Returns href. + * + * @returns Href. + */ + public get href(): SVGAnimatedString { + if (!this[PropertySymbol.href]) { + this[PropertySymbol.href] = new SVGAnimatedString( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('href'), + setAttribute: (value) => this.setAttribute('href', value) + } + ); + } + return this[PropertySymbol.href]; + } + + /** + * Returns x position. + * + * @returns X position. + */ + public get x(): SVGAnimatedLength { + if (!this[PropertySymbol.x]) { + this[PropertySymbol.x] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('x'), + setAttribute: (value) => this.setAttribute('x', value) + } + ); + } + return this[PropertySymbol.x]; + } + + /** + * Returns y position. + * + * @returns Y position. + */ + public get y(): SVGAnimatedLength { + if (!this[PropertySymbol.y]) { + this[PropertySymbol.y] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('y'), + setAttribute: (value) => this.setAttribute('y', value) + } + ); + } + return this[PropertySymbol.y]; + } + + /** + * Returns width. + * + * @returns Width. + */ + public get width(): SVGAnimatedLength { + if (!this[PropertySymbol.width]) { + this[PropertySymbol.width] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('width'), + setAttribute: (value) => this.setAttribute('width', value) + } + ); + } + return this[PropertySymbol.width]; + } + + /** + * Returns height. + * + * @returns Height. + */ + public get height(): SVGAnimatedLength { + if (!this[PropertySymbol.height]) { + this[PropertySymbol.height] = new SVGAnimatedLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: () => this.getAttribute('height'), + setAttribute: (value) => this.setAttribute('height', value) + } + ); + } + return this[PropertySymbol.height]; + } +} diff --git a/packages/happy-dom/src/nodes/svg-view-element/SVGViewElement.ts b/packages/happy-dom/src/nodes/svg-view-element/SVGViewElement.ts new file mode 100644 index 000000000..91d0ff3a0 --- /dev/null +++ b/packages/happy-dom/src/nodes/svg-view-element/SVGViewElement.ts @@ -0,0 +1,8 @@ +import SVGElement from '../svg-element/SVGElement.js'; + +/** + * SVG View Element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGViewElement + */ +export default class SVGViewElement extends SVGElement {} diff --git a/packages/happy-dom/src/range/Range.ts b/packages/happy-dom/src/range/Range.ts index 88795d6c7..515d82b02 100644 --- a/packages/happy-dom/src/range/Range.ts +++ b/packages/happy-dom/src/range/Range.ts @@ -2,7 +2,7 @@ import Node from '../nodes/node/Node.js'; import * as PropertySymbol from '../PropertySymbol.js'; import Document from '../nodes/document/Document.js'; import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js'; -import DOMRect from '../nodes/element/DOMRect.js'; +import DOMRect from '../dom/DOMRect.js'; import RangeHowEnum from './RangeHowEnum.js'; import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js'; import RangeUtility from './RangeUtility.js'; @@ -11,7 +11,7 @@ import NodeUtility from '../nodes/node/NodeUtility.js'; import XMLParser from '../xml-parser/XMLParser.js'; import Comment from '../nodes/comment/Comment.js'; import Text from '../nodes/text/Text.js'; -import DOMRectList from '../nodes/element/DOMRectList.js'; +import DOMRectList from '../dom/DOMRectList.js'; import IRangeBoundaryPoint from './IRangeBoundaryPoint.js'; import BrowserWindow from '../window/BrowserWindow.js'; @@ -761,7 +761,7 @@ export default class Range { */ public getClientRects(): DOMRectList { // TODO: Not full implementation - return new DOMRectList(); + return new DOMRectList(PropertySymbol.illegalConstructor); } /** diff --git a/packages/happy-dom/src/storage/Storage.ts b/packages/happy-dom/src/storage/Storage.ts index 115d47c0b..32f819845 100644 --- a/packages/happy-dom/src/storage/Storage.ts +++ b/packages/happy-dom/src/storage/Storage.ts @@ -15,14 +15,12 @@ export default class Storage { constructor() { const data = this[PropertySymbol.data]; - ClassMethodBinder.bindMethods(this, [Storage], { - bindSymbols: true, - forwardToPrototype: true - }); + const methodBinder = new ClassMethodBinder(this, [Storage]); return new Proxy(this, { get: (target, property) => { if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); return target[property]; } if (property in data) { @@ -30,6 +28,8 @@ export default class Storage { } }, set(target, property, newValue): boolean { + methodBinder.bind(property); + if (property in target || typeof property === 'symbol') { return true; } @@ -55,6 +55,8 @@ export default class Storage { return false; }, defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + if (property in target) { Object.defineProperty(target, property, descriptor); return true; diff --git a/packages/happy-dom/src/svg/SVGAngle.ts b/packages/happy-dom/src/svg/SVGAngle.ts new file mode 100644 index 000000000..143884153 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAngle.ts @@ -0,0 +1,310 @@ +import SVGAngleTypeEnum from './SVGAngleTypeEnum.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +const ATTRIBUTE_REGEXP = /^(\d+|\d+\.\d+)(deg|rad|grad|turn|)$/; + +/** + * SVG angle. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAngle + */ +export default class SVGAngle { + // Static properties + public static SVG_ANGLETYPE_UNKNOWN = SVGAngleTypeEnum.unknown; + public static SVG_ANGLETYPE_UNSPECIFIED = SVGAngleTypeEnum.unspecified; + public static SVG_ANGLETYPE_DEG = SVGAngleTypeEnum.deg; + public static SVG_ANGLETYPE_RAD = SVGAngleTypeEnum.rad; + public static SVG_ANGLETYPE_GRAD = SVGAngleTypeEnum.grad; + + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.attributeValue]: string = ''; + public [PropertySymbol.readOnly]: boolean = false; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns unit type. + * + * @returns Unit type. + */ + public get unitType(): SVGAngleTypeEnum { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + const match = attributeValue?.match(ATTRIBUTE_REGEXP); + + if (!match) { + return SVGAngleTypeEnum.unknown; + } + + if (isNaN(parseFloat(match[1]))) { + return SVGAngleTypeEnum.unknown; + } + + switch (match[2]) { + case '': + return SVGAngleTypeEnum.unspecified; + case 'deg': + return SVGAngleTypeEnum.deg; + case 'rad': + return SVGAngleTypeEnum.rad; + case 'grad': + return SVGAngleTypeEnum.grad; + case 'turn': + return SVGAngleTypeEnum.unknown; + default: + return SVGAngleTypeEnum.unspecified; + } + } + + /** + * Returns value. + * + * @returns Value. + */ + public get value(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + const match = attributeValue?.match(ATTRIBUTE_REGEXP); + + if (!match) { + return 0; + } + + const parsedValue = parseFloat(match[1]); + + if (isNaN(parsedValue)) { + return 0; + } + + switch (match[2]) { + case '': + return parsedValue; + case 'deg': + return parsedValue; + case 'rad': + return parsedValue * (180 / Math.PI); + case 'grad': + return parsedValue * (180 / 200); + case 'turn': + return parsedValue * 360; + default: + return parsedValue; + } + } + + /** + * Sets value. + * + * @param value Value in pixels. + */ + public set value(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'value' property on 'SVGAngle': The object is read-only.` + ); + } + + // Value in pixels + value = typeof value !== 'number' ? parseFloat(String(value)) : value; + if (isNaN(value)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'value' property on 'SVGAngle': The provided float value is non-finite.` + ); + } + + let unitType = ''; + let valueInSpecifiedUnits = value; + + switch (this.unitType) { + case SVGAngleTypeEnum.unspecified: + valueInSpecifiedUnits = value; + unitType = ''; + break; + case SVGAngleTypeEnum.deg: + valueInSpecifiedUnits = value; + unitType = 'deg'; + break; + case SVGAngleTypeEnum.rad: + valueInSpecifiedUnits = value / (180 / Math.PI); + unitType = 'rad'; + break; + case SVGAngleTypeEnum.grad: + valueInSpecifiedUnits = value / (180 / 200); + unitType = 'grad'; + break; + case SVGAngleTypeEnum.unknown: + valueInSpecifiedUnits = value / 360; + unitType = 'turn'; + default: + break; + } + + this[PropertySymbol.attributeValue] = String(valueInSpecifiedUnits) + unitType; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Returns value as string. + * + * @returns Value as string. + */ + public get valueAsString(): string { + return this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() || '0' + : this[PropertySymbol.attributeValue] || '0'; + } + + /** + * Returns value in specified units. + * + * @returns Value in specified units. + */ + public get valueInSpecifiedUnits(): number { + const attributeValue = this.valueAsString; + return parseFloat(attributeValue) || 0; + } + + /** + * New value specific units. + * @param unitType + * @param value + */ + public newValueSpecifiedUnits(unitType: number, value: number): void { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'newValueSpecifiedUnits' on 'SVGAngle': The object is read-only.` + ); + } + + if (typeof unitType !== 'number') { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'newValueSpecifiedUnits' on 'SVGAngle': parameter 1 ('unitType') is not of type 'number'.` + ); + } + + value = typeof value !== 'number' ? parseFloat(value) : value; + + if (isNaN(value)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'newValueSpecifiedUnits' on 'SVGAngle': The provided float value is non-finite.` + ); + } + + let unit = ''; + + switch (unitType) { + case SVGAngleTypeEnum.unspecified: + unit = ''; + break; + case SVGAngleTypeEnum.deg: + unit = 'deg'; + break; + case SVGAngleTypeEnum.rad: + unit = 'rad'; + break; + case SVGAngleTypeEnum.grad: + unit = 'grad'; + break; + case SVGAngleTypeEnum.unknown: + unit = 'turn'; + break; + default: + break; + } + + this[PropertySymbol.attributeValue] = String(value) + unit; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Convert to specific units. + * @param unitType + */ + public convertToSpecifiedUnits(unitType: SVGAngleTypeEnum): void { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'convertToSpecifiedUnits' on 'SVGAngle': The object is read-only.` + ); + } + + if (typeof unitType !== 'number') { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'convertToSpecifiedUnits' on 'SVGAngle': parameter 1 ('unitType') is not of type 'number'.` + ); + } + + let value = this.value; + let unit = ''; + + switch (unitType) { + case SVGAngleTypeEnum.unspecified: + unit = ''; + break; + case SVGAngleTypeEnum.deg: + unit = 'deg'; + break; + case SVGAngleTypeEnum.rad: + unit = 'rad'; + value = value / (180 / Math.PI); + break; + case SVGAngleTypeEnum.grad: + unit = 'grad'; + value = value / (180 / 200); + break; + case SVGAngleTypeEnum.unknown: + unit = 'turn'; + value = value / 360; + break; + default: + break; + } + + this[PropertySymbol.attributeValue] = String(value) + unit; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGAngleTypeEnum.ts b/packages/happy-dom/src/svg/SVGAngleTypeEnum.ts new file mode 100644 index 000000000..4275fbc00 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAngleTypeEnum.ts @@ -0,0 +1,9 @@ +enum SVGAngleTypeEnum { + unknown = 0, + unspecified = 1, + deg = 2, + rad = 3, + grad = 4 +} + +export default SVGAngleTypeEnum; diff --git a/packages/happy-dom/src/svg/SVGAnimatedAngle.ts b/packages/happy-dom/src/svg/SVGAnimatedAngle.ts new file mode 100644 index 000000000..aed9e0512 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedAngle.ts @@ -0,0 +1,100 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import SVGAngle from './SVGAngle.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated Angle. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedAngle + */ +export default class SVGAnimatedAngle { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.baseVal]: SVGAngle | null = null; + public [PropertySymbol.animVal]: SVGAngle | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): SVGAngle { + if (!this[PropertySymbol.animVal]) { + this[PropertySymbol.animVal] = new SVGAngle( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: this[PropertySymbol.getAttribute] + } + ); + } + return this[PropertySymbol.animVal]; + } + + /** + * Sets animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): SVGAngle { + if (!this[PropertySymbol.baseVal]) { + this[PropertySymbol.baseVal] = new SVGAngle( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.baseVal]; + } + + /** + * Sets base value. + * + * @param value Base value. + */ + public set baseVal(_value) { + // Do nothing + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedBoolean.ts b/packages/happy-dom/src/svg/SVGAnimatedBoolean.ts new file mode 100644 index 000000000..ffa046d9e --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedBoolean.ts @@ -0,0 +1,80 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated Boolean. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedBoolean + */ +export default class SVGAnimatedBoolean { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): boolean { + return this.baseVal; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): boolean { + const attributeValue = this[PropertySymbol.getAttribute](); + return attributeValue === 'true'; + } + + /** + * Sets base value. + * + * @param value Base value. + */ + public set baseVal(value: boolean) { + this[PropertySymbol.setAttribute]( + typeof value !== 'boolean' ? String(Boolean(value)) : String(value) + ); + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedEnumeration.ts b/packages/happy-dom/src/svg/SVGAnimatedEnumeration.ts new file mode 100644 index 000000000..188abc4fc --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedEnumeration.ts @@ -0,0 +1,119 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated Enumaration. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedEnumeration + */ +export default class SVGAnimatedEnumeration { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.values]: Array; + public [PropertySymbol.defaultValue]: string; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + * @param options.values Values. + * @param options.defaultValue Default value. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + values: Array; + defaultValue: string; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + this[PropertySymbol.values] = options.values; + this[PropertySymbol.defaultValue] = options.defaultValue; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): number { + return this.baseVal; + } + + /** + * Returns animated value. + * + * @param _value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): number { + const value = this[PropertySymbol.getAttribute](); + if (!value) { + return this[PropertySymbol.values].indexOf(this[PropertySymbol.defaultValue]) + 1; + } + const index = this[PropertySymbol.values].indexOf(value); + if (index === -1) { + const anyValueIndex = this[PropertySymbol.values].indexOf(null); + return anyValueIndex !== -1 ? anyValueIndex + 1 : 0; + } + return index + 1; + } + + /** + * Sets base value. + * + * @param value Base value. + */ + public set baseVal(value: number) { + let parsedValue = Number(value); + if (isNaN(parsedValue)) { + parsedValue = 0; + } + if (parsedValue < 1) { + throw new TypeError( + `Failed to set the 'baseVal' property on 'SVGAnimatedEnumeration': The enumeration value provided is ${parsedValue}, which is not settable.` + ); + } + if (parsedValue > this[PropertySymbol.values].length) { + throw new TypeError( + `Failed to set the 'baseVal' property on 'SVGAnimatedEnumeration': The enumeration value provided (${parsedValue}) is larger than the largest allowed value (${ + this[PropertySymbol.values].length + }).` + ); + } + const currentValue = this[PropertySymbol.getAttribute](); + const isAnyValue = this[PropertySymbol.values][parsedValue - 1] === null; + const newValue = isAnyValue ? '0' : this[PropertySymbol.values][parsedValue - 1]; + if ( + !currentValue || + (isAnyValue && this[PropertySymbol.values].includes(currentValue)) || + (!isAnyValue && currentValue !== newValue) + ) { + this[PropertySymbol.setAttribute](newValue); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedInteger.ts b/packages/happy-dom/src/svg/SVGAnimatedInteger.ts new file mode 100644 index 000000000..f73c1408b --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedInteger.ts @@ -0,0 +1,97 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated Integer. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedInteger + */ +export default class SVGAnimatedInteger { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): number { + return this.baseVal; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): number { + const attributeValue = this[PropertySymbol.getAttribute](); + + if (!attributeValue) { + return 0; + } + + const value = parseInt(attributeValue); + + if (isNaN(value)) { + return 0; + } + + return value; + } + + /** + * Sets base value. + * + * @param value Base value. + */ + public set baseVal(value: number) { + const parsedValue = parseInt(String(value)); + + if (isNaN(parsedValue)) { + throw new this[PropertySymbol.window].TypeError( + `TypeError: Failed to set the 'baseVal' property on 'SVGAnimatedInteger': The provided float value is non-finite.` + ); + } + + this[PropertySymbol.setAttribute](String(parsedValue)); + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedLength.ts b/packages/happy-dom/src/svg/SVGAnimatedLength.ts new file mode 100644 index 000000000..1142c4fdf --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedLength.ts @@ -0,0 +1,100 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import SVGLength from './SVGLength.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated Length. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedLength + */ +export default class SVGAnimatedLength { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.baseVal]: SVGLength | null = null; + public [PropertySymbol.animVal]: SVGLength | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): SVGLength { + if (!this[PropertySymbol.animVal]) { + this[PropertySymbol.animVal] = new SVGLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: this[PropertySymbol.getAttribute] + } + ); + } + return this[PropertySymbol.animVal]; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): SVGLength { + if (!this[PropertySymbol.baseVal]) { + this[PropertySymbol.baseVal] = new SVGLength( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.baseVal]; + } + + /** + * Returns base value. + * + * @param value Base value. + */ + public set baseVal(_value) { + // Do nothing + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedLengthList.ts b/packages/happy-dom/src/svg/SVGAnimatedLengthList.ts new file mode 100644 index 000000000..512ecdf55 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedLengthList.ts @@ -0,0 +1,101 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import SVGLengthList from './SVGLengthList.js'; + +/** + * SVG Animated Length. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedLengthList + */ +export default class SVGAnimatedLengthList { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.baseVal]: SVGLengthList | null = null; + public [PropertySymbol.animVal]: SVGLengthList | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): SVGLengthList { + if (!this[PropertySymbol.animVal]) { + this[PropertySymbol.animVal] = new SVGLengthList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.animVal]; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): SVGLengthList { + if (!this[PropertySymbol.baseVal]) { + this[PropertySymbol.baseVal] = new SVGLengthList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.baseVal]; + } + + /** + * Returns base value. + * + * @param value Base value. + */ + public set baseVal(_value) { + // Do nothing + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedNumber.ts b/packages/happy-dom/src/svg/SVGAnimatedNumber.ts new file mode 100644 index 000000000..1cd86182f --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedNumber.ts @@ -0,0 +1,101 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated Number. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedNumber + */ +export default class SVGAnimatedNumber { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.defaultValue]: number = 0; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + * @param options.defaultValue + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + defaultValue?: number; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + this[PropertySymbol.defaultValue] = options.defaultValue || 0; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): number { + return this.baseVal; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): number { + const attributeValue = this[PropertySymbol.getAttribute](); + + if (!attributeValue) { + return this[PropertySymbol.defaultValue]; + } + + const value = parseFloat(attributeValue); + + if (isNaN(value)) { + return this[PropertySymbol.defaultValue]; + } + + return value; + } + + /** + * Sets base value. + * + * @param value Base value. + */ + public set baseVal(value: number) { + const parsedValue = typeof value !== 'number' ? parseFloat((value)) : value; + + if (isNaN(parsedValue)) { + throw new this[PropertySymbol.window].TypeError( + `TypeError: Failed to set the 'baseVal' property on 'SVGAnimatedNumber': The provided float value is non-finite.` + ); + } + + this[PropertySymbol.setAttribute](String(parsedValue)); + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedNumberList.ts b/packages/happy-dom/src/svg/SVGAnimatedNumberList.ts new file mode 100644 index 000000000..75d8e8217 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedNumberList.ts @@ -0,0 +1,101 @@ +import SVGNumberList from './SVGNumberList.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * The SVGAnimatedNumberList interface is used for attributes which take a list of numbers and which can be animated. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedNumberList + */ +export default class SVGAnimatedNumberList { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.baseVal]: SVGNumberList | null = null; + public [PropertySymbol.animVal]: SVGNumberList | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): SVGNumberList { + if (!this[PropertySymbol.animVal]) { + this[PropertySymbol.animVal] = new SVGNumberList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: () => {} + } + ); + } + return this[PropertySymbol.animVal]; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): SVGNumberList { + if (!this[PropertySymbol.baseVal]) { + this[PropertySymbol.baseVal] = new SVGNumberList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.baseVal]; + } + + /** + * Sets base value. + * + * @param value Base value. + */ + public set baseVal(_value) { + // Do nothing + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedPreserveAspectRatio.ts b/packages/happy-dom/src/svg/SVGAnimatedPreserveAspectRatio.ts new file mode 100644 index 000000000..91c8d0397 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedPreserveAspectRatio.ts @@ -0,0 +1,100 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import SVGPreserveAspectRatio from './SVGPreserveAspectRatio.js'; + +/** + * SVG Animated Preserve Aspect Ratio + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedPreserveAspectRatio + */ +export default class SVGAnimatedPreserveAspectRatio { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.baseVal]: SVGPreserveAspectRatio | null = null; + public [PropertySymbol.animVal]: SVGPreserveAspectRatio | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): SVGPreserveAspectRatio { + if (!this[PropertySymbol.animVal]) { + this[PropertySymbol.animVal] = new SVGPreserveAspectRatio( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: this[PropertySymbol.getAttribute] + } + ); + } + return this[PropertySymbol.animVal]; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): SVGPreserveAspectRatio { + if (!this[PropertySymbol.baseVal]) { + this[PropertySymbol.baseVal] = new SVGPreserveAspectRatio( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.baseVal]; + } + + /** + * Returns base value. + * + * @param value Base value. + */ + public set baseVal(_value) { + // Do nothing + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedRect.ts b/packages/happy-dom/src/svg/SVGAnimatedRect.ts new file mode 100644 index 000000000..996207654 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedRect.ts @@ -0,0 +1,100 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import SVGRect from './SVGRect.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated Number. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedRect + */ +export default class SVGAnimatedRect { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.baseVal]: SVGRect | null = null; + public [PropertySymbol.animVal]: SVGRect | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): SVGRect { + if (!this[PropertySymbol.animVal]) { + this[PropertySymbol.animVal] = new SVGRect( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: this[PropertySymbol.getAttribute] + } + ); + } + return this[PropertySymbol.animVal]; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): SVGRect { + if (!this[PropertySymbol.baseVal]) { + this[PropertySymbol.baseVal] = new SVGRect( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.baseVal]; + } + + /** + * Returns base value. + * + * @param value Base value. + */ + public set baseVal(_value) { + // Do nothing + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedString.ts b/packages/happy-dom/src/svg/SVGAnimatedString.ts new file mode 100644 index 000000000..600505c4b --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedString.ts @@ -0,0 +1,83 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG Animated String. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedString + */ +export default class SVGAnimatedString { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): string { + return this.baseVal; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): string { + const attributeValue = this[PropertySymbol.getAttribute](); + + if (!attributeValue) { + return ''; + } + + return attributeValue; + } + + /** + * Sets base value. + * + * @param value Base value. + */ + public set baseVal(value: string) { + this[PropertySymbol.setAttribute](String(value)); + } +} diff --git a/packages/happy-dom/src/svg/SVGAnimatedTransformList.ts b/packages/happy-dom/src/svg/SVGAnimatedTransformList.ts new file mode 100644 index 000000000..e9b2471fa --- /dev/null +++ b/packages/happy-dom/src/svg/SVGAnimatedTransformList.ts @@ -0,0 +1,101 @@ +import SVGTransformList from './SVGTransformList.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * The SVGAnimatedTransformList interface is used for attributes which take a list of numbers and which can be animated. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAnimatedTransformList + */ +export default class SVGAnimatedTransformList { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.baseVal]: SVGTransformList | null = null; + public [PropertySymbol.animVal]: SVGTransformList | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + } + + /** + * Returns animated value. + * + * @returns Animated value. + */ + public get animVal(): SVGTransformList { + if (!this[PropertySymbol.animVal]) { + this[PropertySymbol.animVal] = new SVGTransformList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: true, + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: () => {} + } + ); + } + return this[PropertySymbol.animVal]; + } + + /** + * Returns animated value. + * + * @param value Animated value. + */ + public set animVal(_value) { + // Do nothing + } + + /** + * Returns base value. + * + * @returns Base value. + */ + public get baseVal(): SVGTransformList { + if (!this[PropertySymbol.baseVal]) { + this[PropertySymbol.baseVal] = new SVGTransformList( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + getAttribute: this[PropertySymbol.getAttribute], + setAttribute: this[PropertySymbol.setAttribute] + } + ); + } + return this[PropertySymbol.baseVal]; + } + + /** + * Returns base value. + * + * @param value Base value. + */ + public set baseVal(_value) { + // Do nothing + } +} diff --git a/packages/happy-dom/src/svg/SVGLength.ts b/packages/happy-dom/src/svg/SVGLength.ts new file mode 100644 index 000000000..aa7a97f64 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGLength.ts @@ -0,0 +1,375 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import SVGLengthTypeEnum from './SVGLengthTypeEnum.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +const ATTRIBUTE_REGEXP = /^(\d+|\d+\.\d+)(px|em|ex|cm|mm|in|pt|pc|%|)$/; + +/** + * SVG length. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGLength + */ +export default class SVGLength { + // Static properties + public static SVG_LENGTHTYPE_UNKNOWN = SVGLengthTypeEnum.unknown; + public static SVG_LENGTHTYPE_NUMBER = SVGLengthTypeEnum.number; + public static SVG_LENGTHTYPE_PERCENTAGE = SVGLengthTypeEnum.percentage; + public static SVG_LENGTHTYPE_EMS = SVGLengthTypeEnum.ems; + public static SVG_LENGTHTYPE_EXS = SVGLengthTypeEnum.exs; + public static SVG_LENGTHTYPE_PX = SVGLengthTypeEnum.px; + public static SVG_LENGTHTYPE_CM = SVGLengthTypeEnum.cm; + public static SVG_LENGTHTYPE_MM = SVGLengthTypeEnum.mm; + public static SVG_LENGTHTYPE_IN = SVGLengthTypeEnum.in; + public static SVG_LENGTHTYPE_PT = SVGLengthTypeEnum.pt; + public static SVG_LENGTHTYPE_PC = SVGLengthTypeEnum.pc; + + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.attributeValue]: string | null = null; + public [PropertySymbol.readOnly]: boolean = false; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns unit type. + * + * @returns Unit type. + */ + public get unitType(): SVGLengthTypeEnum { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() || '' + : this[PropertySymbol.attributeValue] || ''; + const match = attributeValue.match(ATTRIBUTE_REGEXP); + + if (!match) { + return SVGLengthTypeEnum.unknown; + } + + if (isNaN(parseFloat(match[1]))) { + return SVGLengthTypeEnum.unknown; + } + + switch (match[2]) { + case '': + return SVGLengthTypeEnum.number; + case 'px': + return SVGLengthTypeEnum.px; + case 'cm': + return SVGLengthTypeEnum.cm; + case 'mm': + return SVGLengthTypeEnum.mm; + case 'in': + return SVGLengthTypeEnum.in; + case 'pt': + return SVGLengthTypeEnum.pt; + case 'pc': + return SVGLengthTypeEnum.pc; + case 'em': + case 'ex': + case '%': + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'value' on 'SVGLength': Could not resolve relative length.` + ); + default: + return SVGLengthTypeEnum.unknown; + } + } + + /** + * Returns value. + * + * @returns Value. + */ + public get value(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() || '' + : this[PropertySymbol.attributeValue] || ''; + const match = attributeValue.match(ATTRIBUTE_REGEXP); + + if (!match) { + return 0; + } + + const parsedValue = parseFloat(match[1]); + + if (isNaN(parsedValue)) { + return 0; + } + + switch (match[2]) { + case '': + return parsedValue; + case 'px': + return parsedValue; + case 'cm': + return (parsedValue / 2.54) * 96; + case 'mm': + return (parsedValue / 25.4) * 96; + case 'in': + return parsedValue * 96; + case 'pt': + return parsedValue * 72; + case 'pc': + return parsedValue * 6; + case 'em': + case 'ex': + case '%': + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'value' on 'SVGLength': Could not resolve relative length.` + ); + default: + return 0; + } + } + + /** + * Sets value. + * + * @param value Value in pixels. + */ + public set value(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'value' property on 'SVGLength': The object is read-only.` + ); + } + + // Value in pixels + value = typeof value !== 'number' ? parseFloat(String(value)) : value; + if (isNaN(value)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'value' property on 'SVGLength': The provided float value is non-finite.` + ); + } + let unitType = ''; + let valueInSpecifiedUnits = value; + switch (this.unitType) { + case SVGLengthTypeEnum.number: + valueInSpecifiedUnits = value; + unitType = ''; + break; + case SVGLengthTypeEnum.px: + valueInSpecifiedUnits = value; + unitType = 'px'; + break; + case SVGLengthTypeEnum.cm: + valueInSpecifiedUnits = (value / 96) * 2.54; + unitType = 'cm'; + break; + case SVGLengthTypeEnum.mm: + valueInSpecifiedUnits = (value / 96) * 25.4; + unitType = 'mm'; + break; + case SVGLengthTypeEnum.in: + valueInSpecifiedUnits = value / 96; + unitType = 'in'; + break; + case SVGLengthTypeEnum.pt: + valueInSpecifiedUnits = value / 72; + unitType = 'pt'; + break; + case SVGLengthTypeEnum.pc: + valueInSpecifiedUnits = value / 6; + unitType = 'pc'; + break; + case SVGLengthTypeEnum.percentage: + case SVGLengthTypeEnum.ems: + case SVGLengthTypeEnum.exs: + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'value' property on 'SVGLength': Could not resolve relative length.` + ); + default: + break; + } + + this[PropertySymbol.attributeValue] = String(valueInSpecifiedUnits) + unitType; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Returns value as string. + * + * @returns Value as string. + */ + public get valueAsString(): string { + return this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() || '0' + : this[PropertySymbol.attributeValue] || '0'; + } + + /** + * Returns value in specified units. + * + * @returns Value in specified units. + */ + public get valueInSpecifiedUnits(): number { + const attributeValue = this.valueAsString; + return parseFloat(attributeValue) || 0; + } + + /** + * New value specific units. + * + * @param unitType + * @param value + */ + public newValueSpecifiedUnits(unitType: number, value: number): void { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'newValueSpecifiedUnits' on 'SVGLength': The object is read-only.` + ); + } + + if (typeof unitType !== 'number') { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'newValueSpecifiedUnits' on 'SVGLength': parameter 1 ('unitType') is not of type 'number'.` + ); + } + + value = typeof value !== 'number' ? parseFloat(String(value)) : value; + + if (isNaN(value)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'newValueSpecifiedUnits' on 'SVGLength': The provided float value is non-finite.` + ); + } + + let unit = ''; + switch (unitType) { + case SVGLengthTypeEnum.number: + unit = ''; + break; + case SVGLengthTypeEnum.px: + unit = 'px'; + break; + case SVGLengthTypeEnum.cm: + unit = 'cm'; + break; + case SVGLengthTypeEnum.mm: + unit = 'mm'; + break; + case SVGLengthTypeEnum.in: + unit = 'in'; + break; + case SVGLengthTypeEnum.pt: + unit = 'pt'; + break; + case SVGLengthTypeEnum.pc: + unit = 'pc'; + break; + case SVGLengthTypeEnum.ems: + case SVGLengthTypeEnum.exs: + case SVGLengthTypeEnum.percentage: + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'newValueSpecifiedUnits' on 'SVGLength': Could not resolve relative length.` + ); + default: + break; + } + + this[PropertySymbol.attributeValue] = String(value) + unit; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Convert to specific units. + * @param unitType + */ + public convertToSpecifiedUnits(unitType: SVGLengthTypeEnum): void { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'convertToSpecifiedUnits' on 'SVGLength': The object is read-only.` + ); + } + + if (typeof unitType !== 'number') { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'convertToSpecifiedUnits' on 'SVGLength': parameter 1 ('unitType') is not of type 'number'.` + ); + } + + let value = this.value; + let unit = ''; + + switch (unitType) { + case SVGLengthTypeEnum.number: + unit = ''; + break; + case SVGLengthTypeEnum.px: + unit = 'px'; + break; + case SVGLengthTypeEnum.cm: + value = (value / 96) * 2.54; + unit = 'cm'; + break; + case SVGLengthTypeEnum.mm: + value = (value / 96) * 25.4; + unit = 'mm'; + break; + case SVGLengthTypeEnum.in: + value = value / 96; + unit = 'in'; + break; + case SVGLengthTypeEnum.pt: + value = value / 72; + unit = 'pt'; + break; + case SVGLengthTypeEnum.pc: + value = value / 6; + unit = 'pc'; + break; + case SVGLengthTypeEnum.percentage: + case SVGLengthTypeEnum.ems: + case SVGLengthTypeEnum.exs: + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'convertToSpecifiedUnits' on 'SVGLength': Could not resolve relative length.` + ); + default: + break; + } + + this[PropertySymbol.attributeValue] = String(value) + unit; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGLengthList.ts b/packages/happy-dom/src/svg/SVGLengthList.ts new file mode 100644 index 000000000..ce9bb5b3b --- /dev/null +++ b/packages/happy-dom/src/svg/SVGLengthList.ts @@ -0,0 +1,489 @@ +import ClassMethodBinder from '../ClassMethodBinder.js'; +import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import SVGLength from './SVGLength.js'; + +const ATTRIBUTE_SEPARATOR_REGEXP = /[\t\f\n\r, ]+/; + +/** + * SVG Length List. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGLengthList + */ +export default class SVGLengthList { + [index: number]: SVGLength; + + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.readOnly]: boolean = false; + private [PropertySymbol.cache]: { items: SVGLength[]; attributeValue: string } = { + items: [], + attributeValue: '' + }; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + * @param [options.readOnly] Read only. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + readOnly?: boolean; + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + + const methodBinder = new ClassMethodBinder(this, [SVGLengthList]); + + return new Proxy(this, { + get: (target, property) => { + if (property === 'length' || property === 'numberOfItems') { + return target[PropertySymbol.getItemList]().length; + } + if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); + return target[property]; + } + const index = Number(property); + if (!isNaN(index)) { + return target[PropertySymbol.getItemList]()[index]; + } + }, + set(target, property, newValue): boolean { + methodBinder.bind(property); + if (typeof property === 'symbol') { + target[property] = newValue; + return true; + } + const index = Number(property); + if (isNaN(index)) { + target[property] = newValue; + } + return true; + }, + deleteProperty(target, property): boolean { + if (typeof property === 'symbol') { + delete target[property]; + return true; + } + const index = Number(property); + if (isNaN(index)) { + delete target[property]; + } + return true; + }, + ownKeys(target): string[] { + return Object.keys(target[PropertySymbol.getItemList]()); + }, + has(target, property): boolean { + if (property in target) { + return true; + } + + if (typeof property === 'symbol') { + return false; + } + + const index = Number(property); + return !isNaN(index) && index >= 0 && index < target[PropertySymbol.getItemList]().length; + }, + defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + + if (property in target) { + Object.defineProperty(target, property, descriptor); + return true; + } + + return false; + }, + getOwnPropertyDescriptor(target, property): PropertyDescriptor { + if (property in target || typeof property === 'symbol') { + return; + } + + const index = Number(property); + const items = target[PropertySymbol.getItemList](); + + if (!isNaN(index) && items[index]) { + return { + value: items[index], + writable: false, + enumerable: true, + configurable: true + }; + } + } + }); + } + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns length. + * + * @returns Length. + */ + public get numberOfItems(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns an iterator, allowing you to go through all values of the key/value pairs contained in this object. + */ + public [Symbol.iterator](): IterableIterator { + return this[PropertySymbol.getItemList]().values(); + } + + /** + * Clears all items from the list. + */ + public clear(): void { + this[PropertySymbol.setAttribute](''); + } + + /** + * Replace Token. + * + * @param newItem New item. + * @returns The item being replaced. + */ + public initialize(newItem: SVGLength): SVGLength { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGLengthList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGLengthList': 1 arguments required, but only ${arguments.length} present.` + ); + } + + for (const item of this[PropertySymbol.cache].items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].items = [newItem]; + this[PropertySymbol.cache].attributeValue = newItem[PropertySymbol.attributeValue]; + this[PropertySymbol.setAttribute](newItem[PropertySymbol.attributeValue]); + + return newItem; + } + + /** + * Returns item at index. + * + * @param index Index. + * @returns The item at the index. + **/ + public getItem(index: number | string): SVGLength { + const items = this[PropertySymbol.getItemList](); + if (typeof index === 'number') { + return items[index] ? items[index] : null; + } + index = Number(index); + index = isNaN(index) ? 0 : index; + return items[index] ? items[index] : null; + } + + /** + * Inserts a new item into the list at the specified position. The first item is number 0. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to insert before is before the removal of the item. If the index is equal to 0, then the new item is inserted at the front of the list. If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being inserted. + */ + public insertItemBefore(newItem: SVGLength, index: number): SVGLength { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGLengthList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGLengthList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index > items.length) { + index = items.length; + } + + items.splice(index, 0, newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Replaces an existing item in the list with a new item. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to replace is before the removal of the item. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being replaced. + */ + public replaceItem(newItem: SVGLength, index: number): SVGLength { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGLengthList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGLengthList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex === index) { + return newItem; + } + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index >= items.length) { + index = items.length - 1; + } + + if (items[index]) { + items[index][PropertySymbol.getAttribute] = null; + items[index][PropertySymbol.setAttribute] = null; + } + + const replacedItem = items[index]; + + items[index] = newItem; + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return replacedItem; + } + + /** + * Removes an existing item from the list. + * + * @param index Index. + * @returns The removed item. + */ + public removeItem(index: number): SVGLength { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGLengthList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGLengthList': 1 argument required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + + index = Number(index); + + if (isNaN(index)) { + index = 0; + } + + if (index >= items.length) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGLengthList': The index provided (${index}) is greater than the maximum bound.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + if (index < 0) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGLengthList': The index provided (${index}) is negative.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + const removedItem = items[index]; + + if (removedItem) { + removedItem[PropertySymbol.getAttribute] = null; + removedItem[PropertySymbol.setAttribute] = null; + } + + items.splice(index, 1); + + this[PropertySymbol.setAttribute]( + items.map((item) => item[PropertySymbol.attributeValue] ?? '0').join(' ') + ); + + return removedItem; + } + + /** + * Appends an item to the end of the list. + * + * @param newItem The item to add to the list. + * @returns The item being appended. + */ + public appendItem(newItem: SVGLength): SVGLength { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGLengthList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGLengthList': 1 argument required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + items.push(newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Returns item list from attribute value. + * + * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace + */ + public [PropertySymbol.getItemList](): SVGLength[] { + const attributeValue = this[PropertySymbol.getAttribute]() ?? ''; + + const cache = this[PropertySymbol.cache]; + + if (cache.attributeValue === attributeValue) { + return cache.items; + } + + if (cache.items.length) { + for (const item of cache.items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + } + + // It is possible to make this statement shorter by using Array.from() and Set, but this is faster when comparing using a bench test. + const items: SVGLength[] = []; + const trimmed = attributeValue.trim(); + + if (trimmed) { + const parts = trimmed.split(ATTRIBUTE_SEPARATOR_REGEXP); + for (let i = 0, max = parts.length; i < max; i++) { + const item = new SVGLength(PropertySymbol.illegalConstructor, this[PropertySymbol.window], { + readOnly: this[PropertySymbol.readOnly], + getAttribute: () => item[PropertySymbol.attributeValue], + setAttribute: (value: string) => { + item[PropertySymbol.attributeValue] = value; + const newAttributeValue = items + .map((item) => item[PropertySymbol.attributeValue] ?? '0') + .join(' '); + cache.attributeValue = newAttributeValue; + this[PropertySymbol.setAttribute](newAttributeValue); + } + }); + item[PropertySymbol.attributeValue] = parts[i]; + items.push(item); + } + } + + cache.attributeValue = attributeValue; + cache.items = items; + + return items; + } +} diff --git a/packages/happy-dom/src/svg/SVGLengthTypeEnum.ts b/packages/happy-dom/src/svg/SVGLengthTypeEnum.ts new file mode 100644 index 000000000..a757a0982 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGLengthTypeEnum.ts @@ -0,0 +1,15 @@ +enum SVGLengthTypeEnum { + unknown = 0, + number = 1, + percentage = 2, + ems = 3, + exs = 4, + px = 5, + cm = 6, + mm = 7, + in = 8, + pt = 9, + pc = 10 +} + +export default SVGLengthTypeEnum; diff --git a/packages/happy-dom/src/svg/SVGMatrix.ts b/packages/happy-dom/src/svg/SVGMatrix.ts new file mode 100644 index 000000000..c0056761b --- /dev/null +++ b/packages/happy-dom/src/svg/SVGMatrix.ts @@ -0,0 +1,428 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import DOMMatrix from '../dom/dom-matrix/DOMMatrix.js'; + +const TRANSFORM_REGEXP = /([a-zA-Z0-9]+)\(([^)]+)\)/; +const TRANSFORM_PARAMETER_SPLIT_REGEXP = /[\s,]+/; + +/** + * SVG Matrix. + * + * Documentation missing at developer.mozilla.org. + */ +export default class SVGMatrix { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.attributeValue]: string | null = null; + public [PropertySymbol.readOnly]: boolean = false; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns the `a` value of the matrix. + */ + public get a(): number { + return this[PropertySymbol.getDOMMatrix]().a; + } + + /** + * Sets the `a` value of the matrix. + */ + public set a(value: number) { + if (this[PropertySymbol.readOnly]) { + return; + } + const domMatrix = this[PropertySymbol.getDOMMatrix](); + domMatrix.a = value; + this[PropertySymbol.setDOMMatrix](domMatrix); + } + + /** + * Returns the `b` value of the matrix. + */ + public get b(): number { + return this[PropertySymbol.getDOMMatrix]().b; + } + + /** + * Sets the `b` value of the matrix. + */ + public set b(value: number) { + if (this[PropertySymbol.readOnly]) { + return; + } + const domMatrix = this[PropertySymbol.getDOMMatrix](); + domMatrix.b = value; + this[PropertySymbol.setDOMMatrix](domMatrix); + } + + /** + * Returns the `c` value of the matrix. + */ + public get c(): number { + return this[PropertySymbol.getDOMMatrix]().c; + } + + /** + * Sets the `c` value of the matrix. + */ + public set c(value: number) { + if (this[PropertySymbol.readOnly]) { + return; + } + const domMatrix = this[PropertySymbol.getDOMMatrix](); + domMatrix.c = value; + this[PropertySymbol.setDOMMatrix](domMatrix); + } + + /** + * Returns the `d` value of the matrix. + */ + public get d(): number { + return this[PropertySymbol.getDOMMatrix]().d; + } + + /** + * Sets the `d` value of the matrix. + */ + public set d(value: number) { + if (this[PropertySymbol.readOnly]) { + return; + } + const domMatrix = this[PropertySymbol.getDOMMatrix](); + domMatrix.d = value; + this[PropertySymbol.setDOMMatrix](domMatrix); + } + + /** + * Returns the `e` value of the matrix. + */ + public get e(): number { + return this[PropertySymbol.getDOMMatrix]().e; + } + + /** + * Sets the `e` value of the matrix. + */ + public set e(value: number) { + if (this[PropertySymbol.readOnly]) { + return; + } + const domMatrix = this[PropertySymbol.getDOMMatrix](); + domMatrix.e = value; + this[PropertySymbol.setDOMMatrix](domMatrix); + } + + /** + * Returns the `f` value of the matrix. + */ + public get f(): number { + return this[PropertySymbol.getDOMMatrix]().f; + } + + /** + * Sets the `f` value of the matrix. + */ + public set f(value: number) { + if (this[PropertySymbol.readOnly]) { + return; + } + const domMatrix = this[PropertySymbol.getDOMMatrix](); + domMatrix.f = value; + this[PropertySymbol.setDOMMatrix](domMatrix); + } + + /** + * Returns a new SVGMatrix instance which is the result of this matrix multiplied by the passed matrix. + * + * @param secondMatrix Matrix object. + * @returns A new SVGMatrix object. + */ + public multiply(secondMatrix: SVGMatrix): SVGMatrix { + if (!(secondMatrix instanceof SVGMatrix)) { + throw new this[PropertySymbol.window].TypeError( + "Failed to execute 'multiply' on 'SVGMatrix': parameter 1 is not of type 'SVGMatrix'." + ); + } + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.multiplySelf(secondMatrix[PropertySymbol.getDOMMatrix]()); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix post multiplied by a translation matrix containing the passed values. + * + * @param [x=0] X component of the translation value. + * @param [y=0] Y component of the translation value. + * @returns The resulted matrix + */ + public translate(x: number = 0, y: number = 0): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.translateSelf(x, y); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix post multiplied by a scale 2D matrix containing the passed values. + * + * @param scale The scale factor. + * @returns The resulted matrix + */ + public scale(scale: number): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.scaleSelf(scale); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix post multiplied by a scale 3D matrix containing the passed values. + * + * @param [scaleX] X-Axis scale. + * @param [scaleY] Y-Axis scale. + * @returns The resulted matrix + */ + public scaleNonUniform(scaleX = 1, scaleY = 1): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.scaleNonUniformSelf(scaleX, scaleY); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix post multiplied by each of 3 rotation matrices about the major axes, first X, then Y, then Z. + * + * @param angle Angle of rotation in degrees. + * @returns The resulted matrix + */ + public rotate(angle: number): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.rotateSelf(angle); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix post multiplied by a skew matrix along the X axis by the given angle. + * + * Not implemented in Happy DOM yet. + * + * @param [x] X-Axis skew. + * @param [y] Y-Axis skew. + */ + public rotateFromVector(x: number = 0, y: number = 0): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.rotateFromVectorSelf(x, y); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance that specifies a skew transformation along X-Axis by the given angle. + * + * @param angle Angle amount in degrees to skew. + * @returns The resulted matrix + */ + public skewX(angle: number): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.skewXSelf(angle); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance that specifies a skew transformation along Y-Axis by the given angle. + * + * @param angle Angle amount in degrees to skew. + * @returns The resulted matrix + */ + public skewY(angle: number): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.skewYSelf(angle); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix flipped on X-axis. + */ + public flipX(): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.flipXSelf(); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix flipped on Y-axis. + */ + public flipY(): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.flipYSelf(); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns a new SVGMatrix instance which is this matrix inverted. + */ + public inverse(): SVGMatrix { + const domMatrix = this[PropertySymbol.getDOMMatrix](); + const svgMatrix = new SVGMatrix(PropertySymbol.illegalConstructor, this[PropertySymbol.window]); + domMatrix.invertSelf(); + svgMatrix[PropertySymbol.setDOMMatrix](domMatrix); + return svgMatrix; + } + + /** + * Returns DOM matrix. + * + * @returns DOM matrix. + */ + public [PropertySymbol.getDOMMatrix](): DOMMatrix { + const attribute = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + + if (!attribute) { + return new DOMMatrix(); + } + + const match = attribute.match(TRANSFORM_REGEXP); + + if (!match) { + return new DOMMatrix(); + } + const parameters: number[] = []; + + for (const parameter of match[2].trim().split(TRANSFORM_PARAMETER_SPLIT_REGEXP)) { + const value = Number(parameter); + if (isNaN(value)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to parse transform attribute: Expected number, but got "${parameter}" in "${attribute}".` + ); + } + parameters.push(value); + } + + switch (match[1]) { + case 'matrix': + if (parameters.length !== 6) { + throw new this[PropertySymbol.window].TypeError( + `Failed to parse transform attribute: Expected 6 parameters in "${attribute}".` + ); + } + return DOMMatrix[PropertySymbol.fromString](attribute); + case 'scale': + case 'translate': + if (parameters.length !== 1 && parameters.length !== 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to parse transform attribute: Expected 1 or 2 parameters in "${attribute}".` + ); + } + return DOMMatrix[PropertySymbol.fromString](attribute); + case 'skewY': + case 'skewX': + if (parameters.length !== 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to parse transform attribute: Expected 1 parameter in "${attribute}".` + ); + } + return DOMMatrix[PropertySymbol.fromString](attribute); + case 'rotate': + const domMatrix = new DOMMatrix(); + + if (parameters.length !== 1 && parameters.length !== 3) { + throw new this[PropertySymbol.window].TypeError( + `Failed to parse transform attribute: Expected 1 or 3 parameters in "${attribute}".` + ); + } + + const [angle, x, y] = parameters; + + if (x || y) { + domMatrix.translateSelf(x, y); + } + + const radian = (angle * Math.PI) / 180; + + /** + * @see https://www.w3.org/TR/SVG11/coords.html#TransformAttribute + **/ + domMatrix.multiplySelf( + // prettier-ignore + new DOMMatrix([ + Math.cos(radian), Math.sin(radian), -Math.sin(radian), + Math.cos(radian), 0, 0 + ]) + ); + + if (x || y) { + domMatrix.translateSelf(-x, -y); + } + + return domMatrix; + default: + throw new this[PropertySymbol.window].TypeError( + `Failed to parse transform attribute: Unknown transformation "${attribute}".` + ); + } + } + + /** + * Sets DOM matrix. + * + * @param domMatrix DOM matrix. + */ + public [PropertySymbol.setDOMMatrix](domMatrix: DOMMatrix): void { + this[PropertySymbol.attributeValue] = domMatrix.toString().replace(/, /g, ' '); + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGNumber.ts b/packages/happy-dom/src/svg/SVGNumber.ts new file mode 100644 index 000000000..9a8e06f5b --- /dev/null +++ b/packages/happy-dom/src/svg/SVGNumber.ts @@ -0,0 +1,83 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * SVG number. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGNumber + */ +export default class SVGNumber { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.attributeValue]: string | null = null; + public [PropertySymbol.readOnly]: boolean = false; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns value. + * + * @returns Value. + */ + public get value(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + return parseFloat(attributeValue || '0'); + } + + /** + * Sets value. + * + * @param value Value. + */ + public set value(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new TypeError( + `Failed to set the 'value' property on 'SVGNumber': The object is read-only.` + ); + } + const parsedValue = typeof value !== 'number' ? parseFloat(String(value)) : value; + if (isNaN(parsedValue)) { + throw new TypeError( + `Failed to set the 'value' property on 'SVGNumber': The provided value is not a number.` + ); + } + this[PropertySymbol.attributeValue] = String(value); + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue] || ''); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGNumberList.ts b/packages/happy-dom/src/svg/SVGNumberList.ts new file mode 100644 index 000000000..76b252ba8 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGNumberList.ts @@ -0,0 +1,522 @@ +import ClassMethodBinder from '../ClassMethodBinder.js'; +import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import SVGNumber from './SVGNumber.js'; + +const ATTRIBUTE_SEPARATOR_REGEXP = /[\t\f\n\r, ]+/; + +/** + * SVGNumberList. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGNumberList + */ +export default class SVGNumberList { + [index: number]: SVGNumber; + + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.readOnly]: boolean = false; + private [PropertySymbol.cache]: { items: SVGNumber[]; attributeValue: string } = { + items: [], + attributeValue: '' + }; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param [options.readOnly] Read only. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + readOnly?: boolean; + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + const methodBinder = new ClassMethodBinder(this, [SVGNumberList]); + + return new Proxy(this, { + get: (target, property) => { + if (property === 'length' || property === 'numberOfItems') { + return target[PropertySymbol.getItemList]().length; + } + if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); + return target[property]; + } + const index = Number(property); + if (!isNaN(index)) { + return target[PropertySymbol.getItemList]()[index]; + } + }, + set(target, property, newValue): boolean { + methodBinder.bind(property); + if (typeof property === 'symbol') { + target[property] = newValue; + return true; + } + const index = Number(property); + if (isNaN(index)) { + target[property] = newValue; + } + return true; + }, + deleteProperty(target, property): boolean { + if (typeof property === 'symbol') { + delete target[property]; + return true; + } + const index = Number(property); + if (isNaN(index)) { + delete target[property]; + } + return true; + }, + ownKeys(target): string[] { + return Object.keys(target[PropertySymbol.getItemList]()); + }, + has(target, property): boolean { + if (property in target) { + return true; + } + + if (typeof property === 'symbol') { + return false; + } + + const index = Number(property); + return !isNaN(index) && index >= 0 && index < target[PropertySymbol.getItemList]().length; + }, + defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + + if (property in target) { + Object.defineProperty(target, property, descriptor); + return true; + } + + return false; + }, + getOwnPropertyDescriptor(target, property): PropertyDescriptor { + if (property in target || typeof property === 'symbol') { + return; + } + + const index = Number(property); + const items = target[PropertySymbol.getItemList](); + + if (!isNaN(index) && items[index]) { + return { + value: items[index], + writable: false, + enumerable: true, + configurable: true + }; + } + } + }); + } + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns length. + * + * @returns Length. + */ + public get numberOfItems(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns an iterator, allowing you to go through all values of the key/value pairs contained in this object. + */ + public [Symbol.iterator](): IterableIterator { + return this[PropertySymbol.getItemList]().values(); + } + + /** + * Clears all items from the list. + */ + public clear(): void { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'clear' on 'SVGNumberList': The object is read-only.` + ); + } + + for (const item of this[PropertySymbol.cache].items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + + this[PropertySymbol.cache].items = []; + this[PropertySymbol.cache].attributeValue = ''; + this[PropertySymbol.setAttribute](''); + } + + /** + * Replace Token. + * + * @param newItem New item. + * @returns The item being replaced. + */ + public initialize(newItem: SVGNumber): SVGNumber { + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGNumberList': 1 arguments required, but only ${arguments.length} present.` + ); + } + + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGNumberList': The object is read-only.` + ); + } + + for (const item of this[PropertySymbol.cache].items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].items = [newItem]; + this[PropertySymbol.cache].attributeValue = newItem[PropertySymbol.attributeValue]; + this[PropertySymbol.setAttribute](newItem[PropertySymbol.attributeValue]); + + return newItem; + } + + /** + * Returns item at index. + * + * @param index Index. + * @returns The item at the index. + **/ + public getItem(index: number | string): SVGNumber { + const items = this[PropertySymbol.getItemList](); + if (typeof index === 'number') { + return items[index] ? items[index] : null; + } + index = Number(index); + index = isNaN(index) ? 0 : index; + return items[index] ? items[index] : null; + } + + /** + * Inserts a new item into the list at the specified position. The first item is number 0. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to insert before is before the removal of the item. If the index is equal to 0, then the new item is inserted at the front of the list. If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being inserted. + */ + public insertItemBefore(newItem: SVGNumber, index: number): SVGNumber { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGNumberList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGNumberList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGNumber)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGNumberList': parameter 1 is not of type 'SVGNumber'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index > items.length) { + index = items.length; + } + + items.splice(index, 0, newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Replaces an existing item in the list with a new item. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to replace is before the removal of the item. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being replaced. + */ + public replaceItem(newItem: SVGNumber, index: number): SVGNumber { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGNumberList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGNumberList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGNumber)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGNumberList': parameter 1 is not of type 'SVGNumber'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex === index) { + return newItem; + } + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index >= items.length) { + index = items.length - 1; + } + + if (items[index]) { + items[index][PropertySymbol.getAttribute] = null; + items[index][PropertySymbol.setAttribute] = null; + } + + const replacedItem = items[index]; + + items[index] = newItem; + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return replacedItem; + } + + /** + * Removes an existing item from the list. + * + * @param index Index. + * @returns The removed item. + */ + public removeItem(index: number): SVGNumber { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGNumberList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGNumberList': 1 argument required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + + index = Number(index); + + if (isNaN(index)) { + index = 0; + } + + if (index >= items.length) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGNumberList': The index provided (${index}) is greater than the maximum bound.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + if (index < 0) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGNumberList': The index provided (${index}) is negative.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + const removedItem = items[index]; + + if (removedItem) { + removedItem[PropertySymbol.getAttribute] = null; + removedItem[PropertySymbol.setAttribute] = null; + } + + items.splice(index, 1); + + this[PropertySymbol.setAttribute]( + items.map((item) => item[PropertySymbol.attributeValue] || '0').join(' ') + ); + + return removedItem; + } + + /** + * Appends an item to the end of the list. + * + * @param newItem The item to add to the list. + * @returns The item being appended. + */ + public appendItem(newItem: SVGNumber): SVGNumber { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGNumberList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGNumberList': 1 argument required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGNumber)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGNumberList': parameter 1 is not of type 'SVGNumber'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + items.push(newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Returns item list from attribute value. + * + * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace + */ + public [PropertySymbol.getItemList](): SVGNumber[] { + const attributeValue = this[PropertySymbol.getAttribute]() ?? ''; + + const cache = this[PropertySymbol.cache]; + + if (cache.attributeValue === attributeValue) { + return cache.items; + } + + if (cache.items.length) { + for (const item of cache.items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + } + + // It is possible to make this statement shorter by using Array.from() and Set, but this is faster when comparing using a bench test. + const items: SVGNumber[] = []; + const trimmed = attributeValue.trim(); + + if (trimmed) { + const parts = trimmed.split(ATTRIBUTE_SEPARATOR_REGEXP); + for (let i = 0, max = parts.length; i < max; i++) { + const item = new SVGNumber(PropertySymbol.illegalConstructor, this[PropertySymbol.window], { + readOnly: this[PropertySymbol.readOnly], + getAttribute: () => item[PropertySymbol.attributeValue], + setAttribute: () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + } + }); + item[PropertySymbol.attributeValue] = String(parseFloat(parts[i])); + items.push(item); + } + } + + cache.attributeValue = attributeValue; + cache.items = items; + + return items; + } +} diff --git a/packages/happy-dom/src/svg/SVGPoint.ts b/packages/happy-dom/src/svg/SVGPoint.ts new file mode 100644 index 000000000..afe396a6b --- /dev/null +++ b/packages/happy-dom/src/svg/SVGPoint.ts @@ -0,0 +1,114 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +const ATTRIBUTE_SEPARATOR_REGEXP = /[\t\f\n\r, ]+/; + +/** + * SVG point. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPoint + */ +export default class SVGPoint { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.attributeValue]: string | null = null; + public [PropertySymbol.readOnly]: boolean = false; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns x. + * + * @returns X. + */ + public get x(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + const parts = (attributeValue || '').split(ATTRIBUTE_SEPARATOR_REGEXP); + return !!parts[0] ? parseFloat(parts[0]) : 0; + } + + /** + * Sets x. + * + * @param value X. + */ + public set x(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'x' property on 'SVGPoint': The object is read-only.` + ); + } + + this[PropertySymbol.attributeValue] = `${value} ${this.y}`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Returns y. + * + * @returns Y. + */ + public get y(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + const parts = (attributeValue || '').split(ATTRIBUTE_SEPARATOR_REGEXP); + return !!parts[1] ? parseFloat(parts[1]) : 0; + } + + /** + * Sets y. + * + * @param value Y. + */ + public set y(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'y' property on 'SVGPoint': The object is read-only.` + ); + } + + this[PropertySymbol.attributeValue] = `${this.x} ${value}`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGPointList.ts b/packages/happy-dom/src/svg/SVGPointList.ts new file mode 100644 index 000000000..873943f94 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGPointList.ts @@ -0,0 +1,526 @@ +import ClassMethodBinder from '../ClassMethodBinder.js'; +import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import SVGPoint from './SVGPoint.js'; + +const ATTRIBUTE_SEPARATOR_REGEXP = /[\t\f\n\r, ]+/; + +/** + * SVGPointList. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPointList + */ +export default class SVGPointList { + [index: number]: SVGPoint; + + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.readOnly]: boolean = false; + private [PropertySymbol.cache]: { items: SVGPoint[]; attributeValue: string } = { + items: [], + attributeValue: '' + }; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + * @param [options.readOnly] Read only. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + readOnly?: boolean; + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + + const methodBinder = new ClassMethodBinder(this, [SVGPointList]); + + return new Proxy(this, { + get: (target, property) => { + if (property === 'length' || property === 'numberOfItems') { + return target[PropertySymbol.getItemList]().length; + } + if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); + return target[property]; + } + const index = Number(property); + if (!isNaN(index)) { + return target[PropertySymbol.getItemList]()[index]; + } + }, + set(target, property, newValue): boolean { + methodBinder.bind(property); + if (typeof property === 'symbol') { + target[property] = newValue; + return true; + } + const index = Number(property); + if (isNaN(index)) { + target[property] = newValue; + } + return true; + }, + deleteProperty(target, property): boolean { + if (typeof property === 'symbol') { + delete target[property]; + return true; + } + const index = Number(property); + if (isNaN(index)) { + delete target[property]; + } + return true; + }, + ownKeys(target): string[] { + return Object.keys(target[PropertySymbol.getItemList]()); + }, + has(target, property): boolean { + if (property in target) { + return true; + } + + if (typeof property === 'symbol') { + return false; + } + + const index = Number(property); + return !isNaN(index) && index >= 0 && index < target[PropertySymbol.getItemList]().length; + }, + defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + + if (property in target) { + Object.defineProperty(target, property, descriptor); + return true; + } + + return false; + }, + getOwnPropertyDescriptor(target, property): PropertyDescriptor { + if (property in target || typeof property === 'symbol') { + return; + } + + const index = Number(property); + const items = target[PropertySymbol.getItemList](); + + if (!isNaN(index) && items[index]) { + return { + value: items[index], + writable: false, + enumerable: true, + configurable: true + }; + } + } + }); + } + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns length. + * + * @returns Length. + */ + public get numberOfItems(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns an iterator, allowing you to go through all values of the key/value pairs contained in this object. + */ + public [Symbol.iterator](): IterableIterator { + return this[PropertySymbol.getItemList]().values(); + } + + /** + * Clears all items from the list. + */ + public clear(): void { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'clear' on 'SVGPointList': The object is read-only.` + ); + } + + for (const item of this[PropertySymbol.cache].items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + + this[PropertySymbol.cache].items = []; + this[PropertySymbol.cache].attributeValue = ''; + this[PropertySymbol.setAttribute](''); + } + + /** + * Replace Token. + * + * @param newItem New item. + * @returns The item being replaced. + */ + public initialize(newItem: SVGPoint): SVGPoint { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGPointList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGPointList': 1 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGPoint)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGPointList': parameter 1 is not of type 'SVGPoint'.` + ); + } + + for (const item of this[PropertySymbol.cache].items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].items = [newItem]; + this[PropertySymbol.cache].attributeValue = newItem[PropertySymbol.attributeValue]; + this[PropertySymbol.setAttribute](newItem[PropertySymbol.attributeValue]); + + return newItem; + } + + /** + * Returns item at index. + * + * @param index Index. + * @returns The item at the index. + **/ + public getItem(index: number | string): SVGPoint { + const items = this[PropertySymbol.getItemList](); + if (typeof index === 'number') { + return items[index] ? items[index] : null; + } + index = Number(index); + index = isNaN(index) ? 0 : index; + return items[index] ? items[index] : null; + } + + /** + * Inserts a new item into the list at the specified position. The first item is number 0. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to insert before is before the removal of the item. If the index is equal to 0, then the new item is inserted at the front of the list. If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being inserted. + */ + public insertItemBefore(newItem: SVGPoint, index: number): SVGPoint { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGPointList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGPointList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGPoint)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGPointList': parameter 1 is not of type 'SVGPoint'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index > items.length) { + index = items.length; + } + + items.splice(index, 0, newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Replaces an existing item in the list with a new item. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to replace is before the removal of the item. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being replaced. + */ + public replaceItem(newItem: SVGPoint, index: number): SVGPoint { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGPointList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGPointList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGPoint)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGPointList': parameter 1 is not of type 'SVGPoint'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex === index) { + return newItem; + } + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index >= items.length) { + index = items.length - 1; + } + + if (items[index]) { + items[index][PropertySymbol.getAttribute] = null; + items[index][PropertySymbol.setAttribute] = null; + } + + const replacedItem = items[index]; + + items[index] = newItem; + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return replacedItem; + } + + /** + * Removes an existing item from the list. + * + * @param index Index. + * @returns The removed item. + */ + public removeItem(index: number): SVGPoint { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGPointList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGPointList': 1 argument required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + + index = Number(index); + + if (isNaN(index)) { + index = 0; + } + + if (index >= items.length) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGPointList': The index provided (${index}) is greater than the maximum bound.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + if (index < 0) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGPointList': The index provided (${index}) is negative.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + const removedItem = items[index]; + + if (removedItem) { + removedItem[PropertySymbol.getAttribute] = null; + removedItem[PropertySymbol.setAttribute] = null; + } + + items.splice(index, 1); + + this[PropertySymbol.setAttribute]( + items.map((item) => item[PropertySymbol.attributeValue] || '0 0').join(' ') + ); + + return removedItem; + } + + /** + * Appends an item to the end of the list. + * + * @param newItem The item to add to the list. + * @returns The item being appended. + */ + public appendItem(newItem: SVGPoint): SVGPoint { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGPointList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGPointList': 1 argument required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGPoint)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGPointList': parameter 1 is not of type 'SVGPoint'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + items.push(newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Returns item list from attribute value. + * + * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace + */ + public [PropertySymbol.getItemList](): SVGPoint[] { + const attributeValue = this[PropertySymbol.getAttribute]() ?? ''; + + const cache = this[PropertySymbol.cache]; + + if (cache.attributeValue === attributeValue) { + return cache.items; + } + + if (cache.items.length) { + for (const item of cache.items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + } + + // It is possible to make this statement shorter by using Array.from() and Set, but this is faster when comparing using a bench test. + const items: SVGPoint[] = []; + const trimmed = attributeValue.trim(); + + if (trimmed) { + const parts = trimmed.split(ATTRIBUTE_SEPARATOR_REGEXP); + for (let i = 0, max = parts.length; i < max; i += 2) { + const x = parseFloat(parts[i]); + const y = parts[i + 1] !== undefined ? ' ' + parseFloat(parts[i + 1]) : ''; + const item = new SVGPoint(PropertySymbol.illegalConstructor, this[PropertySymbol.window], { + readOnly: this[PropertySymbol.readOnly], + getAttribute: () => item[PropertySymbol.attributeValue], + setAttribute: () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || '0 0') + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + } + }); + item[PropertySymbol.attributeValue] = `${x}${y}`; + items.push(item); + } + } + + cache.attributeValue = attributeValue; + cache.items = items; + + return items; + } +} diff --git a/packages/happy-dom/src/svg/SVGPreserveAspectRatio.ts b/packages/happy-dom/src/svg/SVGPreserveAspectRatio.ts new file mode 100644 index 000000000..1759fdb89 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGPreserveAspectRatio.ts @@ -0,0 +1,176 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import SVGPreserveAspectRatioMeetOrSliceEnum from './SVGPreserveAspectRatioMeetOrSliceEnum.js'; +import SVGPreserveAspectRatioAlignEnum from './SVGPreserveAspectRatioAlignEnum.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +const ALIGN_KEYS = Object.values(SVGPreserveAspectRatioAlignEnum); +ALIGN_KEYS.length = ALIGN_KEYS.indexOf(0); + +const MEET_OR_SLICE_KEYS = Object.values(SVGPreserveAspectRatioMeetOrSliceEnum); +MEET_OR_SLICE_KEYS.length = MEET_OR_SLICE_KEYS.indexOf(0); + +/** + * SVG preserve aspect ratio. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGPreserveAspectRatio + */ +export default class SVGPreserveAspectRatio { + // Static properties + public static SVG_MEETORSLICE_UNKNOWN = SVGPreserveAspectRatioMeetOrSliceEnum.unknown; + public static SVG_MEETORSLICE_MEET = SVGPreserveAspectRatioMeetOrSliceEnum.meet; + public static SVG_MEETORSLICE_SLICE = SVGPreserveAspectRatioMeetOrSliceEnum.slice; + public static SVG_PRESERVEASPECTRATIO_UNKNOWN = SVGPreserveAspectRatioAlignEnum.unknown; + public static SVG_PRESERVEASPECTRATIO_NONE = SVGPreserveAspectRatioAlignEnum.none; + public static SVG_PRESERVEASPECTRATIO_XMINYMIN = SVGPreserveAspectRatioAlignEnum.xMinYMin; + public static SVG_PRESERVEASPECTRATIO_XMIDYMIN = SVGPreserveAspectRatioAlignEnum.xMidYMin; + public static SVG_PRESERVEASPECTRATIO_XMAXYMIN = SVGPreserveAspectRatioAlignEnum.xMaxYMin; + public static SVG_PRESERVEASPECTRATIO_XMINYMID = SVGPreserveAspectRatioAlignEnum.xMinYMid; + public static SVG_PRESERVEASPECTRATIO_XMIDYMID = SVGPreserveAspectRatioAlignEnum.xMidYMid; + public static SVG_PRESERVEASPECTRATIO_XMAXYMID = SVGPreserveAspectRatioAlignEnum.xMaxYMid; + public static SVG_PRESERVEASPECTRATIO_XMINYMAX = SVGPreserveAspectRatioAlignEnum.xMinYMax; + public static SVG_PRESERVEASPECTRATIO_XMIDYMAX = SVGPreserveAspectRatioAlignEnum.xMidYMax; + public static SVG_PRESERVEASPECTRATIO_XMAXYMAX = SVGPreserveAspectRatioAlignEnum.xMaxYMax; + + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.attributeValue]: string | null = null; + public [PropertySymbol.readOnly]: boolean = false; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns align. + * + * @returns Align. + */ + public get align(): SVGPreserveAspectRatioAlignEnum { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + + if (!attributeValue) { + return SVGPreserveAspectRatioAlignEnum.xMidYMid; + } + + const align = attributeValue.split(/\s+/)[0]; + + if (SVGPreserveAspectRatioAlignEnum[align] === undefined) { + return SVGPreserveAspectRatioAlignEnum.xMidYMid; + } + + return SVGPreserveAspectRatioAlignEnum[align]; + } + + /** + * Sets align. + * + * @param value Align. + */ + public set align(value: SVGPreserveAspectRatioAlignEnum) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'align' property on 'SVGPreserveAspectRatio': The object is read-only.` + ); + } + + const parsedValue = Number(value); + + if (isNaN(parsedValue) || parsedValue < 1 || parsedValue > ALIGN_KEYS.length) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'align' property on 'SVGPreserveAspectRatio': The alignment provided is invalid.` + ); + } + + this[PropertySymbol.attributeValue] = `${ALIGN_KEYS[parsedValue]} ${ + MEET_OR_SLICE_KEYS[this.meetOrSlice] + }`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Returns meet or slice. + * + * @returns Meet or slice. + */ + public get meetOrSlice(): SVGPreserveAspectRatioMeetOrSliceEnum { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + + if (!attributeValue) { + return SVGPreserveAspectRatioMeetOrSliceEnum.meet; + } + + const meetOrSlice = attributeValue.split(/\s+/)[1]; + + if (!meetOrSlice || SVGPreserveAspectRatioMeetOrSliceEnum[meetOrSlice] === undefined) { + return SVGPreserveAspectRatioMeetOrSliceEnum.meet; + } + + return SVGPreserveAspectRatioMeetOrSliceEnum[meetOrSlice]; + } + + /** + * Sets meet or slice. + * + * @param value Meet or slice. + */ + public set meetOrSlice(value: SVGPreserveAspectRatioMeetOrSliceEnum) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'meetOrSlice' property on 'SVGPreserveAspectRatio': The object is read-only.` + ); + } + + const parsedValue = Number(value); + + if (isNaN(parsedValue) || parsedValue < 1 || parsedValue > 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'meetOrSlice' property on 'SVGPreserveAspectRatio': The meetOrSlice provided is invalid.` + ); + } + + this[PropertySymbol.attributeValue] = `${ALIGN_KEYS[this.align]} ${ + MEET_OR_SLICE_KEYS[parsedValue] + }`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGPreserveAspectRatioAlignEnum.ts b/packages/happy-dom/src/svg/SVGPreserveAspectRatioAlignEnum.ts new file mode 100644 index 000000000..8142c1ed1 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGPreserveAspectRatioAlignEnum.ts @@ -0,0 +1,15 @@ +enum SVGPreserveAspectRatioAlignEnum { + unknown = 0, + none = 1, + xMinYMin = 2, + xMidYMin = 3, + xMaxYMin = 4, + xMinYMid = 5, + xMidYMid = 6, + xMaxYMid = 7, + xMinYMax = 8, + xMidYMax = 9, + xMaxYMax = 10 +} + +export default SVGPreserveAspectRatioAlignEnum; diff --git a/packages/happy-dom/src/svg/SVGPreserveAspectRatioMeetOrSliceEnum.ts b/packages/happy-dom/src/svg/SVGPreserveAspectRatioMeetOrSliceEnum.ts new file mode 100644 index 000000000..c54be3fd8 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGPreserveAspectRatioMeetOrSliceEnum.ts @@ -0,0 +1,7 @@ +enum SVGPreserveAspectRatioMeetOrSliceEnum { + unknown = 0, + meet = 1, + slice = 2 +} + +export default SVGPreserveAspectRatioMeetOrSliceEnum; diff --git a/packages/happy-dom/src/svg/SVGRect.ts b/packages/happy-dom/src/svg/SVGRect.ts new file mode 100644 index 000000000..697755bef --- /dev/null +++ b/packages/happy-dom/src/svg/SVGRect.ts @@ -0,0 +1,200 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +/** + * Rect object. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGRect + */ +export default class SVGRect { + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.attributeValue]: string | null = null; + public [PropertySymbol.readOnly]: boolean = false; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns x value. + * + * @returns X value. + */ + public get x(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + if (!attributeValue) { + return 0; + } + const parts = attributeValue.split(/\s+/); + const value = Number(parts[0]); + return isNaN(value) ? 0 : value; + } + + /** + * Sets x value. + * + * @param value X value. + */ + public set x(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'x' property on 'SVGRect': The object is read-only.` + ); + } + + this[PropertySymbol.attributeValue] = `${String( + typeof value === 'number' ? value : parseFloat(value) + )} ${this.y} ${this.width} ${this.height}`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Returns y value. + * + * @returns Y value. + */ + public get y(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + if (!attributeValue) { + return 0; + } + const parts = attributeValue.split(/\s+/); + const value = Number(parts[1]); + return isNaN(value) ? 0 : value; + } + + /** + * Sets y value. + * + * @param value Y value. + */ + public set y(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'y' property on 'SVGRect': The object is read-only.` + ); + } + + this[PropertySymbol.attributeValue] = `${this.x} ${String( + typeof value === 'number' ? value : parseFloat(value) + )} ${this.width} ${this.height}`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Returns width value. + * + * @returns Width value. + */ + public get width(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + if (!attributeValue) { + return 0; + } + const parts = attributeValue.split(/\s+/); + const value = Number(parts[2]); + return isNaN(value) ? 0 : value; + } + + /** + * Sets width value. + * + * @param value Width value. + */ + public set width(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'width' property on 'SVGRect': The object is read-only.` + ); + } + + this[PropertySymbol.attributeValue] = `${this.x} ${this.y} ${String( + typeof value === 'number' ? value : parseFloat(value) + )} ${this.height}`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Returns height value. + * + * @returns Height value. + */ + public get height(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + if (!attributeValue) { + return 0; + } + const parts = attributeValue.split(/\s+/); + const value = Number(parts[3]); + return isNaN(value) ? 0 : value; + } + + /** + * Sets height value. + * + * @param value Height value. + */ + public set height(value: number) { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to set the 'height' property on 'SVGRect': The object is read-only.` + ); + } + + this[PropertySymbol.attributeValue] = `${this.x} ${this.y} ${this.width} ${String( + typeof value === 'number' ? value : parseFloat(value) + )}`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGStringList.ts b/packages/happy-dom/src/svg/SVGStringList.ts new file mode 100644 index 000000000..a6502ad85 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGStringList.ts @@ -0,0 +1,422 @@ +import ClassMethodBinder from '../ClassMethodBinder.js'; +import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; + +const ATTRIBUTE_SPLIT_REGEXP = /[\t\f\n\r ,]+/; + +/** + * SVGStringList. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGStringList + */ +export default class SVGStringList { + [index: number]: string; + + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.readOnly]: boolean = false; + private [PropertySymbol.cache]: { items: string[]; attributeValue: string } = { + items: [], + attributeValue: '' + }; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param [options.readOnly] Read only. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + readOnly?: boolean; + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + const methodBinder = new ClassMethodBinder(this, [SVGStringList]); + + return new Proxy(this, { + get: (target, property) => { + if (property === 'length' || property === 'numberOfItems') { + return target[PropertySymbol.getItemList]().length; + } + if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); + return target[property]; + } + const index = Number(property); + if (!isNaN(index)) { + return target[PropertySymbol.getItemList]()[index]; + } + }, + set(target, property, newValue): boolean { + methodBinder.bind(property); + if (typeof property === 'symbol') { + target[property] = newValue; + return true; + } + const index = Number(property); + if (isNaN(index)) { + target[property] = newValue; + } + return true; + }, + deleteProperty(target, property): boolean { + if (typeof property === 'symbol') { + delete target[property]; + return true; + } + const index = Number(property); + if (isNaN(index)) { + delete target[property]; + } + return true; + }, + ownKeys(target): string[] { + return Object.keys(target[PropertySymbol.getItemList]()); + }, + has(target, property): boolean { + if (property in target) { + return true; + } + + if (typeof property === 'symbol') { + return false; + } + + const index = Number(property); + return !isNaN(index) && index >= 0 && index < target[PropertySymbol.getItemList]().length; + }, + defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + + if (property in target) { + Object.defineProperty(target, property, descriptor); + return true; + } + + return false; + }, + getOwnPropertyDescriptor(target, property): PropertyDescriptor { + if (property in target || typeof property === 'symbol') { + return; + } + + const index = Number(property); + const items = target[PropertySymbol.getItemList](); + + if (!isNaN(index) && items[index]) { + return { + value: items[index], + writable: false, + enumerable: true, + configurable: true + }; + } + } + }); + } + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns length. + * + * @returns Length. + */ + public get numberOfItems(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns an iterator, allowing you to go through all values of the key/value pairs contained in this object. + */ + public [Symbol.iterator](): IterableIterator { + return this[PropertySymbol.getItemList]().values(); + } + + /** + * Clears all items from the list. + */ + public clear(): void { + this[PropertySymbol.cache].attributeValue = ''; + this[PropertySymbol.cache].items = []; + this[PropertySymbol.setAttribute](''); + } + + /** + * Replace Token. + * + * @param newItem New item. + * @returns The item being replaced. + */ + public initialize(newItem: string): string { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGStringList': The object is read-only.` + ); + } + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGStringList': 1 arguments required, but only ${arguments.length} present.` + ); + } + newItem = String(newItem); + if (!newItem) { + this.clear(); + return; + } + this[PropertySymbol.setAttribute](newItem); + return newItem; + } + + /** + * Returns item at index. + * + * @param index Index. + * @returns The item at the index. + **/ + public getItem(index: number | string): string { + const items = this[PropertySymbol.getItemList](); + if (typeof index === 'number') { + return items[index] ? items[index] : null; + } + index = Number(index); + index = isNaN(index) ? 0 : index; + return items[index] ? items[index] : null; + } + + /** + * Inserts a new item into the list at the specified position. The first item is number 0. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to insert before is before the removal of the item. If the index is equal to 0, then the new item is inserted at the front of the list. If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being inserted. + */ + public insertItemBefore(newItem: string, index: number): string { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGStringList': The object is read-only.` + ); + } + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGStringList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + newItem = String(newItem); + + if (!newItem) { + return newItem; + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index > items.length) { + index = items.length; + } + + items.splice(index, 0, newItem); + + this[PropertySymbol.setAttribute](items.join(' ')); + + return newItem; + } + + /** + * Replaces an existing item in the list with a new item. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to replace is before the removal of the item. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being replaced. + */ + public replaceItem(newItem: string, index: number): string { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGStringList': The object is read-only.` + ); + } + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGStringList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + newItem = String(newItem); + + if (!newItem) { + return this.removeItem(index); + } + + const items = this[PropertySymbol.getItemList](); + + if (index < 0) { + index = 0; + } else if (index >= items.length) { + index = items.length - 1; + } + + const replacedItem = items[index]; + + items[index] = newItem; + + this[PropertySymbol.setAttribute](items.join(' ')); + + return replacedItem; + } + + /** + * Removes an existing item from the list. + * + * @param index Index. + * @returns The removed item. + */ + public removeItem(index: number): string { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGStringList': The object is read-only.` + ); + } + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGStringList': 1 argument required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + + index = Number(index); + + if (isNaN(index)) { + index = 0; + } + + if (index >= items.length) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGStringList': The index provided (${index}) is greater than the maximum bound.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + if (index < 0) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGStringList': The index provided (${index}) is negative.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + const removedItem = items[index]; + + items.splice(index, 1); + + this[PropertySymbol.setAttribute](items.join(' ')); + + return removedItem; + } + + /** + * Appends an item to the end of the list. + * + * @param newItem The item to add to the list. + * @returns The item being appended. + */ + public appendItem(newItem: string): string { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGStringList': The object is read-only.` + ); + } + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGStringList': 1 argument required, but only ${arguments.length} present.` + ); + } + + newItem = String(newItem); + + if (!newItem) { + return newItem; + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + items.push(newItem); + + this[PropertySymbol.setAttribute](items.join(' ')); + + return newItem; + } + + /** + * Returns item list from attribute value. + * + * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace + */ + public [PropertySymbol.getItemList](): string[] { + const attributeValue = this[PropertySymbol.getAttribute]() ?? ''; + + const cache = this[PropertySymbol.cache]; + + if (cache.attributeValue === attributeValue) { + return cache.items; + } + + // It is possible to make this statement shorter by using Array.from() and Set, but this is faster when comparing using a bench test. + const items = []; + const trimmed = attributeValue.trim(); + + if (trimmed) { + for (const item of trimmed.split(ATTRIBUTE_SPLIT_REGEXP)) { + if (!items.includes(item)) { + items.push(item); + } + } + } + + cache.attributeValue = attributeValue; + cache.items = items; + + return items; + } +} diff --git a/packages/happy-dom/src/svg/SVGTransform.ts b/packages/happy-dom/src/svg/SVGTransform.ts new file mode 100644 index 000000000..06f2747b8 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGTransform.ts @@ -0,0 +1,369 @@ +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import SVGMatrix from './SVGMatrix.js'; +import SVGTransformTypeEnum from './SVGTransformTypeEnum.js'; + +const TRANSFORM_REGEXP = /([a-zA-Z0-9]+)\(([^)]+)\)/; +const TRANSFORM_PARAMETER_SPLIT_REGEXP = /[\s,]+/; + +/** + * SVG transform. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTransform + */ +export default class SVGTransform { + public static SVG_TRANSFORM_UNKNOWN = SVGTransformTypeEnum.unknown; + public static SVG_TRANSFORM_MATRIX = SVGTransformTypeEnum.matrix; + public static SVG_TRANSFORM_TRANSLATE = SVGTransformTypeEnum.translate; + public static SVG_TRANSFORM_SCALE = SVGTransformTypeEnum.scale; + public static SVG_TRANSFORM_ROTATE = SVGTransformTypeEnum.rotate; + public static SVG_TRANSFORM_SKEWX = SVGTransformTypeEnum.skewX; + public static SVG_TRANSFORM_SKEWY = SVGTransformTypeEnum.skewY; + + // Internal properties + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string; + public [PropertySymbol.setAttribute]: (value: string) => void; + public [PropertySymbol.attributeValue]: string | null = null; + public [PropertySymbol.readOnly]: boolean = false; + public [PropertySymbol.matrix]: SVGMatrix | null = null; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param [options] Options. + * @param [options.readOnly] Read only. + * @param [options.getAttribute] Get attribute. + * @param [options.setAttribute] Set attribute. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options?: { + readOnly?: boolean; + getAttribute?: () => string | null; + setAttribute?: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + + if (options) { + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute || null; + this[PropertySymbol.setAttribute] = options.setAttribute || null; + } + } + + /** + * Returns type. + * + * @returns Type. + */ + public get type(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + const match = attributeValue?.match(TRANSFORM_REGEXP); + + if (!match) { + return SVGTransformTypeEnum.unknown; + } + + switch (match[1]) { + case 'matrix': + return SVGTransformTypeEnum.matrix; + case 'translate': + return SVGTransformTypeEnum.translate; + case 'rotate': + return SVGTransformTypeEnum.rotate; + case 'scale': + return SVGTransformTypeEnum.scale; + case 'skewX': + return SVGTransformTypeEnum.skewX; + case 'skewY': + return SVGTransformTypeEnum.skewY; + } + + return 0; + } + + /** + * Returns angle. + * + * @returns Angle. + */ + public get angle(): number { + const attributeValue = this[PropertySymbol.getAttribute] + ? this[PropertySymbol.getAttribute]() + : this[PropertySymbol.attributeValue]; + const match = attributeValue?.match(TRANSFORM_REGEXP); + + if (!match) { + return 0; + } + + const angle = parseFloat(match[2].trim().split(TRANSFORM_PARAMETER_SPLIT_REGEXP)[0]); + + if (isNaN(angle)) { + return 0; + } + + switch (match[1]) { + case 'rotate': + case 'skewX': + case 'skewY': + return angle; + } + + return 0; + } + + /** + * Returns matrix. + * + * @returns Matrix. + */ + public get matrix(): SVGMatrix { + if (!this[PropertySymbol.matrix]) { + this[PropertySymbol.matrix] = new SVGMatrix( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: this[PropertySymbol.readOnly], + getAttribute: () => { + if (this[PropertySymbol.getAttribute]) { + return this[PropertySymbol.getAttribute](); + } + return this[PropertySymbol.attributeValue]; + }, + setAttribute: (value: string) => { + this[PropertySymbol.attributeValue] = value; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](value); + return; + } + } + } + ); + } + return this[PropertySymbol.matrix]; + } + + /** + * Set matrix. + * + * @param matrix Matrix. + */ + public setMatrix(matrix: SVGMatrix): void { + if (!(matrix instanceof SVGMatrix)) { + throw new TypeError( + 'Failed to set the "matrix" property on "SVGTransform": The provided value is not of type "SVGMatrix".' + ); + } + + if (this[PropertySymbol.readOnly]) { + return; + } + + if (this[PropertySymbol.matrix]) { + this[PropertySymbol.matrix][PropertySymbol.getAttribute] = null; + this[PropertySymbol.matrix][PropertySymbol.setAttribute] = null; + } + + matrix[PropertySymbol.getAttribute] = () => { + if (this[PropertySymbol.getAttribute]) { + return this[PropertySymbol.getAttribute](); + } + return this[PropertySymbol.attributeValue]; + }; + + matrix[PropertySymbol.setAttribute] = (value: string) => { + this[PropertySymbol.attributeValue] = value; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](value); + return; + } + }; + + this[PropertySymbol.matrix] = matrix; + + if (matrix[PropertySymbol.attributeValue] !== this[PropertySymbol.attributeValue]) { + this[PropertySymbol.attributeValue] = matrix[PropertySymbol.attributeValue]; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + } + + /** + * Set translate. + * + * @param x X. + * @param y Y. + */ + public setTranslate(x: number, y: number): void { + if (arguments.length < 2) { + throw new TypeError( + `Failed to execute 'setTranslate' on 'SVGTransform': 2 arguments required, but only ${arguments.length} present.` + ); + } + + x = Number(x); + y = Number(y); + + if (isNaN(x) || isNaN(y)) { + throw new TypeError( + `Failed to execute 'setTranslate' on 'SVGTransform': The provided float value is non-finite.` + ); + } + + if (this[PropertySymbol.readOnly]) { + return; + } + + this[PropertySymbol.attributeValue] = `translate(${x} ${y})`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Set scale. + * + * @param x X. + * @param y Y. + */ + public setScale(x: number, y: number): void { + if (arguments.length < 2) { + throw new TypeError( + `Failed to execute 'setScale' on 'SVGTransform': 2 arguments required, but only ${arguments.length} present.` + ); + } + + x = Number(x); + y = Number(y); + + if (isNaN(x) || isNaN(y)) { + throw new TypeError( + `Failed to execute 'setScale' on 'SVGTransform': The provided float value is non-finite.` + ); + } + + if (this[PropertySymbol.readOnly]) { + return; + } + + this[PropertySymbol.attributeValue] = `scale(${x} ${y})`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Set rotate. + * + * @param angle Angle. + * @param x X. + * @param y Y. + */ + public setRotate(angle: number, x: number, y: number): void { + if (arguments.length < 3) { + throw new TypeError( + `Failed to execute 'setRotate' on 'SVGTransform': 3 arguments required, but only ${arguments.length} present.` + ); + } + + angle = Number(angle); + x = Number(x); + y = Number(y); + + if (isNaN(angle) || isNaN(x) || isNaN(y)) { + throw new TypeError( + `Failed to execute 'setRotate' on 'SVGTransform': The provided float value is non-finite.` + ); + } + + if (this[PropertySymbol.readOnly]) { + return; + } + + this[PropertySymbol.attributeValue] = `rotate(${angle} ${x} ${y})`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Set skew x. + * + * @param angle Angle. + */ + public setSkewX(angle: number): void { + if (arguments.length < 1) { + throw new TypeError( + `Failed to execute 'setSkewX' on 'SVGTransform': 1 arguments required, but only ${arguments.length} present.` + ); + } + + angle = Number(angle); + + if (isNaN(angle)) { + throw new TypeError( + `Failed to execute 'setSkewX' on 'SVGTransform': The provided float value is non-finite.` + ); + } + + if (this[PropertySymbol.readOnly]) { + return; + } + + this[PropertySymbol.attributeValue] = `skewX(${angle})`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } + + /** + * Set skew y. + * + * @param angle Angle. + */ + public setSkewY(angle: number): void { + if (arguments.length < 1) { + throw new TypeError( + `Failed to execute 'setSkewY' on 'SVGTransform': 1 arguments required, but only ${arguments.length} present.` + ); + } + + angle = Number(angle); + + if (isNaN(angle)) { + throw new TypeError( + `Failed to execute 'setSkewY' on 'SVGTransform': The provided float value is non-finite.` + ); + } + + if (this[PropertySymbol.readOnly]) { + return; + } + + this[PropertySymbol.attributeValue] = `skewY(${angle})`; + + if (this[PropertySymbol.setAttribute]) { + this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]); + } + } +} diff --git a/packages/happy-dom/src/svg/SVGTransformList.ts b/packages/happy-dom/src/svg/SVGTransformList.ts new file mode 100644 index 000000000..fb0ff0c6c --- /dev/null +++ b/packages/happy-dom/src/svg/SVGTransformList.ts @@ -0,0 +1,531 @@ +import ClassMethodBinder from '../ClassMethodBinder.js'; +import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js'; +import * as PropertySymbol from '../PropertySymbol.js'; +import BrowserWindow from '../window/BrowserWindow.js'; +import SVGTransform from './SVGTransform.js'; + +const TRANSFORM_REGEXP = /([a-zA-Z0-9]+)\(([^)]+)\)/gm; +const EMPTY_MATRIX = 'matrix(1 0 0 1 0 0)'; + +/** + * SVGTransformList. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGTransformList + */ +export default class SVGTransformList { + [index: number]: SVGTransform; + + public [PropertySymbol.window]: BrowserWindow; + public [PropertySymbol.getAttribute]: () => string | null = null; + public [PropertySymbol.setAttribute]: (value: string) => void | null = null; + public [PropertySymbol.readOnly]: boolean = false; + private [PropertySymbol.cache]: { items: SVGTransform[]; attributeValue: string } = { + items: [], + attributeValue: '' + }; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + * @param window Window. + * @param options Options. + * @param options.getAttribute Get attribute. + * @param options.setAttribute Set attribute. + * @param [options.readOnly] Read only. + */ + constructor( + illegalConstructorSymbol: symbol, + window: BrowserWindow, + options: { + readOnly?: boolean; + getAttribute: () => string | null; + setAttribute: (value: string) => void; + } + ) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + + this[PropertySymbol.window] = window; + this[PropertySymbol.readOnly] = !!options.readOnly; + this[PropertySymbol.getAttribute] = options.getAttribute; + this[PropertySymbol.setAttribute] = options.setAttribute; + + const methodBinder = new ClassMethodBinder(this, [SVGTransformList]); + + return new Proxy(this, { + get: (target, property) => { + if (property === 'length' || property === 'numberOfItems') { + return target[PropertySymbol.getItemList]().length; + } + if (property in target || typeof property === 'symbol') { + methodBinder.bind(property); + return target[property]; + } + const index = Number(property); + if (!isNaN(index)) { + return target[PropertySymbol.getItemList]()[index]; + } + }, + set(target, property, newValue): boolean { + methodBinder.bind(property); + if (typeof property === 'symbol') { + target[property] = newValue; + return true; + } + const index = Number(property); + if (isNaN(index)) { + target[property] = newValue; + } + return true; + }, + deleteProperty(target, property): boolean { + if (typeof property === 'symbol') { + delete target[property]; + return true; + } + const index = Number(property); + if (isNaN(index)) { + delete target[property]; + } + return true; + }, + ownKeys(target): string[] { + return Object.keys(target[PropertySymbol.getItemList]()); + }, + has(target, property): boolean { + if (property in target) { + return true; + } + + if (typeof property === 'symbol') { + return false; + } + + const index = Number(property); + return !isNaN(index) && index >= 0 && index < target[PropertySymbol.getItemList]().length; + }, + defineProperty(target, property, descriptor): boolean { + methodBinder.preventBinding(property); + + if (property in target) { + Object.defineProperty(target, property, descriptor); + return true; + } + + return false; + }, + getOwnPropertyDescriptor(target, property): PropertyDescriptor { + if (property in target || typeof property === 'symbol') { + return; + } + + const index = Number(property); + const items = target[PropertySymbol.getItemList](); + + if (!isNaN(index) && items[index]) { + return { + value: items[index], + writable: false, + enumerable: true, + configurable: true + }; + } + } + }); + } + + /** + * Returns length. + * + * @returns Length. + */ + public get length(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns length. + * + * @returns Length. + */ + public get numberOfItems(): number { + return this[PropertySymbol.getItemList]().length; + } + + /** + * Returns an iterator, allowing you to go through all values of the key/value pairs contained in this object. + */ + public [Symbol.iterator](): IterableIterator { + return this[PropertySymbol.getItemList]().values(); + } + + /** + * Clears all items from the list. + */ + public clear(): void { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'clear' on 'SVGTransformList': The object is read-only.` + ); + } + + for (const item of this[PropertySymbol.cache].items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + + this[PropertySymbol.cache].items = []; + this[PropertySymbol.cache].attributeValue = ''; + this[PropertySymbol.setAttribute](''); + } + + /** + * Replace Token. + * + * @param newItem New item. + * @returns The item being replaced. + */ + public initialize(newItem: SVGTransform): SVGTransform { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGTransformList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'initialize' on 'SVGTransformList': 1 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGTransform)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGTransformList': parameter 1 is not of type 'SVGTransform'.` + ); + } + + for (const item of this[PropertySymbol.cache].items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].items = [newItem]; + this[PropertySymbol.cache].attributeValue = newItem[PropertySymbol.attributeValue]; + this[PropertySymbol.setAttribute](newItem[PropertySymbol.attributeValue]); + + return newItem; + } + + /** + * Returns item at index. + * + * @param index Index. + * @returns The item at the index. + **/ + public getItem(index: number | string): SVGTransform { + const items = this[PropertySymbol.getItemList](); + if (typeof index === 'number') { + return items[index] ? items[index] : null; + } + index = Number(index); + index = isNaN(index) ? 0 : index; + return items[index] ? items[index] : null; + } + + /** + * Inserts a new item into the list at the specified position. The first item is number 0. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to insert before is before the removal of the item. If the index is equal to 0, then the new item is inserted at the front of the list. If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being inserted. + */ + public insertItemBefore(newItem: SVGTransform, index: number): SVGTransform { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGTransformList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGTransformList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGTransform)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'insertItemBefore' on 'SVGTransformList': parameter 1 is not of type 'SVGTransform'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index > items.length) { + index = items.length; + } + + items.splice(index, 0, newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Replaces an existing item in the list with a new item. If newItem is already in a list, it is removed from its previous list before it is inserted into this list. The inserted item is the item itself and not a copy. If the item is already in this list, note that the index of the item to replace is before the removal of the item. + * + * @param newItem The item to insert into the list. + * @param index Index. + * @returns The item being replaced. + */ + public replaceItem(newItem: SVGTransform, index: number): SVGTransform { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGTransformList': The object is read-only.` + ); + } + + if (arguments.length < 2) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGTransformList': 2 arguments required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGTransform)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'replaceItem' on 'SVGTransformList': parameter 1 is not of type 'SVGTransform'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex === index) { + return newItem; + } + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + if (index < 0) { + index = 0; + } else if (index >= items.length) { + index = items.length - 1; + } + + if (items[index]) { + items[index][PropertySymbol.getAttribute] = null; + items[index][PropertySymbol.setAttribute] = null; + } + + const replacedItem = items[index]; + + items[index] = newItem; + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return replacedItem; + } + + /** + * Removes an existing item from the list. + * + * @param index Index. + * @returns The removed item. + */ + public removeItem(index: number): SVGTransform { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGTransformList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'removeItem' on 'SVGTransformList': 1 argument required, but only ${arguments.length} present.` + ); + } + + const items = this[PropertySymbol.getItemList](); + + index = Number(index); + + if (isNaN(index)) { + index = 0; + } + + if (index >= items.length) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGTransformList': The index provided (${index}) is greater than the maximum bound.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + if (index < 0) { + throw new this[PropertySymbol.window].DOMException( + `Failed to execute 'removeItem' on 'SVGTransformList': The index provided (${index}) is negative.`, + DOMExceptionNameEnum.indexSizeError + ); + } + + const removedItem = items[index]; + + if (removedItem) { + removedItem[PropertySymbol.getAttribute] = null; + removedItem[PropertySymbol.setAttribute] = null; + } + + items.splice(index, 1); + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return removedItem; + } + + /** + * Appends an item to the end of the list. + * + * @param newItem The item to add to the list. + * @returns The item being appended. + */ + public appendItem(newItem: SVGTransform): SVGTransform { + if (this[PropertySymbol.readOnly]) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGTransformList': The object is read-only.` + ); + } + + if (arguments.length < 1) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGTransformList': 1 argument required, but only ${arguments.length} present.` + ); + } + + if (!(newItem instanceof SVGTransform)) { + throw new this[PropertySymbol.window].TypeError( + `Failed to execute 'appendItem' on 'SVGTransformList': parameter 1 is not of type 'SVGTransform'.` + ); + } + + const items = this[PropertySymbol.getItemList](); + const existingIndex = items.indexOf(newItem); + + if (existingIndex !== -1) { + items.splice(existingIndex, 1); + } + + items.push(newItem); + + newItem[PropertySymbol.getAttribute] = () => newItem[PropertySymbol.attributeValue]; + newItem[PropertySymbol.setAttribute] = () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + }; + + this[PropertySymbol.cache].attributeValue = items + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + + return newItem; + } + + /** + * Returns item list from attribute value. + * + * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace + */ + public [PropertySymbol.getItemList](): SVGTransform[] { + const attributeValue = this[PropertySymbol.getAttribute]() ?? ''; + + const cache = this[PropertySymbol.cache]; + + if (cache.attributeValue === attributeValue) { + return cache.items; + } + + if (cache.items.length) { + for (const item of cache.items) { + item[PropertySymbol.getAttribute] = null; + item[PropertySymbol.setAttribute] = null; + } + } + + // It is possible to make this statement shorter by using Array.from() and Set, but this is faster when comparing using a bench test. + const items: SVGTransform[] = []; + const trimmed = attributeValue.trim(); + + if (trimmed) { + const regexp = new RegExp(TRANSFORM_REGEXP); + let match: RegExpExecArray | null; + while ((match = regexp.exec(trimmed))) { + const item = new SVGTransform( + PropertySymbol.illegalConstructor, + this[PropertySymbol.window], + { + readOnly: this[PropertySymbol.readOnly], + getAttribute: () => item[PropertySymbol.attributeValue], + setAttribute: () => { + this[PropertySymbol.cache].attributeValue = this[PropertySymbol.getItemList]() + .map((item) => item[PropertySymbol.attributeValue] || EMPTY_MATRIX) + .join(' '); + this[PropertySymbol.setAttribute](this[PropertySymbol.cache].attributeValue); + } + } + ); + item[PropertySymbol.attributeValue] = `${match[1]}(${match[2]})`; + items.push(item); + } + } + + cache.attributeValue = attributeValue; + cache.items = items; + + return items; + } +} diff --git a/packages/happy-dom/src/svg/SVGTransformTypeEnum.ts b/packages/happy-dom/src/svg/SVGTransformTypeEnum.ts new file mode 100644 index 000000000..2e85a8887 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGTransformTypeEnum.ts @@ -0,0 +1,11 @@ +enum SVGTransformTypeEnum { + unknown = 0, + matrix = 1, + translate = 2, + scale = 3, + rotate = 4, + skewX = 5, + skewY = 6 +} + +export default SVGTransformTypeEnum; diff --git a/packages/happy-dom/src/svg/SVGUnitTypes.ts b/packages/happy-dom/src/svg/SVGUnitTypes.ts new file mode 100644 index 000000000..24ac14cd8 --- /dev/null +++ b/packages/happy-dom/src/svg/SVGUnitTypes.ts @@ -0,0 +1,26 @@ +import * as PropertySymbol from '../PropertySymbol.js'; + +/** + * SVG Unit Types. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGUnitTypes + */ +export default class SVGUnitTypes { + public static readonly SVG_UNIT_TYPE_UNKNOWN = 0; + public static readonly SVG_UNIT_TYPE_USERSPACEONUSE = 1; + public static readonly SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = 2; + public readonly SVG_UNIT_TYPE_UNKNOWN = 0; + public readonly SVG_UNIT_TYPE_USERSPACEONUSE = 1; + public readonly SVG_UNIT_TYPE_OBJECTBOUNDINGBOX = 2; + + /** + * Constructor. + * + * @param illegalConstructorSymbol Illegal constructor symbol. + */ + constructor(illegalConstructorSymbol: Symbol) { + if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) { + throw new TypeError('Illegal constructor'); + } + } +} diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index 1d1964d8c..30e29bfe6 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -4,7 +4,6 @@ import Stream from 'stream'; import { ReadableStream } from 'stream/web'; import { URLSearchParams } from 'url'; import VM from 'vm'; -import ClassMethodBinder from '../ClassMethodBinder.js'; import * as PropertySymbol from '../PropertySymbol.js'; import Base64 from '../base64/Base64.js'; import BrowserErrorCaptureEnum from '../browser/enums/BrowserErrorCaptureEnum.js'; @@ -84,8 +83,8 @@ import DocumentType from '../nodes/document-type/DocumentType.js'; import Document from '../nodes/document/Document.js'; import DocumentReadyStateEnum from '../nodes/document/DocumentReadyStateEnum.js'; import DocumentReadyStateManager from '../nodes/document/DocumentReadyStateManager.js'; -import DOMRect from '../nodes/element/DOMRect.js'; -import DOMRectReadOnly from '../nodes/element/DOMRectReadOnly.js'; +import DOMRect from '../dom/DOMRect.js'; +import DOMRectReadOnly from '../dom/DOMRectReadOnly.js'; import Element from '../nodes/element/Element.js'; import HTMLCollection from '../nodes/element/HTMLCollection.js'; import NamedNodeMap from '../nodes/element/NamedNodeMap.js'; @@ -174,10 +173,7 @@ import Node from '../nodes/node/Node.js'; import NodeList from '../nodes/node/NodeList.js'; import ProcessingInstruction from '../nodes/processing-instruction/ProcessingInstruction.js'; import ShadowRoot from '../nodes/shadow-root/ShadowRoot.js'; -import SVGDocument from '../nodes/svg-document/SVGDocument.js'; import SVGElement from '../nodes/svg-element/SVGElement.js'; -import SVGGraphicsElement from '../nodes/svg-element/SVGGraphicsElement.js'; -import SVGSVGElement from '../nodes/svg-element/SVGSVGElement.js'; import Text from '../nodes/text/Text.js'; import XMLDocument from '../nodes/xml-document/XMLDocument.js'; import PermissionStatus from '../permissions/PermissionStatus.js'; @@ -211,6 +207,104 @@ import HTMLOptionsCollection from '../nodes/html-select-element/HTMLOptionsColle import WindowClassExtender from './WindowClassExtender.js'; import WindowBrowserContext from './WindowBrowserContext.js'; import CanvasCaptureMediaStreamTrack from '../nodes/html-canvas-element/CanvasCaptureMediaStreamTrack.js'; +import SVGSVGElement from '../nodes/svg-svg-element/SVGSVGElement.js'; +import SVGGraphicsElement from '../nodes/svg-graphics-element/SVGGraphicsElement.js'; +import SVGAnimateElement from '../nodes/svg-animate-element/SVGAnimateElement.js'; +import SVGAnimateMotionElement from '../nodes/svg-animate-motion-element/SVGAnimateMotionElement.js'; +import SVGAnimateTransformElement from '../nodes/svg-animate-transform-element/SVGAnimateTransformElement.js'; +import SVGCircleElement from '../nodes/svg-circle-element/SVGCircleElement.js'; +import SVGClipPathElement from '../nodes/svg-clip-path-element/SVGClipPathElement.js'; +import SVGDefsElement from '../nodes/svg-defs-element/SVGDefsElement.js'; +import SVGDescElement from '../nodes/svg-desc-element/SVGDescElement.js'; +import SVGEllipseElement from '../nodes/svg-ellipse-element/SVGEllipseElement.js'; +import SVGFEBlendElement from '../nodes/svg-fe-blend-element/SVGFEBlendElement.js'; +import SVGFEColorMatrixElement from '../nodes/svg-fe-color-matrix-element/SVGFEColorMatrixElement.js'; +import SVGFEComponentTransferElement from '../nodes/svg-fe-component-transfer-element/SVGFEComponentTransferElement.js'; +import SVGFECompositeElement from '../nodes/svg-fe-composite-element/SVGFECompositeElement.js'; +import SVGFEConvolveMatrixElement from '../nodes/svg-fe-convolve-matrix-element/SVGFEConvolveMatrixElement.js'; +import SVGFEDiffuseLightingElement from '../nodes/svg-fe-diffuse-lighting-element/SVGFEDiffuseLightingElement.js'; +import SVGFEDisplacementMapElement from '../nodes/svg-fe-displacement-map-element/SVGFEDisplacementMapElement.js'; +import SVGFEDistantLightElement from '../nodes/svg-fe-distant-light-element/SVGFEDistantLightElement.js'; +import SVGFEDropShadowElement from '../nodes/svg-fe-drop-shadow-element/SVGFEDropShadowElement.js'; +import SVGFEFloodElement from '../nodes/svg-fe-flood-element/SVGFEFloodElement.js'; +import SVGFEFuncAElement from '../nodes/svg-fe-func-a-element/SVGFEFuncAElement.js'; +import SVGFEFuncBElement from '../nodes/svg-fe-func-b-element/SVGFEFuncBElement.js'; +import SVGFEFuncGElement from '../nodes/svg-fe-func-g-element/SVGFEFuncGElement.js'; +import SVGFEFuncRElement from '../nodes/svg-fe-func-r-element/SVGFEFuncRElement.js'; +import SVGFEGaussianBlurElement from '../nodes/svg-fe-gaussian-blur-element/SVGFEGaussianBlurElement.js'; +import SVGFEImageElement from '../nodes/svg-fe-image-element/SVGFEImageElement.js'; +import SVGFEMergeElement from '../nodes/svg-fe-merge-element/SVGFEMergeElement.js'; +import SVGFEMergeNodeElement from '../nodes/svg-fe-merge-node-element/SVGFEMergeNodeElement.js'; +import SVGFEMorphologyElement from '../nodes/svg-fe-morphology-element/SVGFEMorphologyElement.js'; +import SVGFEOffsetElement from '../nodes/svg-fe-offset-element/SVGFEOffsetElement.js'; +import SVGFEPointLightElement from '../nodes/svg-fe-point-light-element/SVGFEPointLightElement.js'; +import SVGFESpecularLightingElement from '../nodes/svg-fe-specular-lighting-element/SVGFESpecularLightingElement.js'; +import SVGFESpotLightElement from '../nodes/svg-fe-spot-light-element/SVGFESpotLightElement.js'; +import SVGFETileElement from '../nodes/svg-fe-tile-element/SVGFETileElement.js'; +import SVGFETurbulenceElement from '../nodes/svg-fe-turbulence-element/SVGFETurbulenceElement.js'; +import SVGFilterElement from '../nodes/svg-filter-element/SVGFilterElement.js'; +import SVGForeignObjectElement from '../nodes/svg-foreign-object-element/SVGForeignObjectElement.js'; +import SVGGElement from '../nodes/svg-g-element/SVGGElement.js'; +import SVGImageElement from '../nodes/svg-image-element/SVGImageElement.js'; +import SVGLineElement from '../nodes/svg-line-element/SVGLineElement.js'; +import SVGLinearGradientElement from '../nodes/svg-linear-gradient-element/SVGLinearGradientElement.js'; +import SVGMarkerElement from '../nodes/svg-marker-element/SVGMarkerElement.js'; +import SVGMaskElement from '../nodes/svg-mask-element/SVGMaskElement.js'; +import SVGMetadataElement from '../nodes/svg-metadata-element/SVGMetadataElement.js'; +import SVGMPathElement from '../nodes/svg-m-path-element/SVGMPathElement.js'; +import SVGPathElement from '../nodes/svg-path-element/SVGPathElement.js'; +import SVGPatternElement from '../nodes/svg-pattern-element/SVGPatternElement.js'; +import SVGPolygonElement from '../nodes/svg-polygon-element/SVGPolygonElement.js'; +import SVGPolylineElement from '../nodes/svg-polyline-element/SVGPolylineElement.js'; +import SVGRadialGradientElement from '../nodes/svg-radial-gradient-element/SVGRadialGradientElement.js'; +import SVGRectElement from '../nodes/svg-rect-element/SVGRectElement.js'; +import SVGScriptElement from '../nodes/svg-script-element/SVGScriptElement.js'; +import SVGSetElement from '../nodes/svg-set-element/SVGSetElement.js'; +import SVGStopElement from '../nodes/svg-stop-element/SVGStopElement.js'; +import SVGStyleElement from '../nodes/svg-style-element/SVGStyleElement.js'; +import SVGSwitchElement from '../nodes/svg-switch-element/SVGSwitchElement.js'; +import SVGSymbolElement from '../nodes/svg-symbol-element/SVGSymbolElement.js'; +import SVGTextElement from '../nodes/svg-text-element/SVGTextElement.js'; +import SVGTextPathElement from '../nodes/svg-text-path-element/SVGTextPathElement.js'; +import SVGTitleElement from '../nodes/svg-title-element/SVGTitleElement.js'; +import SVGTSpanElement from '../nodes/svg-t-span-element/SVGTSpanElement.js'; +import SVGUseElement from '../nodes/svg-use-element/SVGUseElement.js'; +import SVGViewElement from '../nodes/svg-view-element/SVGViewElement.js'; +import SVGAnimationElement from '../nodes/svg-animation-element/SVGAnimationElement.js'; +import SVGComponentTransferFunctionElement from '../nodes/svg-component-transfer-function-element/SVGComponentTransferFunctionElement.js'; +import SVGGeometryElement from '../nodes/svg-geometry-element/SVGGeometryElement.js'; +import SVGGradientElement from '../nodes/svg-gradient-element/SVGGradientElement.js'; +import SVGTextPositioningElement from '../nodes/svg-text-positioning-element/SVGTextPositioningElement.js'; +import DOMMatrixReadOnly from '../dom/dom-matrix/DOMMatrixReadOnly.js'; +import DOMMatrix from '../dom/dom-matrix/DOMMatrix.js'; +import SVGAngle from '../svg/SVGAngle.js'; +import SVGAnimatedAngle from '../svg/SVGAnimatedAngle.js'; +import SVGAnimatedBoolean from '../svg/SVGAnimatedBoolean.js'; +import SVGAnimatedEnumeration from '../svg/SVGAnimatedEnumeration.js'; +import SVGAnimatedInteger from '../svg/SVGAnimatedInteger.js'; +import SVGAnimatedLength from '../svg/SVGAnimatedLength.js'; +import SVGLength from '../svg/SVGLength.js'; +import SVGAnimatedNumber from '../svg/SVGAnimatedNumber.js'; +import SVGAnimatedNumberList from '../svg/SVGAnimatedNumberList.js'; +import SVGAnimatedPreserveAspectRatio from '../svg/SVGAnimatedPreserveAspectRatio.js'; +import SVGAnimatedRect from '../svg/SVGAnimatedRect.js'; +import SVGAnimatedString from '../svg/SVGAnimatedString.js'; +import SVGAnimatedTransformList from '../svg/SVGAnimatedTransformList.js'; +import SVGLengthList from '../svg/SVGLengthList.js'; +import SVGMatrix from '../svg/SVGMatrix.js'; +import SVGNumber from '../svg/SVGNumber.js'; +import SVGNumberList from '../svg/SVGNumberList.js'; +import SVGPoint from '../svg/SVGPoint.js'; +import SVGPointList from '../svg/SVGPointList.js'; +import SVGPreserveAspectRatio from '../svg/SVGPreserveAspectRatio.js'; +import SVGRect from '../svg/SVGRect.js'; +import SVGStringList from '../svg/SVGStringList.js'; +import SVGTransform from '../svg/SVGTransform.js'; +import SVGTransformList from '../svg/SVGTransformList.js'; +import SVGUnitTypes from '../svg/SVGUnitTypes.js'; +import DOMPoint from '../dom/DOMPoint.js'; +import SVGAnimatedLengthList from '../svg/SVGAnimatedLengthList.js'; + const TIMER = { setTimeout: globalThis.setTimeout.bind(globalThis), clearTimeout: globalThis.clearTimeout.bind(globalThis), @@ -220,6 +314,7 @@ const TIMER = { setImmediate: globalThis.setImmediate.bind(globalThis), clearImmediate: globalThis.clearImmediate.bind(globalThis) }; + const IS_NODE_JS_TIMEOUT_ENVIRONMENT = setTimeout.toString().includes('new Timeout'); /** @@ -257,9 +352,6 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal // Nodes public readonly Node = Node; public readonly Attr = Attr; - public readonly SVGSVGElement = SVGSVGElement; - public readonly SVGElement = SVGElement; - public readonly SVGGraphicsElement = SVGGraphicsElement; public readonly ShadowRoot = ShadowRoot; public readonly ProcessingInstruction = ProcessingInstruction; public readonly Element = Element; @@ -270,14 +362,13 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal public declare readonly Document: typeof Document; public declare readonly HTMLDocument: typeof HTMLDocument; public declare readonly XMLDocument: typeof XMLDocument; - public declare readonly SVGDocument: typeof SVGDocument; public declare readonly DocumentFragment: typeof DocumentFragment; public declare readonly Text: typeof Text; public declare readonly Comment: typeof Comment; public declare readonly Image: typeof Image; public declare readonly Audio: typeof Audio; - // Element classes + // HTML Element classes public readonly HTMLAnchorElement = HTMLAnchorElement; public readonly HTMLButtonElement = HTMLButtonElement; public readonly HTMLOptGroupElement = HTMLOptGroupElement; @@ -345,6 +436,79 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal public readonly HTMLBodyElement = HTMLBodyElement; public readonly HTMLAreaElement = HTMLAreaElement; + // SVG Element classes + public readonly SVGSVGElement = SVGSVGElement; + public readonly SVGAnimateElement = SVGAnimateElement; + public readonly SVGAnimateMotionElement = SVGAnimateMotionElement; + public readonly SVGAnimateTransformElement = SVGAnimateTransformElement; + public readonly SVGCircleElement = SVGCircleElement; + public readonly SVGClipPathElement = SVGClipPathElement; + public readonly SVGDefsElement = SVGDefsElement; + public readonly SVGDescElement = SVGDescElement; + public readonly SVGEllipseElement = SVGEllipseElement; + public readonly SVGFEBlendElement = SVGFEBlendElement; + public readonly SVGFEColorMatrixElement = SVGFEColorMatrixElement; + public readonly SVGFEComponentTransferElement = SVGFEComponentTransferElement; + public readonly SVGFECompositeElement = SVGFECompositeElement; + public readonly SVGFEConvolveMatrixElement = SVGFEConvolveMatrixElement; + public readonly SVGFEDiffuseLightingElement = SVGFEDiffuseLightingElement; + public readonly SVGFEDisplacementMapElement = SVGFEDisplacementMapElement; + public readonly SVGFEDistantLightElement = SVGFEDistantLightElement; + public readonly SVGFEDropShadowElement = SVGFEDropShadowElement; + public readonly SVGFEFloodElement = SVGFEFloodElement; + public readonly SVGFEFuncAElement = SVGFEFuncAElement; + public readonly SVGFEFuncBElement = SVGFEFuncBElement; + public readonly SVGFEFuncGElement = SVGFEFuncGElement; + public readonly SVGFEFuncRElement = SVGFEFuncRElement; + public readonly SVGFEGaussianBlurElement = SVGFEGaussianBlurElement; + public readonly SVGFEImageElement = SVGFEImageElement; + public readonly SVGFEMergeElement = SVGFEMergeElement; + public readonly SVGFEMergeNodeElement = SVGFEMergeNodeElement; + public readonly SVGFEMorphologyElement = SVGFEMorphologyElement; + public readonly SVGFEOffsetElement = SVGFEOffsetElement; + public readonly SVGFEPointLightElement = SVGFEPointLightElement; + public readonly SVGFESpecularLightingElement = SVGFESpecularLightingElement; + public readonly SVGFESpotLightElement = SVGFESpotLightElement; + public readonly SVGFETileElement = SVGFETileElement; + public readonly SVGFETurbulenceElement = SVGFETurbulenceElement; + public readonly SVGFilterElement = SVGFilterElement; + public readonly SVGForeignObjectElement = SVGForeignObjectElement; + public readonly SVGGElement = SVGGElement; + public readonly SVGImageElement = SVGImageElement; + public readonly SVGLineElement = SVGLineElement; + public readonly SVGLinearGradientElement = SVGLinearGradientElement; + public readonly SVGMarkerElement = SVGMarkerElement; + public readonly SVGMaskElement = SVGMaskElement; + public readonly SVGMetadataElement = SVGMetadataElement; + public readonly SVGMPathElement = SVGMPathElement; + public readonly SVGPathElement = SVGPathElement; + public readonly SVGPatternElement = SVGPatternElement; + public readonly SVGPolygonElement = SVGPolygonElement; + public readonly SVGPolylineElement = SVGPolylineElement; + public readonly SVGRadialGradientElement = SVGRadialGradientElement; + public readonly SVGRectElement = SVGRectElement; + public readonly SVGScriptElement = SVGScriptElement; + public readonly SVGSetElement = SVGSetElement; + public readonly SVGStopElement = SVGStopElement; + public readonly SVGStyleElement = SVGStyleElement; + public readonly SVGSwitchElement = SVGSwitchElement; + public readonly SVGSymbolElement = SVGSymbolElement; + public readonly SVGTextElement = SVGTextElement; + public readonly SVGTextPathElement = SVGTextPathElement; + public readonly SVGTitleElement = SVGTitleElement; + public readonly SVGTSpanElement = SVGTSpanElement; + public readonly SVGUseElement = SVGUseElement; + public readonly SVGViewElement = SVGViewElement; + + // Abstract SVG Element classes + public readonly SVGElement = SVGElement; + public readonly SVGAnimationElement = SVGAnimationElement; + public readonly SVGComponentTransferFunctionElement = SVGComponentTransferFunctionElement; + public readonly SVGGeometryElement = SVGGeometryElement; + public readonly SVGGradientElement = SVGGradientElement; + public readonly SVGTextPositioningElement = SVGTextPositioningElement; + public readonly SVGGraphicsElement = SVGGraphicsElement; + // Event classes public readonly Event = Event; public readonly UIEvent = UIEvent; @@ -411,7 +575,6 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal public declare readonly NodeIterator: typeof NodeIterator; public declare readonly TreeWalker: typeof TreeWalker; public declare readonly MutationObserver: typeof MutationObserver; - public declare readonly CSSStyleDeclaration: typeof CSSStyleDeclaration; public declare readonly MessagePort: typeof MessagePort; public declare readonly DataTransfer: typeof DataTransfer; public declare readonly DataTransferItem: typeof DataTransferItem; @@ -457,6 +620,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal public readonly MutationRecord = MutationRecord; public readonly IntersectionObserver = IntersectionObserver; public readonly IntersectionObserverEntry = IntersectionObserverEntry; + public readonly CSSStyleDeclaration = CSSStyleDeclaration; public readonly CSSRule = CSSRule; public readonly CSSContainerRule = CSSContainerRule; public readonly CSSFontFaceRule = CSSFontFaceRule; @@ -486,6 +650,35 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal public readonly RadioNodeList = RadioNodeList; public readonly FileList = FileList; public readonly Screen = Screen; + public readonly DOMMatrixReadOnly = DOMMatrixReadOnly; + public readonly DOMMatrix = DOMMatrix; + public readonly SVGAngle = SVGAngle; + public readonly SVGAnimatedAngle = SVGAnimatedAngle; + public readonly SVGAnimatedBoolean = SVGAnimatedBoolean; + public readonly SVGAnimatedEnumeration = SVGAnimatedEnumeration; + public readonly SVGAnimatedInteger = SVGAnimatedInteger; + public readonly SVGAnimatedLength = SVGAnimatedLength; + public readonly SVGAnimatedNumber = SVGAnimatedNumber; + public readonly SVGAnimatedNumberList = SVGAnimatedNumberList; + public readonly SVGAnimatedPreserveAspectRatio = SVGAnimatedPreserveAspectRatio; + public readonly SVGAnimatedRect = SVGAnimatedRect; + public readonly SVGAnimatedString = SVGAnimatedString; + public readonly SVGAnimatedTransformList = SVGAnimatedTransformList; + public readonly SVGLength = SVGLength; + public readonly SVGLengthList = SVGLengthList; + public readonly SVGMatrix = SVGMatrix; + public readonly SVGNumber = SVGNumber; + public readonly SVGNumberList = SVGNumberList; + public readonly SVGPoint = SVGPoint; + public readonly SVGPointList = SVGPointList; + public readonly SVGPreserveAspectRatio = SVGPreserveAspectRatio; + public readonly SVGRect = SVGRect; + public readonly SVGStringList = SVGStringList; + public readonly SVGTransform = SVGTransform; + public readonly SVGTransformList = SVGTransformList; + public readonly SVGAnimatedLengthList = SVGAnimatedLengthList; + public readonly SVGUnitTypes = SVGUnitTypes; + public readonly DOMPoint = DOMPoint; public readonly Window = this.constructor; // Node.js Classes @@ -657,7 +850,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal loadEvent[PropertySymbol.eventPhase] = EventPhaseEnum.none; }); - ClassMethodBinder.bindMethods(this, [EventTarget, BrowserWindow]); + this[PropertySymbol.bindMethods](); } /** @@ -926,7 +1119,8 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal */ public getComputedStyle(element: Element): CSSStyleDeclaration { element[PropertySymbol.computedStyle] = - element[PropertySymbol.computedStyle] || new CSSStyleDeclaration(element, true); + element[PropertySymbol.computedStyle] || + new CSSStyleDeclaration(PropertySymbol.illegalConstructor, this, { element, computed: true }); return element[PropertySymbol.computedStyle]; } @@ -1509,4 +1703,34 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal WindowBrowserContext.removeWindowBrowserFrameRelation(this); } + + /** + * Binds methods to a window as scope. + */ + public [PropertySymbol.bindMethods](): void { + for (const _class of [BrowserWindow, EventTarget]) { + const propertyDescriptors = Object.getOwnPropertyDescriptors(_class.prototype); + const keys: Array = Object.keys(propertyDescriptors); + + for (const key of keys) { + const descriptor = propertyDescriptors[key]; + if (descriptor.get || descriptor.set) { + Object.defineProperty(this, key, { + ...descriptor, + get: descriptor.get?.bind(this), + set: descriptor.set?.bind(this) + }); + } else if ( + key !== 'constructor' && + typeof descriptor.value === 'function' && + !descriptor.value.toString().startsWith('class ') + ) { + Object.defineProperty(this, key, { + ...descriptor, + value: descriptor.value.bind(this) + }); + } + } + } + } } diff --git a/packages/happy-dom/src/window/WindowBrowserContext.ts b/packages/happy-dom/src/window/WindowBrowserContext.ts index a7a3e1727..7b0d11730 100644 --- a/packages/happy-dom/src/window/WindowBrowserContext.ts +++ b/packages/happy-dom/src/window/WindowBrowserContext.ts @@ -68,6 +68,9 @@ export default class WindowBrowserContext { * @returns Browser frame. */ public getBrowserFrame(): IBrowserFrame | null { + if (!this.#window) { + return null; + } return ( (this.constructor)[PropertySymbol.browserFrames].get( this.#window[PropertySymbol.internalId] diff --git a/packages/happy-dom/src/window/WindowClassExtender.ts b/packages/happy-dom/src/window/WindowClassExtender.ts index 5505517ba..dbe61f7e2 100644 --- a/packages/happy-dom/src/window/WindowClassExtender.ts +++ b/packages/happy-dom/src/window/WindowClassExtender.ts @@ -4,7 +4,6 @@ import * as PropertySymbol from '../PropertySymbol.js'; import DocumentImplementation from '../nodes/document/Document.js'; import HTMLDocumentImplementation from '../nodes/html-document/HTMLDocument.js'; import XMLDocumentImplementation from '../nodes/xml-document/XMLDocument.js'; -import SVGDocumentImplementation from '../nodes/svg-document/SVGDocument.js'; import DocumentFragmentImplementation from '../nodes/document-fragment/DocumentFragment.js'; import TextImplementation from '../nodes/text/Text.js'; import CommentImplementation from '../nodes/comment/Comment.js'; @@ -13,7 +12,6 @@ import AudioImplementation from '../nodes/html-audio-element/Audio.js'; import NodeIteratorImplementation from '../tree-walker/NodeIterator.js'; import TreeWalkerImplementation from '../tree-walker/TreeWalker.js'; import MutationObserverImplementation from '../mutation-observer/MutationObserver.js'; -import CSSStyleDeclarationImplementation from '../css/declaration/CSSStyleDeclaration.js'; import MessagePortImplementation from '../event/MessagePort.js'; import DataTransferImplementation from '../event/DataTransfer.js'; import DataTransferItemImplementation from '../event/DataTransferItem.js'; @@ -78,11 +76,6 @@ export default class WindowClassExtender { XMLDocument.prototype[PropertySymbol.window] = window; (window.XMLDocument) = XMLDocument; - // SVGDocument - class SVGDocument extends SVGDocumentImplementation {} - SVGDocument.prototype[PropertySymbol.window] = window; - (window.SVGDocument) = SVGDocument; - // DocumentFragment class DocumentFragment extends DocumentFragmentImplementation {} DocumentFragment.prototype[PropertySymbol.window] = window; @@ -123,11 +116,6 @@ export default class WindowClassExtender { MutationObserver.prototype[PropertySymbol.window] = window; (window.MutationObserver) = MutationObserver; - // CSSStyleDeclaration - class CSSStyleDeclaration extends CSSStyleDeclarationImplementation {} - CSSStyleDeclaration.prototype[PropertySymbol.window] = window; - (window.CSSStyleDeclaration) = CSSStyleDeclaration; - // MessagePort class MessagePort extends MessagePortImplementation {} MessagePort.prototype[PropertySymbol.window] = window; diff --git a/packages/happy-dom/src/xml-parser/XMLParser.ts b/packages/happy-dom/src/xml-parser/XMLParser.ts index 65a96f9fc..b5452b572 100755 --- a/packages/happy-dom/src/xml-parser/XMLParser.ts +++ b/packages/happy-dom/src/xml-parser/XMLParser.ts @@ -10,6 +10,8 @@ import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js'; import HTMLElementConfig from '../config/HTMLElementConfig.js'; import * as Entities from 'entities'; import HTMLElementConfigContentModelEnum from '../config/HTMLElementConfigContentModelEnum.js'; +import SVGElementConfig from '../config/SVGElementConfig.js'; +import StringUtility from '../StringUtility.js'; /** * Markup RegExp. @@ -24,7 +26,7 @@ import HTMLElementConfigContentModelEnum from '../config/HTMLElementConfigConten * Group 8: End of start tag (e.g. ">" in "
"). */ const MARKUP_REGEXP = - /<([a-zA-Z0-9-]+)|<\/([a-zA-Z0-9-]+)\s*>|||