{"id":19013970,"url":"https://github.com/pictogrammers/element","last_synced_at":"2025-04-13T08:44:17.737Z","repository":{"id":118032426,"uuid":"481429418","full_name":"Pictogrammers/Element","owner":"Pictogrammers","description":"@pictogrammers/element TypeScript Web Component","archived":false,"fork":false,"pushed_at":"2025-03-29T23:27:18.000Z","size":225,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-30T00:23:11.295Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Pictogrammers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-14T01:40:21.000Z","updated_at":"2025-03-29T23:27:21.000Z","dependencies_parsed_at":"2025-01-01T22:26:29.739Z","dependency_job_id":"8c042085-74dc-46a3-b245-17de1e704cde","html_url":"https://github.com/Pictogrammers/Element","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pictogrammers%2FElement","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pictogrammers%2FElement/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pictogrammers%2FElement/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pictogrammers%2FElement/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Pictogrammers","download_url":"https://codeload.github.com/Pictogrammers/Element/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248687811,"owners_count":21145761,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T19:26:51.151Z","updated_at":"2025-04-13T08:44:17.731Z","avatar_url":"https://github.com/Pictogrammers.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Element\n\nSimple TypeScript wrapper for creating a Web Component.\n\n```bash\nnpm install @pictogrammers/element\n```\n\nExample Usage: [Element-Hello-World](https://github.com/Pictogrammers/Element-Hello-World)\n\n## Basics\n\nTo make things easier setup the project assuming the custom element `\u003chello-world message=\"Hello World!\"\u003e\u003c/hello-world\u003e`.\n\n```\n📂 src/\n  📂 hello/\n    📂 world/\n      📃 world.ts\n      📃 world.html\n      📃 world.css\n📃 jest.config.json\n📃 package.json\n📃 tsconfig.json\n📃 webpack.config.js\n```\n\n### Class (`world.ts`)\n\n```typescript\nimport { Component, Prop, Part } from '@pictogrammers/element';\n\nimport template from \"./world.html\";\nimport style from './world.css';\n\n@Component({\n  selector: 'hello-world',\n  style,\n  template\n})\nexport default class HelloWorld extends HTMLElement {\n  @Prop() message = 'Hello World';\n  \n  @Part() $message: HTMLDivElement;\n  \n  render(changes) {\n    if (changes.message) {\n      this.$message.textContent = this.message;\n    }\n  }\n}\n```\n\n### Template (`world.html`)\n\n```html\n\u003cdiv part=\"message\"\u003eDefault!\u003c/div\u003e\n```\n\n### CSS Styles (`world.css`)\n\n```css\n:host {\n  display: block;\n}\n[part=message] {\n  /* Style Part */\n}\n```\n\n### Normalizing Props\n\nIt is recommended to use primitives for props where possible. To make this easier functions are provided to normalize values for booleans, integers, numbers, and strings.\n\n```typescript\nimport { Component, Prop, normalizeBoolean } from '@pictogrammers/element';\n// ...\n@Prop(normalizeBoolean) selected = false;\n```\n\nWhich is equivalent to...\n\n```typescript\nimport { Component, Prop, normalizeBoolean } from '@pictogrammers/element';\n// ...\n#selected = false;\n@Prop()\nget selected() {\n  return this.#selected;\n}\nset selected(value: string | boolean) {\n  this.#selected = normalizeBoolean(value);\n}\n```\n\n\u003e **Note:** Instead of ever using `get` / `set` always use the `render` method for managing changes to prevent unncessary operations.\n\n- `normalizeInt` - Wrapper for ``parseInt(`${value}`, 10)``.\n- `normalizeFloat` - Wrapper for ``parseFloat(`${value}`)``.\n- `normalizeBoolean` - Handles `bool` type including string `'true'` / `'false'`.\n- `normalizeString` - Wrapper for `` `${value}` ``.\n\n### Template Loops\n\nComponents can create repeated lists of other components by using the `forEach` utility. A unique `key` property is required in each item of the items array (defaults to a uuid if not provided). Any updates will sync values to the component provided in the type function.\n\n\u003e **Note:** `item` in the callbacks is readonly and contains `index`.\n\n```typescript\nimport { forEach } from '@pictogrammers/element';\n\nimport UiItem from 'ui/item';\n\n// ... in element class\n\n  // Public\n  @Prop() options: any[] = [];\n  // Private\n  @Prop() #options: any[] = [];\n\n  connectedCallback() {\n    forEach({\n      container: this.$items,\n      items: this.options,\n      type: (item) =\u003e {\n        return UiItem;\n      },\n      create: ($item, item) =\u003e {\n        // after creation of $item element\n      },\n      connect: ($item, item, $items) =\u003e {\n        // after connectedCallback\n      },\n      disconnect: ($item, item, $items) =\u003e {\n        // before disconnectedCallback\n      },\n      update: ($item, item, $items) =\u003e {\n        // after every $item update\n      },\n    });\n  }\n```\n\n## Advanced\n\nStarting with a simple component can allow one to extend it with more features later on. This can be done by extending components.\n\n```\n📂 src/\n  📂 hello/\n    📂 world/\n      📃 world.ts\n      📃 world.html\n      📃 world.css\n    📂 worldButton/\n      📃 worldButton.ts\n      📃 worldButton.html\n      📃 worldButton.css\n```\n\n### TypeScript (`worldButton.ts`)\n\n```typescript\nimport { Component, Prop, Part } from '@pictogrammers/element';\nimport HelloWorld from '../world/world';\n\nimport style from './worldButton.css';\nimport template from './worldButton.html';\n\n@Component({\n  selector: 'hello-world-button',\n  style,\n  template\n})\nexport default class HelloWorldButton extends HelloWorld {\n  @Part() $button: HTMLButtonElement;\n\n  renderCallback() {\n    this.$button.addEventListener('click', () =\u003e {\n      alert(this.message);\n    });\n  }\n}\n```\n\n### Template (`worldButton.html`)\n\n```html\n\u003cbutton part=\"button\"\u003e\n  \u003cparent/\u003e \u003c!-- \u003cdiv\u003eDefault!\u003c/div\u003e --\u003e\n\u003c/button\u003e\n```\n\n### CSS Styles (`worldButton.css`)\n\n```css\n[part=button] {\n  border-radius: 0.25rem;\n  border: #ddd;\n  color: #222;\n}\n```\n\n### `@Local(key: string)`\n\nTo access `localStorage` values bind them to a class level property with a `Map` type.\n\n```js\n// store:toggle\n@Local('store') store = new Map([\n  ['toggle', false]\n]);\n// Caches to a private property\n@Local('store') #store = new Map([\n  ['someobj', null]\n]);\n\n// somehere in your code\nthis.store.get('toggle');\nthis.store.set('toggle' true);\n```\n\n## Development\n\n```\n# Build\nnpm run build\n# View files in dist/\n# Then link for use locally\nnpm link\n# Within a local project directory\nnpm link @pictogrammers/element\n```\n\n## Utility Base Class\n\nSome other notes about unique use cases that are handled.\n\n### Optional `Component()` Config\n\nUtility base classes can be defined without a config. These are rarely used, but are supported.\n\n```typescript\nimport { Component } from '@pictogrammers/element';\n\n@Component()\nexport default class HelloOverlay extends HtmlElement {\n  static open() {\n\n  }\n\n  close() {\n\n  }\n}\n```\n\n## Jest Utils\n\n- `selectComponent\u003cT\u003e(tag: string): T`\n- `selectPart\u003cT\u003e(component: HTMLElement, name: string): T`\n- `getProps(tag: string): string[]`\n\n### Basic\n\n```typescript\nimport { selectComponent, getProps } from '@pictogrammers/element';\n\nimport './world';\nimport HelloWorld from './world';\n\nconst HELLO_WORLD = 'hello-world';\n\ndescribe('hello-world', () =\u003e {\n\n  const DEFAULT_MESSAGE = 'None';\n\n  beforeEach(() =\u003e {\n    var c = document.createElement(HELLO_WORLD);\n    document.body.appendChild(c);\n  });\n\n  afterEach(() =\u003e {\n    while (document.body.firstChild) {\n      document.body.removeChild(document.body.firstChild);\n    }\n  });\n\n  it('should be registered', () =\u003e {\n    expect(customElements.get(HELLO_WORLD)).toBeDefined();\n  });\n\n  it('should only expose known props', () =\u003e {\n    const props = getProps(HELLO_WORLD);\n    expect(props.length).toBe(2);\n    expect(props).toContain('message');\n    expect(props).toContain('count');\n  });\n\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpictogrammers%2Felement","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpictogrammers%2Felement","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpictogrammers%2Felement/lists"}