Skip to content
This repository has been archived by the owner on Apr 20, 2024. It is now read-only.

1.0 #22

Merged
merged 30 commits into from
Nov 17, 2023
Merged

1.0 #22

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e8a64de
#20 use decorator factories everywhere (#21)
athopen Oct 29, 2023
24f649e
upgrade packages
towapenz Nov 6, 2023
8a7a0cd
switch to node 20 in github workflows
towapenz Nov 6, 2023
fc66d92
update tsconfig
towapenz Nov 6, 2023
b772d6f
migrate targetable to standard decorators
towapenz Nov 6, 2023
d882d17
migrate actionable to standard decorators
towapenz Nov 6, 2023
282faf4
remove targetable decorator and refactor target and targets decorators
towapenz Nov 8, 2023
6df2339
refactor target and targets
towapenz Nov 8, 2023
cd081af
refactor attributable
towapenz Nov 8, 2023
cd3e6e7
migrate attributable to standard decorators
towapenz Nov 8, 2023
31effbe
support attribute decorators for setters
towapenz Nov 8, 2023
c195588
improve targetable decorator
towapenz Nov 8, 2023
b599e45
refactor and rename
towapenz Nov 8, 2023
4aef833
rename parameterize to kebabCase
towapenz Nov 8, 2023
2b42f48
improve eslint config
towapenz Nov 8, 2023
0217178
add controllable decorator
towapenz Nov 9, 2023
ba57462
simplify attribute metadata
towapenz Nov 9, 2023
6aec485
use custom accessor to set initial values
towapenz Nov 9, 2023
3797c02
call underlying getters and setters if available
towapenz Nov 9, 2023
63a859d
refactor attributable
towapenz Nov 10, 2023
cceaf71
add mutation observer to attributable
towapenz Nov 10, 2023
56ea314
remove redundant checks
towapenz Nov 10, 2023
329732f
observe attributes in controllable
towapenz Nov 11, 2023
e66d405
use context metadata in targetable
towapenz Nov 11, 2023
3e6ebc8
use context metadata in attributable
towapenz Nov 11, 2023
268df04
add registrable decorator
towapenz Nov 13, 2023
957a5d7
export initializers and observers
towapenz Nov 13, 2023
46b9d79
export additional initializers
towapenz Nov 14, 2023
b709ec8
extend exports
towapenz Nov 16, 2023
d795cda
extend exports
towapenz Nov 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"license-header"
],
"rules": {
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"varsIgnorePattern": "_"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
registry-url: https://registry.npmjs.org/
cache: npm

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
cache: npm

- run: npm ci
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16.0.0
v18.0.0
7,673 changes: 5,010 additions & 2,663 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 16 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,22 @@
"prepack": "npm run build"
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.22.5",
"@babel/plugin-transform-runtime": "^7.22.5",
"@babel/plugin-transform-typescript": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@open-wc/testing": "^3.2.0",
"@rollup/plugin-babel": "^6.0.3",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.60.0",
"@web/dev-server-rollup": "^0.5.1",
"@web/test-runner": "^0.16.1",
"eslint": "^8.43.0",
"eslint-plugin-import": "^2.27.5",
"@babel/plugin-proposal-decorators": "^7.23.2",
"@babel/plugin-transform-runtime": "^7.23.2",
"@babel/plugin-transform-typescript": "^7.22.15",
"@babel/preset-typescript": "^7.23.2",
"@open-wc/testing": "^4.0.0",
"@rollup/plugin-babel": "^6.0.4",
"@types/jest": "^29.5.7",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@web/dev-server-rollup": "^0.6.0",
"@web/test-runner": "^0.18.0",
"eslint": "^8.53.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-license-header": "^0.6.0",
"prettier": "2.8.8",
"sinon": "^15.2.0",
"typescript": "<5.1.0"
"prettier": "^3.0.3",
"sinon": "^17.0.1",
"typescript": "^5.2.2"
}
}
65 changes: 36 additions & 29 deletions src/actionable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const handleEvent = (event: Event) => {
continue;
}

type EventDispatcher = HTMLElement & Record<string, (ev: Event) => unknown>;
type EventDispatcher = Component & Record<string, (ev: Event) => unknown>;
const component = element.closest<EventDispatcher>(action.component);
if (!component || typeof component[action.method] !== 'function') {
continue;
Expand All @@ -58,29 +58,36 @@ const handleEvent = (event: Event) => {
}
};

const bindElements = (component: Component, root: Element) => {
for (const element of root.querySelectorAll(`[${component.tagName.toLowerCase()}-action]`)) {
export const initializeActionable = (component: Component, root: Element) => {
const componentTagName = component.tagName.toLowerCase();
for (const element of root.querySelectorAll(`[${componentTagName}-action]`)) {
bindActions(component, element);
}

if (root instanceof Element && root.hasAttribute(`${component.tagName.toLowerCase()}-action`)) {
if (root instanceof Element && root.hasAttribute(`${componentTagName}-action`)) {
bindActions(component, root);
}
};

const observeElements = (component: Component) => {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.target instanceof Element) {
bindElements(component, mutation.target);
} else if (mutation.type === 'childList' && mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
if (node instanceof Element) {
bindElements(component, node);
}
export const observeActionable = (component: Component) => {
const handleMutation = (mutation: MutationRecord) => {
const { type, target, addedNodes } = mutation;

if (type === 'attributes' && target instanceof Element) {
initializeActionable(component, target);
} else if (type === 'childList' && addedNodes.length) {
for (const node of addedNodes) {
if (node instanceof Element) {
initializeActionable(component, node);
}
}
}
};

const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
handleMutation(mutation);
}
});

observer.observe(component, {
Expand All @@ -90,22 +97,22 @@ const observeElements = (component: Component) => {
});
};

const initializeActionable = (component: Component): void => {
bindElements(component, component);
observeElements(component);
type ActionableDecorator<C extends ComponentConstructor> = {
(target: ComponentConstructor, context: ClassDecoratorContext<C>): any;
};

export function actionable(...args: any[]): any {
const [component, context] = args as [ComponentConstructor, ClassDecoratorContext];

if (context.kind !== 'class') {
throw new TypeError('The @actionable decorator is for use on classes only.');
}
export const actionable = <C extends ComponentConstructor>(): ActionableDecorator<C> => {
return (target) => {
return class extends target {
constructor(...args: any[]) {
super(args);
initializeActionable(this, this);
}

return class extends component {
mountCallback() {
initializeActionable(this);
super.mountCallback();
}
connectedCallback() {
super.connectedCallback?.();
observeActionable(this);
}
};
};
}
};
Loading