Skip to content

Commit

Permalink
🐛 Reduce attribute handling complexity
Browse files Browse the repository at this point in the history
  • Loading branch information
NeoLegends committed Dec 27, 2017
1 parent 1d4e632 commit 20b8d7c
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 26 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fit-html",
"version": "0.3.4",
"version": "0.3.5",
"description": "5KB functional Web Components without bloat",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
63 changes: 38 additions & 25 deletions src/with-props.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import camelize from 'lodash-es/camelCase';
import kebapize from 'lodash-es/kebabCase';

import { FitElement } from './connect';

declare const process: any;

/**
* A value constructor.
*/
export type Constructor = (value: string) => any;

/**
* Attribute observer configuration with *camelized* attribute names.
* Attribute observer configuration.
*
* If a constructor function is present, it will be invoked when an
* attribute changes with the attribute's new value. If null is passed
* or a property is set the value will be left untouched.
*/
export interface AttributeDescriptors {
[key: string]: typeof String | typeof Number | typeof Boolean | typeof Object;
[key: string]: Constructor | null;
}

/**
* Attribute values with extracted property values.
*/
export type AttributeValues<A> = {
[key in keyof A]: string | number | boolean | object;
[key in keyof A]: any;
};

/**
Expand All @@ -22,18 +33,26 @@ export type AttributeValues<A> = {
* mapDispatchToProps will just be empty objects.
*
* @param {FitElement<S, P, OP>} Base The base 💪-element.
* @param {A} attributeDescriptors Attribute descriptors (with camelized attribute names) describing which attributes and properties to listen for changes on.
* @param {A} attributeDescriptors Attribute descriptors describing which attributes and properties to listen for changes on.
* @returns {FitElement<S, P, A>} A subclass of the given {@link Base} that listens for changes on the given properties and attributes.
* @template S, P, A, OP
*/
export default function withProps<S, P, A extends AttributeDescriptors>(
export default function withProps<S, P, OP, A extends AttributeDescriptors>(
Base: FitElement<S, P, AttributeValues<A>>,
attributeDescriptors: A
attributeDescriptors: A,
): FitElement<S, P, AttributeValues<A>> {
const observedAttrs = Object.keys(attributeDescriptors).map(kebapize);
if (process && process.env.NODE_ENV !== 'production') {
const hasCasedAttrs = Object.keys(attributeDescriptors)
.some(k => attributeDescriptors[k] !== null && /[A-Z]/.test(k));

if (hasCasedAttrs) {
console.warn("💪-html: DOM attribute changes cannot be detected for property names with uppercase letters. Use lowercase property names to fix this.");
}
}

const observedAttrs = Object.keys(attributeDescriptors);

return class extends Base {
private _attributeDescriptors: A = attributeDescriptors;
private _ownProps: AttributeValues<A> = {} as AttributeValues<A>;

static get observedAttributes(): string[] {
Expand All @@ -45,7 +64,7 @@ export default function withProps<S, P, A extends AttributeDescriptors>(

const obj = {};
for (const propName of observedAttrs) {
obj[propName] = obj[camelize(propName)] = {
obj[propName] = {
configurable: true,
enumerable: true,
get: () => this._ownProps[propName],
Expand All @@ -64,25 +83,19 @@ export default function withProps<S, P, A extends AttributeDescriptors>(
}

attributeChangedCallback(name: string, _: string, newValue: string) {
if (observedAttrs.indexOf(name) === -1) {
if (!(name in attributeDescriptors)) {
return;
}

const realName = camelize(name);
const type = this._attributeDescriptors[realName];
const type = attributeDescriptors[name];

let value;
if (type === Boolean) {
value = newValue !== null;
} else if (type === Number) {
value = Number(newValue);
} else if (type === String) {
value = String(newValue);
if (!type) {
this[name] = newValue;
} else if (type === Boolean) {
this[name] = newValue !== null;
} else {
value = newValue;
this[name] = type(newValue);
}

this[realName] = value;
}

getProps(): P {
Expand Down

0 comments on commit 20b8d7c

Please sign in to comment.