Skip to content

Commit

Permalink
feat(define): Hybrid constructor without definition in the registry
Browse files Browse the repository at this point in the history
  • Loading branch information
smalluban committed Aug 5, 2020
1 parent 7902270 commit c73b5af
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 34 deletions.
26 changes: 19 additions & 7 deletions docs/core-concepts/definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ To define custom element use `define` function. It takes a tag name and plain ob

To simplify using external custom elements with those created by the library, you can pass `constructor` instead of a plain object. Then `define` works exactly the same as the `customElements.define()` method.

### Single Element
## Single Element

```javascript
import { define } from 'hybrids';
Expand All @@ -18,16 +18,28 @@ define('my-element', MyElement);
```

```typescript
define(tagName: string, descriptorsOrConstructor: Object | Function): Wrapper
define(tagName: string | null, descriptorsOrConstructor: Object | Function): Wrapper
```

* **arguments**:
* `tagName` - a custom element tag name,
* `descriptorsOrConstructor` - an object with a map of hybrid property descriptors or constructor
* **returns**:
* `tagName` - a custom element tag name or `null`,
* `descriptorsOrConstructor` - an object with a map of hybrid property descriptors or a constructor
* **returns**:
* `Wrapper` - custom element constructor (extends `HTMLElement`)

### Map of Elements
If the `tagName` is set to `null`, the `define` function only generates a class constructor without registering it in the global custom elements registry. This mode might be helpful for creating a custom element for external usage without dependency on tag name, and where the `hybrids` library is not used directly.

```javascript
// in the package
const MyElement = { ... };
export default define(null, MyElement);

// in the client
import { MyElement } from "components-library";
customElements.define("my-super-element", MyElement);
```

## Map of Elements

```javascript
import { define } from 'hybrids';
Expand All @@ -45,4 +57,4 @@ define({ tagName: descriptorsOrConstructor, ... }): { tagName: Wrapper, ... }
* `tagName` - a custom element tag name in pascal case or camel case,
* `descriptorsOrConstructor` - an object with a map of hybrid property descriptors or constructor
* **returns**:
* `{ tagName: Wrapper, ...}` - a map of custom element constructors (extends `HTMLElement`)
* `{ tagName: Wrapper, ...}` - a map of custom element constructors (extends `HTMLElement`)
56 changes: 30 additions & 26 deletions src/define.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,40 +117,38 @@ function defineElement(tagName, hybridsOrConstructor) {
throw TypeError(`Second argument must be an object or a function: ${type}`);
}

const CustomElement = window.customElements.get(tagName);
if (tagName !== null) {
const CustomElement = window.customElements.get(tagName);

if (type === "function") {
if (CustomElement !== hybridsOrConstructor) {
return window.customElements.define(tagName, hybridsOrConstructor);
}
return CustomElement;
}

if (CustomElement) {
if (CustomElement.hybrids === hybridsOrConstructor) {
if (type === "function") {
if (CustomElement !== hybridsOrConstructor) {
return window.customElements.define(tagName, hybridsOrConstructor);
}
return CustomElement;
}
if (process.env.NODE_ENV !== "production" && CustomElement.hybrids) {
Object.keys(CustomElement.hybrids).forEach(key => {
delete CustomElement.prototype[key];
});

const lastHybrids = CustomElement.hybrids;
if (CustomElement) {
if (CustomElement.hybrids === hybridsOrConstructor) {
return CustomElement;
}
if (process.env.NODE_ENV !== "production" && CustomElement.hybrids) {
Object.keys(CustomElement.hybrids).forEach(key => {
delete CustomElement.prototype[key];
});

compile(CustomElement, hybridsOrConstructor);
update(CustomElement, lastHybrids);
const lastHybrids = CustomElement.hybrids;

return CustomElement;
}
compile(CustomElement, hybridsOrConstructor);
update(CustomElement, lastHybrids);

throw Error(`Element '${tagName}' already defined`);
}
return CustomElement;
}

class Hybrid extends HTMLElement {
static get name() {
return tagName;
throw Error(`Element '${tagName}' already defined`);
}
}

class Hybrid extends HTMLElement {
constructor() {
super();

Expand Down Expand Up @@ -187,7 +185,13 @@ function defineElement(tagName, hybridsOrConstructor) {
}

compile(Hybrid, hybridsOrConstructor);
customElements.define(tagName, Hybrid);

if (tagName !== null) {
Object.defineProperty(Hybrid, "name", {
get: () => tagName,
});
customElements.define(tagName, Hybrid);
}

return Hybrid;
}
Expand All @@ -202,7 +206,7 @@ function defineMap(elements) {
}

export default function define(...args) {
if (typeof args[0] === "object") {
if (typeof args[0] === "object" && args[0] !== null) {
return defineMap(args[0]);
}

Expand Down
16 changes: 16 additions & 0 deletions test/spec/define.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ describe("define:", () => {
expect(CustomElement.name).toBe("test-define-custom-element");
});

it("returns the constructor without defining in the registry", () => {
const MyElement = {
value: "test",
};

const Constructor = define(null, MyElement);

expect(Constructor.prototype.value).toBeDefined();
expect(define(null, MyElement)).not.toBe(Constructor);

customElements.define("test-define-from-constructor", Constructor);
const el = document.createElement("test-define-from-constructor");

expect(el.value).toBe("test");
});

describe("for map argument", () => {
it("defines hybrids", done => {
const testHtmlDefine = { value: "test" };
Expand Down
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ declare namespace hybrids {

/* Define */

function define<E extends HTMLElement>(tagName: string, hybridsOrConstructor: Hybrids<E> | typeof HTMLElement): HybridElement<E>;
function define<E extends HTMLElement>(tagName: string | null, hybridsOrConstructor: Hybrids<E> | typeof HTMLElement): HybridElement<E>;
function define(mapOfHybridsOrConstructors: MapOfHybridsOrConstructors): MapOfConstructors<typeof mapOfHybridsOrConstructors>;

/* Factories */
Expand Down

0 comments on commit c73b5af

Please sign in to comment.