Repository: edwinm/carbonium
Branch: master
Commit: e6fa92369015
Files: 22
Total size: 47.2 KB
Directory structure:
gitextract_aeet774x/
├── .github/
│ └── workflows/
│ ├── codeql.yml
│ ├── coveralls.yml
│ └── scorecard.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .prettierignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── demo/
│ ├── demo.js
│ └── index.html
├── dist/
│ ├── bundle.js
│ ├── carbonium.d.ts
│ └── src/
│ └── carbonium.d.ts
├── eslint.config.js
├── package.json
├── playwright.config.ts
├── rollup.config.js
├── src/
│ └── carbonium.ts
├── test/
│ └── test.ts
├── tests/
│ └── carbonium.spec.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
schedule:
- cron: "11 3 * * 3"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [javascript]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
================================================
FILE: .github/workflows/coveralls.yml
================================================
name: Coveralls
on: ["push", "pull_request"]
jobs:
test:
name: Run units tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install
run: npm install
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium firefox
- name: Lint
run: npm run lint
- name: Test
run: npm test
================================================
FILE: .github/workflows/scorecard.yml
================================================
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: "38 20 * * 1"
push:
branches: ["master"]
# Declare default permissions as read only.
permissions:
contents: read
actions: read
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
contents: read
actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
repo_token: ${{ secrets.GITHUB_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: sarif-results
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
================================================
FILE: .gitignore
================================================
node_modules
coverage
.idea
/playwright-report/
================================================
FILE: .husky/pre-commit
================================================
npx pretty-quick --staged
npm run lint
================================================
FILE: .prettierignore
================================================
dist
================================================
FILE: .prettierrc.json
================================================
{
"trailingComma": "es5",
"arrowParens": "always"
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Edwin Martin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
[](https://github.com/edwinm/carbonium/actions/workflows/scorecard.yml) [](https://github.com/edwinm/carbonium/actions/workflows/codeql.yml) [](https://coveralls.io/github/edwinm/carbonium?branch=master) [](https://socket.dev/npm/package/carbonium) [](https://www.codefactor.io/repository/github/edwinm/carbonium) [](https://sonarcloud.io/summary/new_code?id=edwinm_carbonium) [](https://snyk.io/test/github/edwinm/carbonium) [](https://bundlephobia.com/package/carbonium) [](https://www.npmjs.com/package/carbonium) [](https://github.com/edwinm/carbonium/blob/master/LICENSE) 
[](#readme)
> One kilobyte library for easy DOM manipulation
With carbonium, you can call `$(selector)` and the result can be accessed as both an DOM element and an array of matched elements.
DOM element operations are applied to all matched elements.
## Examples
To set the `left` CSS property of all elements with the class `indent` to 40 pixels:
```javascript
$(".indent").style.left = "40px";
```
To add the class `important` to all div's with "priority" as content:
```javascript
$("div")
.filter((el) => el.textContent == "priority")
.classList.add("important");
```
You can use carbonium to create elements:
```javascript
const error = $("
An error has occured!
")[0];
```
## Installation
```bash
npm install --save-dev carbonium
```
Now you can import carbonium:
```javascript
import { $ } from "carbonium";
```
If you don't want to install or use a bundler like webpack or rollup.js, you can import carbonium like this:
```javascript
const { $ } = await import(
"https://cdn.jsdelivr.net/npm/carbonium@1/dist/bundle.min.js"
);
```
## API
### Select elements
### `$(selector [, parentNode])`
#### Parameters
| Name | Type | Description |
| ---------- | ------------------------------ | -------------------------------------------------------------------------- |
| selector | string | Selector to select elements |
| parentNode | Document \| Element (optional) | Document or element in which to apply the selector, defaults to `document` |
#### Returns
An array of the matched elements, which can also be accessed as a single element.
### Create element
### `$(html [, parentNode])`
#### Parameters
| Name | Type | Description |
| ---------- | ------------------------------ | -------------------------------------------------------------------------- |
| html | string | HTML of element to create, starting with "<" |
| parentNode | Document \| Element (optional) | Document or element in which to apply the selector, defaults to `document` |
#### Returns
An array with one created element.
## TypeScript
If you use TypeScript, it's good to know Carbonium is written in TypeScript and provides all typings.
You can use generics to declare a specific type of element,
for example `HTMLInputElement` to make the `disabled` property available:
```typescript
$("input, select, button").disabled = true;
```
## Why?
You might find most frameworks are quite bulky and bad for performance ([1](https://css-tricks.com/radeventlistener-a-tale-of-client-side-framework-performance/)).
On the other side, you might find using native DOM and writing `document.querySelectorAll(selector)` each time you want to do some DOM operations to become tedious.
You can write your own helper function, but that only takes part of the pain away.
Carbonium seeks to find the sweet spot between using a framework and using the native DOM.
## jQuery
Isn't this just jQuery and isn't that obsolete and bad practice?
No. Carbonium doesn't have the disadvantages of jQuery:
1. Carbonium is very small: just around one kilobyte.
2. There's no new API to learn, carbonium provides only standard DOM API's.
## Browser support
Carbonium is supported by all modern browsers. It is tested to work on desktop and mobile with Firefox 79, Chrome 84, Safari 13 and Edge 84.
It should work with all browsers supporting Proxy, see [Can I use Proxy](https://caniuse.com/#feat=proxy) for support tables.
## Name
[](https://commons.wikimedia.org/wiki/File:Diamond_and_graphite_without_structures.jpg)
Carbonium is the Latin name for carbon. Carbon has two forms (allotropes): graphite and diamond.
Just like this library, in which the result presents itself both as one element and as a list of elements.
[Photo CC BY-SA 3.0](https://commons.wikimedia.org/wiki/File:Diamond_and_graphite_without_structures.jpg)
## License
Copyright 2023 [Edwin Martin](https://bitstorm.org/) and released under the MIT license.
================================================
FILE: demo/demo.js
================================================
const importPromise = import(
"https://cdn.jsdelivr.net/npm/carbonium/dist/bundle.min.js"
);
const loadPromise = new Promise((resolve) => {
document.addEventListener("DOMContentLoaded", resolve);
});
// Start code when both carbonium and the page are loaded
Promise.all([importPromise, loadPromise]).then(([{ $ }]) => {
$("#out").innerText = "Demo.";
$("#hello-button").addEventListener("click", () => {
$("#out").innerText = "Hello. It is working!";
});
});
================================================
FILE: demo/index.html
================================================
Demo
Demo
…
================================================
FILE: dist/bundle.js
================================================
/**
Carbonium 1.3.0
@copyright 2020 Edwin Martin
@license MIT
*/
function $(selectors, parentNode) {
let nodelist;
// If the first parameter starts with "<", create a DOM node
if (selectors.startsWith("<")) {
nodelist = [
new DOMParser().parseFromString(selectors, "text/html").body.firstChild,
];
}
else {
// Else, do querySelectorAll
nodelist = (parentNode || document).querySelectorAll(selectors);
}
// Wrap it in a Proxy
return new Proxy(nodelist, proxyHandler);
}
// Used by style, classList and relList
// When setting one of these, remember the elements to apply to
let currentListNodelist;
let propList;
const proxyHandler = {
get(target, prop) {
let propValue = null;
// Return iterator when asked for iterator, only used in ES2015+
if (prop == Symbol.iterator) {
return function* () {
for (const element of target) {
yield element;
}
};
}
// Special case for style, classList and relList
if (prop == "style" || prop == "classList" || prop == "relList") {
currentListNodelist = target;
propList = prop;
// Matched elements can be a list of any element or an empty list
// Use getter of, for example, document.body.style
const propValue = Reflect.get(document.body, prop);
return new Proxy(propValue, proxyHandler);
}
// style.setProperty, getPropertyValue…, classList.add, contains, remove…, relList…
if (target instanceof CSSStyleDeclaration ||
target instanceof DOMTokenList) {
// Matched elements can be a list of any element or an empty list
// Use getter of, for example, document.body.style.color
propValue = Reflect.get(document.body[propList], prop);
// When getter is a function, apply it to all matched elements
if (typeof propValue == "function") {
return new Proxy(propValue, {
apply: function (target, thisArg, argumentsList) {
currentListNodelist.forEach((el) => {
Reflect.apply(target, el[propList], argumentsList);
});
return new Proxy(currentListNodelist, proxyHandler);
},
});
}
else {
return propValue;
}
}
// Are we dealing with an Array function like forEach, map and filter?
if (Array.prototype.hasOwnProperty(prop)) {
const propValue = Reflect.get(Array.prototype, prop);
if (typeof propValue == "function") {
return new Proxy(propValue, {
apply: function (target, thisArg, argumentsList) {
const ret = Reflect.apply(target, thisArg, argumentsList);
// When function returns undefined (like forEach),
// return all matched elements, so calls can be chained
// For example forEach(…).setAttribute(…)
const newTarget = typeof ret != "undefined" ? ret : thisArg;
return new Proxy(newTarget, proxyHandler);
},
});
}
}
// Get property or call function on DOM elements
if (target.length > 0) {
// Might be DOM element specific, like input.select(),
// so use first array element to get reference
if (prop in target[0]) {
propValue = Reflect.get(target[0], prop);
}
}
else {
// Empty list, targeted DOM element unknown,
// use getter of document.body
if (prop in document.body) {
propValue = Reflect.get(document.body, prop);
}
}
// Propagate DOM prop value
if (propValue) {
if (typeof propValue == "function") {
return new Proxy(propValue, {
apply: function (target, thisArg, argumentsList) {
let retFirst = null;
let first = true;
// Apply on individual elements
for (const el of thisArg) {
const ret = Reflect.apply(target, el, argumentsList);
if (first) {
retFirst = ret;
first = false;
}
}
return retFirst !== null && retFirst !== void 0 ? retFirst : thisArg;
},
});
}
else {
return propValue;
}
}
// Default
return Reflect.get(target, prop);
},
// DOM property is set
set(target, prop, value) {
if ("forEach" in target && !(target instanceof CSSStyleDeclaration)) {
target.forEach((el) => {
Reflect.set(el, prop, value);
});
}
else {
Reflect.set(target, prop, value);
}
return true;
},
deleteProperty(target, prop) {
if (prop in target) {
return delete target[prop];
}
return false;
},
};
export { $ };
//# sourceMappingURL=bundle.js.map
================================================
FILE: dist/carbonium.d.ts
================================================
/**
Carbonium __buildVersion__
@copyright 2020 Edwin Martin
@license MIT
*/
export declare function $(selectors: string, parentNode?: Document | ShadowRoot | HTMLElement): CarboniumType;
export type CarboniumType = CarboniumList & T;
interface CarboniumList extends Array {
concat(...items: ConcatArray[]): CarboniumType;
concat(...items: (T | ConcatArray)[]): CarboniumType;
reverse(): CarboniumType;
slice(start?: number, end?: number): CarboniumType;
splice(start: number, deleteCount?: number): CarboniumType;
splice(start: number, deleteCount: number, ...items: T[]): CarboniumType;
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): CarboniumType;
filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): CarboniumType;
setAttribute(qualifiedName: string, value: string): CarboniumType;
classList: CarboniumClassList;
style: CarboniumStyleList;
}
interface CarboniumClassList extends DOMTokenList {
add(...tokens: string[]): CarboniumType;
remove(...tokens: string[]): CarboniumType;
replace(oldToken: string, newToken: string): boolean;
forEach(callbackfn: (value: string, key: number, parent: DOMTokenList) => void, thisArg?: any): CarboniumType;
}
interface CarboniumStyleList extends CSSStyleDeclaration {
removeProperty(property: string): CarboniumList & string;
setProperty(property: string, value: string | null, priority?: string): CarboniumType;
}
export {};
================================================
FILE: dist/src/carbonium.d.ts
================================================
/**
Carbonium __buildVersion__
@copyright 2020 Edwin Martin
@license MIT
*/
export declare function $(selectors: string, parentNode?: Document | ShadowRoot | HTMLElement): CarboniumType;
export type CarboniumType = CarboniumList & T;
interface CarboniumList extends Array {
concat(...items: ConcatArray[]): CarboniumType;
concat(...items: (T | ConcatArray)[]): CarboniumType;
reverse(): CarboniumType;
slice(start?: number, end?: number): CarboniumType;
splice(start: number, deleteCount?: number): CarboniumType;
splice(start: number, deleteCount: number, ...items: T[]): CarboniumType;
forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): CarboniumType;
filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): CarboniumType;
setAttribute(qualifiedName: string, value: string): CarboniumType;
classList: CarboniumClassList;
style: CarboniumStyleList;
}
interface CarboniumClassList extends DOMTokenList {
add(...tokens: string[]): CarboniumType;
remove(...tokens: string[]): CarboniumType;
replace(oldToken: string, newToken: string): boolean;
forEach(callbackfn: (value: string, key: number, parent: DOMTokenList) => void, thisArg?: any): CarboniumType;
}
interface CarboniumStyleList extends CSSStyleDeclaration {
removeProperty(property: string): CarboniumList & string;
setProperty(property: string, value: string | null, priority?: string): CarboniumType;
}
export {};
================================================
FILE: eslint.config.js
================================================
import tseslint from "typescript-eslint";
import js from "@eslint/js";
import prettier from "eslint-config-prettier";
import globals from "globals";
export default tseslint.config(
{
ignores: [
"dist/**",
"demo/**",
"coverage/**",
"iteratortest/**",
"karma.conf.cjs",
],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
globals: {
...globals.browser,
...globals.es2021,
},
},
rules: {
"no-prototype-builtins": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
},
},
prettier
);
================================================
FILE: package.json
================================================
{
"name": "carbonium",
"version": "1.3.0",
"description": "One kilobyte library for easy DOM manipulation",
"type": "module",
"browser": "dist/bundle.iife.min.js",
"module": "dist/bundle.min.js",
"types": "dist/carbonium.d.ts",
"sideEffects": false,
"scripts": {
"prepare": "husky",
"start": "http-server -o demo/ --silent",
"build": "rollup --config --sourcemap",
"dev": "rollup --config --sourcemap --watch",
"release": "npm i --package-lock && npm run lint && npm test && npm publish",
"pretest": "npm run build",
"test": "playwright test",
"lint": "npx eslint .",
"prettier": "prettier --config .prettierrc.json src/**/*.ts *.json --write"
},
"repository": {
"type": "git",
"url": "git+https://github.com/edwinm/carbonium.git"
},
"files": [
"src/carbonium.ts",
"dist/carbonium.d.ts",
"dist/bundle.min.js",
"dist/bundle.min.js.map",
"dist/bundle.iife.min.js",
"dist/bundle.iife.min.js.map"
],
"keywords": [
"front-end",
"dom",
"qsa",
"jquery",
"typescript",
"front-end",
"lightweight",
"micro"
],
"author": {
"name": "Edwin Martin",
"email": "edwin@bitstorm.org",
"url": "https://bitstorm.org/"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/edwinm/carbonium/issues"
},
"homepage": "https://github.com/edwinm/carbonium#readme",
"devDependencies": {
"@eslint/js": "^9.39.4",
"@playwright/test": "^1.59.1",
"@rollup/plugin-replace": "^6.0.3",
"@rollup/plugin-terser": "^1.0.0",
"@rollup/plugin-typescript": "^12.3.0",
"@types/node": "^22.19.17",
"@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0",
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"globals": "^17.4.0",
"http-server": "^14.1.1",
"husky": "^9.1.7",
"prettier": "^3.8.1",
"pretty-quick": "^4.2.2",
"rollup": "^4.60.1",
"tslib": "^2.8.1",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0"
}
}
================================================
FILE: playwright.config.ts
================================================
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
fullyParallel: true,
retries: 0,
reporter: [["html", { open: "never" }]],
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
],
});
================================================
FILE: rollup.config.js
================================================
import { createRequire } from "module";
import typescript from "@rollup/plugin-typescript";
import terser from "@rollup/plugin-terser";
import replace from "@rollup/plugin-replace";
const require = createRequire(import.meta.url);
const pkg = require("./package.json");
export default {
input: "src/carbonium.ts",
output: [
{
file: "dist/bundle.min.js",
format: "es",
name: "bundle",
sourcemap: true,
plugins: [terser()],
},
{
file: "dist/bundle.js",
format: "es",
name: "bundle",
sourcemap: true,
},
{
file: "dist/bundle.iife.min.js",
format: "iife",
name: "carbonium",
sourcemap: true,
plugins: [terser()],
},
],
plugins: [
replace({
preventAssignment: false,
__buildVersion__: pkg.version,
}),
typescript({ declarationDir: "dist" }),
],
};
================================================
FILE: src/carbonium.ts
================================================
/**
Carbonium __buildVersion__
@copyright 2020 Edwin Martin
@license MIT
*/
export function $(
selectors: string,
parentNode?: Document | ShadowRoot | HTMLElement
): CarboniumType {
let nodelist: NodeListOf;
// If the first parameter starts with "<", create a DOM node
if (selectors.startsWith("<")) {
nodelist = >(
([
new DOMParser().parseFromString(selectors, "text/html").body.firstChild,
])
);
} else {
// Else, do querySelectorAll
nodelist = (parentNode || document).querySelectorAll(selectors);
}
// Wrap it in a Proxy
return >(
(new Proxy>(nodelist, proxyHandler))
);
}
// Used by style, classList and relList
// When setting one of these, remember the elements to apply to
let currentListNodelist: NodeListOf;
let propList: "style" | "classList" | "relList";
const proxyHandler: ProxyHandler> = {
get(target, prop) {
let propValue = null;
// Return iterator when asked for iterator, only used in ES2015+
if (prop == Symbol.iterator) {
return function* () {
for (const element of target) {
yield element;
}
};
}
// Special case for style, classList and relList
if (prop == "style" || prop == "classList" || prop == "relList") {
currentListNodelist = target;
propList = prop;
// Matched elements can be a list of any element or an empty list
// Use getter of, for example, document.body.style
const propValue = Reflect.get(document.body, prop);
return new Proxy(propValue, proxyHandler);
}
// style.setProperty, getPropertyValue…, classList.add, contains, remove…, relList…
if (
target instanceof CSSStyleDeclaration ||
target instanceof DOMTokenList
) {
// Matched elements can be a list of any element or an empty list
// Use getter of, for example, document.body.style.color
propValue = Reflect.get((document.body as any)[propList], prop);
// When getter is a function, apply it to all matched elements
if (typeof propValue == "function") {
return new Proxy(propValue, {
apply: function (target, thisArg, argumentsList) {
currentListNodelist.forEach((el) => {
Reflect.apply(target, (el as any)[propList], argumentsList);
});
return new Proxy(currentListNodelist, proxyHandler);
},
});
} else {
return propValue;
}
}
// Are we dealing with an Array function like forEach, map and filter?
if (Array.prototype.hasOwnProperty(prop)) {
const propValue = Reflect.get(Array.prototype, prop);
if (typeof propValue == "function") {
return new Proxy(propValue, {
apply: function (target, thisArg, argumentsList) {
const ret = Reflect.apply(target, thisArg, argumentsList);
// When function returns undefined (like forEach),
// return all matched elements, so calls can be chained
// For example forEach(…).setAttribute(…)
const newTarget = typeof ret != "undefined" ? ret : thisArg;
return new Proxy(newTarget, proxyHandler);
},
});
}
}
// Get property or call function on DOM elements
if (target.length > 0) {
// Might be DOM element specific, like input.select(),
// so use first array element to get reference
if (prop in target[0]) {
propValue = Reflect.get(target[0], prop);
}
} else {
// Empty list, targeted DOM element unknown,
// use getter of document.body
if (prop in document.body) {
propValue = Reflect.get(document.body, prop);
}
}
// Propagate DOM prop value
if (propValue) {
if (typeof propValue == "function") {
return new Proxy(propValue, {
apply: function (target, thisArg, argumentsList) {
let retFirst = null;
let first = true;
// Apply on individual elements
for (const el of thisArg) {
const ret = Reflect.apply(target, el, argumentsList);
if (first) {
retFirst = ret;
first = false;
}
}
return retFirst ?? thisArg;
},
});
} else {
return propValue;
}
}
// Default
return Reflect.get(target, prop);
},
// DOM property is set
set(target, prop, value) {
if ("forEach" in target && !(target instanceof CSSStyleDeclaration)) {
target.forEach((el) => {
Reflect.set(el, prop, value);
});
} else {
Reflect.set(target, prop, value);
}
return true;
},
deleteProperty(target, prop) {
if (prop in target) {
return delete (target as any)[prop];
}
return false;
},
};
export type CarboniumType =
CarboniumList & T;
// Interface definitions
interface CarboniumList extends Array {
concat(...items: ConcatArray[]): CarboniumType;
concat(...items: (T | ConcatArray)[]): CarboniumType;
reverse(): CarboniumType;
slice(start?: number, end?: number): CarboniumType;
splice(start: number, deleteCount?: number): CarboniumType;
/* tslint:disable:unified-signatures */
splice(start: number, deleteCount: number, ...items: T[]): CarboniumType;
forEach(
callbackfn: (value: T, index: number, array: T[]) => void,
thisArg?: any
): CarboniumType;
filter(
callbackfn: (value: T, index: number, array: T[]) => boolean,
thisArg?: any
): CarboniumType;
setAttribute(qualifiedName: string, value: string): CarboniumType;
classList: CarboniumClassList;
style: CarboniumStyleList;
}
interface CarboniumClassList extends DOMTokenList {
add(...tokens: string[]): CarboniumType;
remove(...tokens: string[]): CarboniumType;
replace(oldToken: string, newToken: string): boolean;
forEach(
callbackfn: (value: string, key: number, parent: DOMTokenList) => void,
thisArg?: any
): CarboniumType;
}
interface CarboniumStyleList<
T extends HTMLElement,
> extends CSSStyleDeclaration {
removeProperty(property: string): CarboniumList & string;
setProperty(
property: string,
value: string | null,
priority?: string
): CarboniumType;
}
================================================
FILE: test/test.ts
================================================
import * as assert from "assert";
import { $, CarboniumType } from "../src/carbonium";
// TODO: this should work - find out why not
// import { $, CarboniumType } from "../";
import "@webcomponents/webcomponentsjs/custom-elements-es5-adapter";
/**
* Test framework used:
* Mocha https://mochajs.org/
* Assert https://nodejs.org/api/assert.html
*/
describe("$", () => {
beforeEach(() => {
document.body.textContent = "";
for (let i = 0; i < 6; i++) {
const div = document.createElement("div");
div.textContent = `item${i}`;
document.body.appendChild(div);
}
});
it("textContent one element", () => {
$("div:first-child").textContent = "hello";
const divs = document.getElementsByTagName("div");
assert.equal(divs[0].textContent, "hello");
});
it("textContent one element", () => {
const div: CarboniumType = $("div:first-child");
div.textContent = "hello";
const divs = document.getElementsByTagName("div");
assert.equal(divs[0].textContent, "hello");
});
it("textContent all elements", () => {
$("div").textContent = "hello";
assert.equal(document.body.textContent, "hellohellohellohellohellohello");
});
it("length", () => {
assert.equal($("div").length, 6);
});
it("forEach", () => {
const divs = $("div");
divs.forEach((div, i) => {
div.textContent = `div ${i}`;
});
assert.equal(divs[0].textContent, "div 0");
assert.equal(divs[5].textContent, "div 5");
});
it("for of", () => {
const divs = $("div");
let i = 0;
for (const div of divs) {
div.textContent = `div ${i++}`;
}
assert.equal(divs[0].textContent, "div 0");
assert.equal(divs[5].textContent, "div 5");
});
it("setAttribute all elements", () => {
$("div").setAttribute("aria-label", "List item");
const divs = document.getElementsByTagName("div");
assert.equal(divs[0].getAttribute("aria-label"), "List item");
assert.equal(divs[1].getAttribute("aria-label"), "List item");
assert.equal(divs[5].getAttribute("aria-label"), "List item");
});
it("filter", () => {
$("div").filter((el) => el.textContent == "item1").textContent = "hello";
assert.equal(document.body.textContent, "item0helloitem2item3item4item5");
});
it("class add method", () => {
$("div").classList.add("some-class");
const divs = document.getElementsByTagName("div");
assert.ok(divs[0].classList.contains("some-class"));
assert.ok(divs[5].classList.contains("some-class"));
});
it("rel add and contains method", () => {
const a = document.createElement("a");
a.relList.add("some-class");
assert.ok(a.relList.contains("some-class"));
});
it("class value property", () => {
$("div").classList.add("some-class");
const divs = document.getElementsByTagName("div");
assert.equal(divs[0].classList.value, "some-class");
});
it("class add method and textContent property", () => {
$("div:first-child").classList.add("some-class").textContent = "hello";
const divs = document.getElementsByTagName("div");
assert.ok(divs[0].classList.contains("some-class"));
assert.ok(!divs[5].classList.contains("some-class"));
assert.equal(divs[0].textContent, "hello");
assert.equal(divs[5].textContent, "item5");
});
it("filter and class add method and textContent property", () => {
$("div")
.filter((el) => el.textContent == "item0")
.classList.add("some-class").textContent = "hello";
const divs = document.getElementsByTagName("div");
assert.ok(divs[0].classList.contains("some-class"));
assert.ok(!divs[5].classList.contains("some-class"));
assert.equal(divs[0].textContent, "hello");
assert.equal(divs[5].textContent, "item5");
});
it("filter and style setProperty method and textContent property", () => {
$("div")
.filter((el) => el.textContent == "item0")
.style.setProperty("--leftmargin", "10px").textContent = "hello";
const divs = document.getElementsByTagName("div");
assert.equal(divs[0].style.getPropertyValue("--leftmargin"), "10px");
assert.equal(divs[5].style.getPropertyValue("--leftmargin"), "");
assert.equal(divs[0].textContent, "hello");
assert.equal(divs[5].textContent, "item5");
});
it("combined", () => {
$("div")
.forEach((el) => (el.title = `A div with content ${el.textContent}`))
.setAttribute("aria-label", "List item")
.filter((el) => el.textContent == "item1").textContent = "hello";
const divs = document.getElementsByTagName("div");
assert.equal(divs[0].getAttribute("aria-label"), "List item");
assert.equal(divs[5].getAttribute("aria-label"), "List item");
assert.equal(divs[0].getAttribute("title"), "A div with content item0");
assert.equal(divs[5].getAttribute("title"), "A div with content item5");
assert.equal(document.body.textContent, "item0helloitem2item3item4item5");
});
it("textContent empty list", () => {
assert.doesNotThrow(() => {
$("div.non-existent").textContent = "hello";
});
});
it("setAttribute empty list", () => {
assert.doesNotThrow(() => {
$("div.non-existent").setAttribute("aria-label", "List item");
});
});
it("call element specific function", () => {
const input = document.createElement("input");
document.querySelector("div:first-child").appendChild(input);
assert.doesNotThrow(() => {
$("input").select();
});
});
it("addEventListener", (done) => {
$("div:first-child").addEventListener("click", () => {
done();
});
$("div:first-child").click();
});
it("canvas", () => {
const canvas = document.createElement("canvas");
$("div:nth-child(1)").appendChild(canvas);
const ctx = $("canvas").getContext("2d", {
alpha: false,
});
ctx.fillRect(0, 0, 100, 100);
});
it("style set/get", () => {
$("div:nth-child(1)").style.color = "red";
assert.equal($("div:nth-child(1)").style.color, "red");
});
it("Parse HTML", () => {
const div$ = $("