├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE ├── labels.json └── workflows │ ├── codeql-analysis.yml │ └── set-default-labels.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composed-composed-path ├── index.html └── main.js ├── defined-pseudo-class └── index.html ├── edit-word ├── index.html └── main.js ├── editable-list ├── index.html ├── main.js └── style.css ├── element-details ├── index.html └── main.js ├── expanding-list-web-component ├── img │ ├── down.png │ └── right.png ├── index.html └── main.js ├── host-selectors ├── index.html ├── main.js └── styles.css ├── life-cycle-callbacks ├── index.html └── main.js ├── popup-info-box-external-stylesheet ├── img │ ├── alt.png │ └── default.png ├── index.html ├── main.js └── style.css ├── popup-info-box-web-component ├── img │ ├── alt.png │ └── default.png ├── index.html └── main.js ├── shadow-part ├── index.html ├── main.js └── style.css ├── simple-template ├── index.html └── main.js ├── slotchange ├── index.html └── main.js ├── slotted-pseudo-element ├── index.html └── main.js └── word-count-web-component ├── index.html └── main.js /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file is used to request PR reviews from the appropriate team. 2 | 3 | # Default 4 | * @mdn/core-yari-content 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 2 | #### Summary 3 | 4 | 5 | #### Supporting details 6 | 7 | 8 | #### Related issues 9 | 10 | 11 | #### Related content pull request 12 | 13 | -------------------------------------------------------------------------------- /.github/labels.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "good first issue", 4 | "color": "028c46", 5 | "description": "A good issue for newcomers to get started with." 6 | }, 7 | { 8 | "name": "help wanted", 9 | "color": "028c46", 10 | "description": "If you know something about this, we would love your help!" 11 | }, 12 | { 13 | "name": "needs info", 14 | "color": "028c46", 15 | "description": "This needs more information to review or act on." 16 | }, 17 | { 18 | "name": "needs triage", 19 | "color": "028c46", 20 | "description": "Triage needed by staff and/or partners. Automatically applied when an issue is opened." 21 | }, 22 | { 23 | "name": "expert help needed", 24 | "color": "028c46", 25 | "description": "This needs more information from a subject matter expert (SME)." 26 | }, 27 | { 28 | "name": "idle", 29 | "color": "028c46", 30 | "description": "Issues and pull requests with no activity for three months." 31 | }, 32 | { 33 | "name": "on hold", 34 | "color": "028c46", 35 | "description": "Waiting on something else before this can be moved forward." 36 | }, 37 | { 38 | "name": "for later", 39 | "color": "028c46", 40 | "description": "Not planned at this time." 41 | }, 42 | { 43 | "name": "needs content update", 44 | "color": "028c46", 45 | "description": "Needs update to the content to support this change." 46 | }, 47 | { 48 | "name": "chore", 49 | "color": "028c46", 50 | "description": "A routine task." 51 | }, 52 | { 53 | "name": "enhancement", 54 | "color": "028c46", 55 | "description": "Improves an existing feature." 56 | }, 57 | { 58 | "name": "bug", 59 | "color": "c05964", 60 | "description": "Indicates an unexpected problem or unintended behavior." 61 | }, 62 | { 63 | "name": "wontfix", 64 | "color": "c05964", 65 | "description": "Deemed to be outside the scope of the project or would require significant time and resources to fix." 66 | }, 67 | { 68 | "name": "effort: small", 69 | "color": "866dc1", 70 | "description": "Task is a small effort." 71 | }, 72 | { 73 | "name": "effort: medium", 74 | "color": "866dc1", 75 | "description": "Task is a medium effort." 76 | }, 77 | { 78 | "name": "effort: large", 79 | "color": "866dc1", 80 | "description": "Task is large effort." 81 | }, 82 | { 83 | "name": "p0", 84 | "color": "6e8bc1", 85 | "description": "Urgent. We will address this as soon as possible." 86 | }, 87 | { 88 | "name": "p1", 89 | "color": "6e8bc1", 90 | "description": "We will address this soon and will provide capacity from our team for it in the next few releases." 91 | }, 92 | { 93 | "name": "p2", 94 | "color": "6e8bc1", 95 | "description": "We want to address this but may have other higher priority items." 96 | }, 97 | { 98 | "name": "p3", 99 | "color": "6e8bc1", 100 | "description": "We don't have visibility when this will be addressed." 101 | } 102 | ] 103 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ main ] 9 | schedule: 10 | - cron: '42 14 * * 3' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'javascript' ] 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v3 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v2 33 | with: 34 | languages: ${{ matrix.language }} 35 | 36 | - name: Perform CodeQL Analysis 37 | uses: github/codeql-action/analyze@v2 38 | -------------------------------------------------------------------------------- /.github/workflows/set-default-labels.yml: -------------------------------------------------------------------------------- 1 | name: set-default-labels 2 | on: [workflow_dispatch] 3 | 4 | jobs: 5 | set-default-labels: 6 | uses: mdn/workflows/.github/workflows/set-default-labels.yml@main 7 | with: 8 | target-repo: "mdn/web-components-examples" 9 | should-delete-labels: true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our [How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | Welcome to the contributing guide for [mdn/web-components-examples](https://github.com/mdn/web-components-examples). 4 | 5 | Thank you for your interest in contributing to this repository. By contributing, you agree to abide by our [community guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/). 6 | 7 | To contribute, please open a pull request. Please make sure any updates reflect the latest version of the relevant specification(s). 8 | 9 | Please note that this repository only contains examples that are documented on MDN Web Docs. For pull requests that update code and/or demo examples (such as in this repository), we require an accompanying pull request on the [mdn/content repository](https://github.com/mdn/content) updating the relevant documentation. 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-components-examples 2 | A series of Web Components examples, related to the MDN Web Components documentation at https://developer.mozilla.org/en-US/docs/Web/API/Web_components. 3 | 4 | > Please refer to our [contribution guidelines](https://github.com/mdn/web-components-examples/blob/main/CONTRIBUTING.md) before contributing. 5 | 6 | The following examples are available: 7 | 8 | * [composed-composed-path](composed-composed-path). A very simple example that shows the behavior of the Event object composed and composedPath properties. [See composed-composed-path live](https://mdn.github.io/web-components-examples/composed-composed-path/). 9 | * [defined-pseudo-class](defined-pseudo-class). A very simple example that shows how the [:defined pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:defined) works. [See defined-pseudo-class live](https://mdn.github.io/web-components-examples/defined-pseudo-class/). 10 | * [editable-list](editable-list) – <editable-list>. A simple example showing how elements can be consolidated to create a list with addable/removable items. Items are added by using a `list-item` attribute or by entering text and clicking the plus sign. [See editable-list live](https://mdn.github.io/web-components-examples/editable-list/). 11 | * [edit-word](edit-word) — <edit-word>. Wrapping one or more words in this element means that you can then click/focus the element to reveal a text input that can then be used to edit the word(s). [See edit-word live](https://mdn.github.io/web-components-examples/edit-word/). 12 | * [element-details](element-details) — <element-details>. Displays a box containing an HTML element name and description. Provides an example of an autonomous custom element that gets its structure from a <template> element (that also has its own styling defined), and also contains <slot> elements populated at runtime. [See element-details live](https://mdn.github.io/web-components-examples/element-details/). 13 | * [expanding-list-web-component](expanding-list-web-component) — <ul is="expanding-list">. Creates an unordered list with expandable/collapsible children. Provides an example of a customized built-in element (the class inherits from HTMLUListElement rather than HTMLElement). [See expanding-list live](https://mdn.github.io/web-components-examples/expanding-list-web-component/). 14 | * [life-cycle-callbacks](life-cycle-callbacks) — <custom-square l="" c="">. A trivial example web component that creates a square colored box on the page. The demo also includes buttons to create, destroy, and change attributes on the element, to demonstrate how the [web components life cycle callbacks](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks) work [See life-cycle-callbacks live](https://mdn.github.io/web-components-examples/life-cycle-callbacks/). 15 | * [popup-info-box-web-component](popup-info-box-web-component) — <popup-info img="" text="">. Creates an info icon that when focused displays a popup info box. Provides an example of an autonomous custom element that takes information from its attributes, and defines structure and basic style in an attached shadow DOM. [See popup-info-box live](https://mdn.github.io/web-components-examples/popup-info-box-web-component/). 16 | * [simple-template](simple-template) — A very simple trivial example that quickly demonstrates usage of the <template> and <slot> elements. [See simple-template live](https://mdn.github.io/web-components-examples/simple-template/). 17 | * [slotchange example](slotchange) — <summary-display>. An example that takes as its two slot values a list of possible choices, and a description for the selected choice. Multiple paragraphs are included inside the element containing all the possible descriptions; when a choice is clicked, its corresponding description paragraph is given an appropriate slot attribute so that it appears in the second slot. This example is written to demonstrate usage of the slotchange attribute, and features of the HTMLSlotElement interface [See the slotchange example live](https://mdn.github.io/web-components-examples/slotchange). 18 | * [slotted-pseudo-element](slotted-pseudo-element). A very simple example that shows how the ::slotted pseudo-element works. [See slotted-pseudo-element live](https://mdn.github.io/web-components-examples/slotted-pseudo-element/). 19 | * [word-count-web-component](word-count-web-component) — <word-count>. When added to an element, counts all the words inside that element and displays them inside an attached shadow DOM. It also contains an interval that periodically updates the word count as it changes. Provides an example of a customized built-in element (the class inherits from HTMLParagraphElement rather than HTMLElement). [See word-count live](https://mdn.github.io/web-components-examples/word-count-web-component/). 20 | -------------------------------------------------------------------------------- /composed-composed-path/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | composed and composedPath demo 6 | 7 | 8 | 9 |

composed and composedPath demo

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /composed-composed-path/main.js: -------------------------------------------------------------------------------- 1 | customElements.define('open-shadow', 2 | class extends HTMLElement { 3 | constructor() { 4 | super(); 5 | 6 | const pElem = document.createElement('p'); 7 | pElem.textContent = this.getAttribute('text'); 8 | 9 | const shadowRoot = this.attachShadow({mode: 'open'}); 10 | shadowRoot.appendChild(pElem); 11 | } 12 | } 13 | ); 14 | 15 | customElements.define('closed-shadow', 16 | class extends HTMLElement { 17 | constructor() { 18 | super(); 19 | 20 | const pElem = document.createElement('p'); 21 | pElem.textContent = this.getAttribute('text'); 22 | 23 | const shadowRoot = this.attachShadow({mode: 'closed'}); 24 | shadowRoot.appendChild(pElem); 25 | } 26 | } 27 | ); 28 | 29 | document.querySelector('html').addEventListener('click', e => { 30 | console.log(e.composed); 31 | console.log(e.composedPath()); 32 | }); 33 | -------------------------------------------------------------------------------- /defined-pseudo-class/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | :defined demo 6 | 55 | 56 | 57 | 58 | 59 |

60 | Loaded content: Lorem ipsum tel sed tellus eiusmod tellus. Aenean. 61 | Semper dolor sit nisi. Elit porttitor nisi sit vivamus. 62 |

63 |
64 | 65 | 66 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /edit-word/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | edit-word demo 6 | 7 | 8 | 9 |

edit-word demo

10 | 11 | 21 | 22 | 23 |

Morgan Stanley

24 | 36 25 | Accountant 26 |
27 | 28 |

My name is Chris, the man said.

29 | 30 | 31 | -------------------------------------------------------------------------------- /edit-word/main.js: -------------------------------------------------------------------------------- 1 | customElements.define('person-details', 2 | class extends HTMLElement { 3 | constructor() { 4 | super(); 5 | 6 | const template = document.getElementById('person-template'); 7 | const templateContent = template.content; 8 | 9 | const shadowRoot = this.attachShadow({mode: 'open'}); 10 | 11 | const style = document.createElement('style'); 12 | style.textContent = ` 13 | div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; } 14 | h2 { margin: 0 0 10px; } 15 | ul { margin: 0; } 16 | p { margin: 10px 0; } 17 | `; 18 | 19 | shadowRoot.appendChild(style); 20 | shadowRoot.appendChild(templateContent.cloneNode(true)); 21 | } 22 | } 23 | ); 24 | 25 | customElements.define('edit-word', 26 | class extends HTMLElement { 27 | constructor() { 28 | super(); 29 | 30 | const shadowRoot = this.attachShadow({mode: 'open'}); 31 | const form = document.createElement('form'); 32 | const input = document.createElement('input'); 33 | const span = document.createElement('span'); 34 | 35 | const style = document.createElement('style'); 36 | style.textContent = 'span { background-color: #eef; padding: 0 2px }'; 37 | 38 | shadowRoot.appendChild(style); 39 | shadowRoot.appendChild(form); 40 | shadowRoot.appendChild(span); 41 | 42 | span.textContent = this.textContent; 43 | input.value = this.textContent; 44 | 45 | form.appendChild(input); 46 | form.style.display = 'none'; 47 | span.style.display = 'inline-block'; 48 | input.style.width = span.clientWidth + 'px'; 49 | 50 | this.setAttribute('tabindex', '0'); 51 | input.setAttribute('required', 'required'); 52 | this.style.display = 'inline-block'; 53 | 54 | this.addEventListener('click', () => { 55 | span.style.display = 'none'; 56 | form.style.display = 'inline-block'; 57 | input.focus(); 58 | input.setSelectionRange(0, input.value.length) 59 | }); 60 | 61 | form.addEventListener('submit', e => { 62 | updateDisplay(); 63 | e.preventDefault(); 64 | }); 65 | 66 | input.addEventListener('blur', updateDisplay); 67 | 68 | function updateDisplay() { 69 | span.style.display = 'inline-block'; 70 | form.style.display = 'none'; 71 | span.textContent = input.value; 72 | input.style.width = span.clientWidth + 'px'; 73 | } 74 | } 75 | } 76 | ); 77 | -------------------------------------------------------------------------------- /editable-list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Editable List | Web Components 6 | 7 | 8 | 9 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /editable-list/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | class EditableList extends HTMLElement { 5 | constructor() { 6 | // establish prototype chain 7 | super(); 8 | 9 | // attaches shadow tree and returns shadow root reference 10 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow 11 | const shadow = this.attachShadow({ mode: 'open' }); 12 | 13 | // creating a container for the editable-list component 14 | const editableListContainer = document.createElement('div'); 15 | 16 | // get attribute values from getters 17 | const title = this.title; 18 | const addItemText = this.addItemText; 19 | const listItems = this.items; 20 | 21 | // adding a class to our container for the sake of clarity 22 | editableListContainer.classList.add('editable-list'); 23 | 24 | // creating the inner HTML of the editable list element 25 | editableListContainer.innerHTML = ` 26 | 41 |

${title}

42 | 49 |
50 | 51 | 52 | 53 |
54 | `; 55 | 56 | // binding methods 57 | this.addListItem = this.addListItem.bind(this); 58 | this.handleRemoveItemListeners = this.handleRemoveItemListeners.bind(this); 59 | this.removeListItem = this.removeListItem.bind(this); 60 | 61 | // appending the container to the shadow DOM 62 | shadow.appendChild(editableListContainer); 63 | } 64 | 65 | // add items to the list 66 | addListItem(e) { 67 | const textInput = this.shadowRoot.querySelector('.add-new-list-item-input'); 68 | 69 | if (textInput.value) { 70 | const li = document.createElement('li'); 71 | const button = document.createElement('button'); 72 | const childrenLength = this.itemList.children.length; 73 | 74 | li.textContent = textInput.value; 75 | button.classList.add('editable-list-remove-item', 'icon'); 76 | button.innerHTML = '⊖'; 77 | 78 | this.itemList.appendChild(li); 79 | this.itemList.children[childrenLength].appendChild(button); 80 | 81 | this.handleRemoveItemListeners([button]); 82 | 83 | textInput.value = ''; 84 | } 85 | } 86 | 87 | // fires after the element has been attached to the DOM 88 | connectedCallback() { 89 | const removeElementButtons = [...this.shadowRoot.querySelectorAll('.editable-list-remove-item')]; 90 | const addElementButton = this.shadowRoot.querySelector('.editable-list-add-item'); 91 | 92 | this.itemList = this.shadowRoot.querySelector('.item-list'); 93 | 94 | this.handleRemoveItemListeners(removeElementButtons); 95 | addElementButton.addEventListener('click', this.addListItem, false); 96 | } 97 | 98 | // gathering data from element attributes 99 | get title() { 100 | return this.getAttribute('title') || ''; 101 | } 102 | 103 | get items() { 104 | const items = []; 105 | 106 | [...this.attributes].forEach(attr => { 107 | if (attr.name.includes('list-item')) { 108 | items.push(attr.value); 109 | } 110 | }); 111 | 112 | return items; 113 | } 114 | 115 | get addItemText() { 116 | return this.getAttribute('add-item-text') || ''; 117 | } 118 | 119 | handleRemoveItemListeners(arrayOfElements) { 120 | arrayOfElements.forEach(element => { 121 | element.addEventListener('click', this.removeListItem, false); 122 | }); 123 | } 124 | 125 | removeListItem(e) { 126 | e.target.parentNode.remove(); 127 | } 128 | } 129 | 130 | // let the browser know about the custom element 131 | customElements.define('editable-list', EditableList); 132 | })(); 133 | -------------------------------------------------------------------------------- /editable-list/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 90%; 3 | } 4 | 5 | body { 6 | color: #2b2b2b; 7 | font-family: sans-serif; 8 | margin: 0 auto; 9 | max-width: 350px; 10 | padding-top: 5rem; 11 | } 12 | -------------------------------------------------------------------------------- /element-details/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | element-details - web component using <template> and <slot> 6 | 12 | 13 | 14 |

element-details - web component using <template> and <slot>

15 | 16 | 41 | 42 | 43 | slot 44 | A placeholder inside a web 45 | component that users can fill with their own markup, 46 | with the effect of composing different DOM trees 47 | together. 48 |
49 |
name
50 |
The name of the slot.
51 |
52 |
53 | 54 | 55 | template 56 | A mechanism for holding client- 57 | side content that is not to be rendered when a page is 58 | loaded but may subsequently be instantiated during 59 | runtime using JavaScript. 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /element-details/main.js: -------------------------------------------------------------------------------- 1 | customElements.define('element-details', 2 | class extends HTMLElement { 3 | constructor() { 4 | super(); 5 | const template = document 6 | .getElementById('element-details-template') 7 | .content; 8 | const shadowRoot = this.attachShadow({mode: 'open'}) 9 | .appendChild(template.cloneNode(true)); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /expanding-list-web-component/img/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/web-components-examples/16c91cd0486afdd6407c2d606742a7fbf54c79f2/expanding-list-web-component/img/down.png -------------------------------------------------------------------------------- /expanding-list-web-component/img/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/web-components-examples/16c91cd0486afdd6407c2d606742a7fbf54c79f2/expanding-list-web-component/img/right.png -------------------------------------------------------------------------------- /expanding-list-web-component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Expanding list web component 6 | 40 | 41 | 42 | 43 |

Expanding list web component

44 | 81 | 82 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /expanding-list-web-component/main.js: -------------------------------------------------------------------------------- 1 | // Create a class for the element 2 | class ExpandingList extends HTMLUListElement { 3 | constructor() { 4 | // Always call super first in constructor 5 | // Return value from super() is a reference to this element 6 | self = super(); 7 | } 8 | 9 | connectedCallback() { 10 | // Get ul and li elements that are a child of this custom ul element 11 | // li elements can be containers if they have uls within them 12 | const uls = Array.from(self.querySelectorAll("ul")); 13 | const lis = Array.from(self.querySelectorAll("li")); 14 | // Hide all child uls 15 | // These lists will be shown when the user clicks a higher level container 16 | uls.forEach((ul) => { 17 | ul.style.display = "none"; 18 | }); 19 | 20 | // Look through each li element in the ul 21 | lis.forEach((li) => { 22 | // If this li has a ul as a child, decorate it and add a click handler 23 | if (li.querySelectorAll("ul").length > 0) { 24 | // Add an attribute which can be used by the style 25 | // to show an open or closed icon 26 | li.setAttribute("class", "closed"); 27 | 28 | // Wrap the li element's text in a new span element 29 | // so we can assign style and event handlers to the span 30 | const childText = li.childNodes[0]; 31 | const newSpan = document.createElement("span"); 32 | 33 | // Copy text from li to span, set cursor style 34 | newSpan.textContent = childText.textContent; 35 | newSpan.style.cursor = "pointer"; 36 | 37 | // Add click handler to this span 38 | newSpan.addEventListener("click", (e) => { 39 | // next sibling to the span should be the ul 40 | const nextul = e.target.nextElementSibling; 41 | 42 | // Toggle visible state and update class attribute on ul 43 | if (nextul.style.display == "block") { 44 | nextul.style.display = "none"; 45 | nextul.parentNode.setAttribute("class", "closed"); 46 | } else { 47 | nextul.style.display = "block"; 48 | nextul.parentNode.setAttribute("class", "open"); 49 | } 50 | }); 51 | // Add the span and remove the bare text node from the li 52 | childText.parentNode.insertBefore(newSpan, childText); 53 | childText.parentNode.removeChild(childText); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | // Define the new element 60 | customElements.define("expanding-list", ExpandingList, { extends: "ul" }); 61 | -------------------------------------------------------------------------------- /host-selectors/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Host selectors 6 | 7 | 8 | 9 | 10 |
11 |

Host selectors example

12 |
13 |
14 |
15 |

This is my first article.

16 | 17 |

This article is rather lovely and exciting — it is all about animals, including Beavers, Bears, and Wolves. I love animals and I'm sure you will too; please let us know what your favorite animals are. Woo hoo!

18 | 19 |
20 | 21 |
22 |

This is my second article.

23 | 24 |

This article is also quite exciting — it is all about colors, including Red, Blue, and Pink. A true joy indeed — funky exciting colors make the world go round. No more gray days for us.

25 | 26 |
27 | 28 | 38 | 39 |
40 | 41 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /host-selectors/main.js: -------------------------------------------------------------------------------- 1 | class ContextSpan extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | const style = document.createElement('style'); 6 | const span = document.createElement('span'); 7 | span.textContent = this.textContent; 8 | 9 | const shadowRoot = this.attachShadow({mode: 'open'}); 10 | shadowRoot.appendChild(style); 11 | shadowRoot.appendChild(span); 12 | 13 | style.textContent = ` 14 | span:hover { text-decoration: underline; } 15 | :host-context(h1) { font-style: italic; } 16 | :host-context(h1):after { content: " - no links in headers!" } 17 | :host(.footer) { color : red; } 18 | :host { background: rgba(0,0,0,0.1); padding: 2px 5px; } 19 | `; 20 | } 21 | } 22 | 23 | // Define the new element 24 | customElements.define('context-span', ContextSpan); 25 | -------------------------------------------------------------------------------- /host-selectors/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | background-color: #a60000; 4 | } 5 | 6 | body { 7 | margin: 0 auto; 8 | width: 80%; 9 | max-width: 800px; 10 | } 11 | 12 | header, article, aside, footer { 13 | background-color: white; 14 | padding: 20px; 15 | margin: 20px 0; 16 | } 17 | 18 | header, footer { 19 | background-color: #ddd; 20 | } 21 | 22 | li, p { 23 | font-size: 1.1rem; 24 | line-height: 1.5; 25 | } 26 | -------------------------------------------------------------------------------- /life-cycle-callbacks/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Life cycle callbacks test 6 | 11 | 12 | 13 | 14 |

Life cycle callbacks test

15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /life-cycle-callbacks/main.js: -------------------------------------------------------------------------------- 1 | // Create a class for the element 2 | class Square extends HTMLElement { 3 | // Specify observed attributes so that 4 | // attributeChangedCallback will work 5 | static get observedAttributes() { 6 | return ["color", "size"]; 7 | } 8 | 9 | constructor() { 10 | // Always call super first in constructor 11 | super(); 12 | 13 | const shadow = this.attachShadow({ mode: "open" }); 14 | 15 | const div = document.createElement("div"); 16 | const style = document.createElement("style"); 17 | shadow.appendChild(style); 18 | shadow.appendChild(div); 19 | } 20 | 21 | connectedCallback() { 22 | console.log("Custom square element added to page."); 23 | updateStyle(this); 24 | } 25 | 26 | disconnectedCallback() { 27 | console.log("Custom square element removed from page."); 28 | } 29 | 30 | adoptedCallback() { 31 | console.log("Custom square element moved to new page."); 32 | } 33 | 34 | attributeChangedCallback(name, oldValue, newValue) { 35 | console.log("Custom square element attributes changed."); 36 | updateStyle(this); 37 | } 38 | } 39 | 40 | customElements.define("custom-square", Square); 41 | 42 | function updateStyle(elem) { 43 | const shadow = elem.shadowRoot; 44 | shadow.querySelector("style").textContent = ` 45 | div { 46 | width: ${elem.getAttribute("size")}px; 47 | height: ${elem.getAttribute("size")}px; 48 | background-color: ${elem.getAttribute("color")}; 49 | } 50 | `; 51 | } 52 | 53 | const add = document.querySelector(".add"); 54 | const update = document.querySelector(".update"); 55 | const remove = document.querySelector(".remove"); 56 | let square; 57 | 58 | update.disabled = true; 59 | remove.disabled = true; 60 | 61 | function random(min, max) { 62 | return Math.floor(Math.random() * (max - min + 1) + min); 63 | } 64 | 65 | add.onclick = function () { 66 | // Create a custom square element 67 | square = document.createElement("custom-square"); 68 | square.setAttribute("size", "100"); 69 | square.setAttribute("color", "red"); 70 | document.body.appendChild(square); 71 | 72 | update.disabled = false; 73 | remove.disabled = false; 74 | add.disabled = true; 75 | }; 76 | 77 | update.onclick = function () { 78 | // Randomly update square's attributes 79 | square.setAttribute("size", random(50, 200)); 80 | square.setAttribute( 81 | "color", 82 | `rgb(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)})` 83 | ); 84 | }; 85 | 86 | remove.onclick = function () { 87 | // Remove the square 88 | document.body.removeChild(square); 89 | 90 | update.disabled = true; 91 | remove.disabled = true; 92 | add.disabled = false; 93 | }; 94 | -------------------------------------------------------------------------------- /popup-info-box-external-stylesheet/img/alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/web-components-examples/16c91cd0486afdd6407c2d606742a7fbf54c79f2/popup-info-box-external-stylesheet/img/alt.png -------------------------------------------------------------------------------- /popup-info-box-external-stylesheet/img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/web-components-examples/16c91cd0486afdd6407c2d606742a7fbf54c79f2/popup-info-box-external-stylesheet/img/default.png -------------------------------------------------------------------------------- /popup-info-box-external-stylesheet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pop-up info box — web components 6 | 7 | 8 | 9 |

Pop-up info widget - web components

10 | 11 |
12 |
13 | 20 | 21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /popup-info-box-external-stylesheet/main.js: -------------------------------------------------------------------------------- 1 | // Create a class for the element 2 | class PopupInfo extends HTMLElement { 3 | constructor() { 4 | // Always call super first in constructor 5 | super(); 6 | } 7 | 8 | connectedCallback() { 9 | // Create a shadow root 10 | const shadow = this.attachShadow({ mode: "open" }); 11 | 12 | // Create spans 13 | const wrapper = document.createElement("span"); 14 | wrapper.setAttribute("class", "wrapper"); 15 | 16 | const icon = document.createElement("span"); 17 | icon.setAttribute("class", "icon"); 18 | icon.setAttribute("tabindex", 0); 19 | 20 | const info = document.createElement("span"); 21 | info.setAttribute("class", "info"); 22 | 23 | // Take attribute content and put it inside the info span 24 | const text = this.getAttribute("data-text"); 25 | info.textContent = text; 26 | 27 | // Insert icon 28 | let imgUrl; 29 | if (this.hasAttribute("img")) { 30 | imgUrl = this.getAttribute("img"); 31 | } else { 32 | imgUrl = "img/default.png"; 33 | } 34 | 35 | const img = document.createElement("img"); 36 | img.src = imgUrl; 37 | icon.appendChild(img); 38 | 39 | // Apply external styles to the shadow dom 40 | const linkElem = document.createElement("link"); 41 | linkElem.setAttribute("rel", "stylesheet"); 42 | linkElem.setAttribute("href", "style.css"); 43 | 44 | // Attach the created elements to the shadow dom 45 | shadow.appendChild(linkElem); 46 | shadow.appendChild(wrapper); 47 | wrapper.appendChild(icon); 48 | wrapper.appendChild(info); 49 | } 50 | } 51 | 52 | // Define the new element 53 | customElements.define("popup-info", PopupInfo); 54 | -------------------------------------------------------------------------------- /popup-info-box-external-stylesheet/style.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: relative; 3 | } 4 | 5 | .info { 6 | font-size: 0.8rem; 7 | width: 200px; 8 | display: inline-block; 9 | border: 1px solid black; 10 | padding: 10px; 11 | background: white; 12 | border-radius: 10px; 13 | opacity: 0; 14 | transition: 0.6s all; 15 | position: absolute; 16 | bottom: 20px; 17 | left: 10px; 18 | z-index: 3; 19 | } 20 | 21 | img { 22 | width: 1.2rem; 23 | } 24 | 25 | .icon:hover + .info, 26 | .icon:focus + .info { 27 | opacity: 1; 28 | } 29 | -------------------------------------------------------------------------------- /popup-info-box-web-component/img/alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/web-components-examples/16c91cd0486afdd6407c2d606742a7fbf54c79f2/popup-info-box-web-component/img/alt.png -------------------------------------------------------------------------------- /popup-info-box-web-component/img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/web-components-examples/16c91cd0486afdd6407c2d606742a7fbf54c79f2/popup-info-box-web-component/img/default.png -------------------------------------------------------------------------------- /popup-info-box-web-component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pop-up info box — web components 6 | 7 | 8 | 9 |

Pop-up info widget - web components

10 | 11 |
12 |
13 | 20 | 21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /popup-info-box-web-component/main.js: -------------------------------------------------------------------------------- 1 | // Create a class for the element 2 | class PopupInfo extends HTMLElement { 3 | constructor() { 4 | // Always call super first in constructor 5 | super(); 6 | } 7 | 8 | connectedCallback() { 9 | // Create a shadow root 10 | const shadow = this.attachShadow({ mode: "open" }); 11 | 12 | // Create spans 13 | const wrapper = document.createElement("span"); 14 | wrapper.setAttribute("class", "wrapper"); 15 | 16 | const icon = document.createElement("span"); 17 | icon.setAttribute("class", "icon"); 18 | icon.setAttribute("tabindex", 0); 19 | 20 | const info = document.createElement("span"); 21 | info.setAttribute("class", "info"); 22 | 23 | // Take attribute content and put it inside the info span 24 | const text = this.getAttribute("data-text"); 25 | info.textContent = text; 26 | 27 | // Insert icon 28 | let imgUrl; 29 | if (this.hasAttribute("img")) { 30 | imgUrl = this.getAttribute("img"); 31 | } else { 32 | imgUrl = "img/default.png"; 33 | } 34 | 35 | const img = document.createElement("img"); 36 | img.src = imgUrl; 37 | icon.appendChild(img); 38 | 39 | // Create some CSS to apply to the shadow dom 40 | const style = document.createElement("style"); 41 | console.log(style.isConnected); 42 | 43 | style.textContent = ` 44 | .wrapper { 45 | position: relative; 46 | } 47 | 48 | .info { 49 | font-size: 0.8rem; 50 | width: 200px; 51 | display: inline-block; 52 | border: 1px solid black; 53 | padding: 10px; 54 | background: white; 55 | border-radius: 10px; 56 | opacity: 0; 57 | transition: 0.6s all; 58 | position: absolute; 59 | bottom: 20px; 60 | left: 10px; 61 | z-index: 3; 62 | } 63 | 64 | img { 65 | width: 1.2rem; 66 | } 67 | 68 | .icon:hover + .info, .icon:focus + .info { 69 | opacity: 1; 70 | } 71 | `; 72 | 73 | // Attach the created elements to the shadow dom 74 | shadow.appendChild(style); 75 | console.log(style.isConnected); 76 | shadow.appendChild(wrapper); 77 | wrapper.appendChild(icon); 78 | wrapper.appendChild(info); 79 | } 80 | } 81 | 82 | // Define the new element 83 | customElements.define("popup-info", PopupInfo); 84 | -------------------------------------------------------------------------------- /shadow-part/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shadow parts example 6 | 7 | 8 | 9 | 10 |

Shadow part styling for tabbed custom element

11 | 12 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /shadow-part/main.js: -------------------------------------------------------------------------------- 1 | let template = document.getElementById("tabbed-custom-element"); 2 | 3 | globalThis.customElements.define(template.id, class extends HTMLElement { 4 | constructor() { 5 | super(); 6 | this.attachShadow({ mode: "open" }); 7 | this.shadowRoot.appendChild(template.content); 8 | 9 | let tabs = []; 10 | let children = this.shadowRoot.children; 11 | 12 | for(let elem of children) { 13 | if(elem.getAttribute('part')) { 14 | tabs.push(elem); 15 | } 16 | } 17 | 18 | tabs.forEach((tab) => { 19 | tab.addEventListener('click', (e) => { 20 | tabs.forEach((tab) => { 21 | tab.part = 'tab'; 22 | }) 23 | e.target.part = 'tab active'; 24 | }) 25 | 26 | console.log(tab.part); 27 | }) 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /shadow-part/style.css: -------------------------------------------------------------------------------- 1 | tabbed-custom-element::part(tab) { 2 | color: #0c0c0dcc; 3 | border-bottom: transparent solid 2px; 4 | } 5 | 6 | tabbed-custom-element::part(tab):hover { 7 | background-color: #0c0c0d19; 8 | border-color: #0c0c0d33; 9 | } 10 | 11 | tabbed-custom-element::part(tab):hover:active { 12 | background-color: #0c0c0d33; 13 | } 14 | 15 | tabbed-custom-element::part(tab):focus { 16 | box-shadow: 17 | 0 0 0 1px #0a84ff inset, 18 | 0 0 0 1px #0a84ff, 19 | 0 0 0 4px rgba(10, 132, 255, 0.3); 20 | } 21 | 22 | tabbed-custom-element::part(active) { 23 | color: #0060df; 24 | border-color: #0a84ff !important; 25 | } 26 | -------------------------------------------------------------------------------- /simple-template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple template 6 | 7 | 8 | 9 |

Simple template

10 | 11 | 23 | 24 | 25 | Let's have some different text! 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /simple-template/main.js: -------------------------------------------------------------------------------- 1 | customElements.define('my-paragraph', 2 | class extends HTMLElement { 3 | constructor() { 4 | super(); 5 | 6 | const template = document.getElementById('custom-paragraph'); 7 | const templateContent = template.content; 8 | 9 | this.attachShadow({ mode: 'open' }).appendChild( 10 | templateContent.cloneNode(true) 11 | ); 12 | } 13 | } 14 | ); 15 | 16 | const slottedSpan = document.querySelector('my-paragraph span'); 17 | 18 | console.log(slottedSpan.assignedSlot); 19 | console.log(slottedSpan.slot); 20 | -------------------------------------------------------------------------------- /slotchange/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | slotchange example 6 | 7 | 12 | 13 | 14 |

slotchange event example

15 | 16 | 17 | 26 | 27 |

A common, sweet, crunchy fruit, usually green or yellow in color.

28 |

A fairly common, sweet, usually green fruit, usually softer than Apples.

29 |

A long, curved, yellow fruit, with a fairly gentle flavor.

30 |

Orange in color, usually sweet but can be sharp, often contains pips.

31 |

An orange fruit with big stone in the middle, and sweet, juicy flesh.

32 |

A red fruit with yellow seeds on the outside; has a sweet flavor and a pretty shape.

33 |

They are berries and they are blue; sweet in flavor, small in size, round.

34 |
35 | 36 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /slotchange/main.js: -------------------------------------------------------------------------------- 1 | customElements.define('summary-display', 2 | class extends HTMLElement { 3 | constructor() { 4 | super(); 5 | 6 | const template = document.getElementById('summary-display-template'); 7 | const templateContent = template.content; 8 | 9 | const shadowRoot = this.attachShadow({mode: 'open'}); 10 | shadowRoot.appendChild(templateContent.cloneNode(true)); 11 | 12 | const items = Array.from(this.querySelectorAll('li')); 13 | const descriptions = Array.from(this.querySelectorAll('p')); 14 | 15 | items.forEach(item => { 16 | handleClick(item); 17 | }); 18 | 19 | function handleClick(item) { 20 | item.addEventListener('click', function() { 21 | items.forEach(item => { 22 | item.style.backgroundColor = 'white'; 23 | }); 24 | 25 | descriptions.forEach(description => { 26 | updateDisplay(description, item); 27 | }); 28 | }); 29 | } 30 | 31 | function updateDisplay(description, item) { 32 | description.removeAttribute('slot'); 33 | 34 | if(description.getAttribute('data-name') === item.textContent) { 35 | description.setAttribute('slot', 'choice'); 36 | item.style.backgroundColor = '#bad0e4'; 37 | } 38 | } 39 | 40 | const slots = this.shadowRoot.querySelectorAll('slot'); 41 | slots[1].addEventListener('slotchange', function(e) { 42 | const nodes = slots[1].assignedNodes(); 43 | console.log(`Element in Slot "${slots[1].name}" changed to "${nodes[0].outerHTML}".`); 44 | }); 45 | } 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /slotted-pseudo-element/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ::slotted example 6 | 7 | 8 | 9 |

::slotted pseudo-element example

10 | 11 | 21 | 22 | 23 |

Morgan Stanley

24 | 36 25 | Accountant 26 |
27 | 28 | 29 |

Dr. Shazaam

30 | Immortal 31 | Superhero 32 |
33 | 34 | 35 |

Boris

36 | 27 37 | Time traveller 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /slotted-pseudo-element/main.js: -------------------------------------------------------------------------------- 1 | customElements.define('person-details', 2 | class extends HTMLElement { 3 | constructor() { 4 | super(); 5 | 6 | const template = document.getElementById('person-template'); 7 | const templateContent = template.content; 8 | 9 | const shadowRoot = this.attachShadow({mode: 'open'}); 10 | 11 | const style = document.createElement('style'); 12 | style.textContent = ` 13 | div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; } 14 | h2 { margin: 0 0 10px; } 15 | ul { margin: 0; } 16 | p { margin: 10px 0; } 17 | ::slotted(*) { color: gray; font-family: sans-serif; } 18 | `; 19 | 20 | shadowRoot.appendChild(style); 21 | shadowRoot.appendChild(templateContent.cloneNode(true)); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /word-count-web-component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple word count web component 6 | 7 | 8 |

Word count rating widget

9 | 10 |
11 |

Sample heading

12 | 13 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus. Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra vestibulum pretium. Mauris at eros vitae ante pellentesque bibendum. Etiam et blandit purus, nec aliquam libero. Etiam leo felis, pulvinar et diam id, sagittis pulvinar diam. Nunc pellentesque rutrum sapien, sed faucibus urna sodales in. Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc, pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.

14 | 15 |

Pellentesque ornare tellus sit amet massa tincidunt congue. Morbi cursus, tellus vitae pulvinar dictum, dui turpis faucibus ipsum, nec hendrerit augue nisi et enim. Curabitur felis metus, euismod et augue et, luctus dignissim metus. Mauris placerat tellus id efficitur ornare. Cras enim urna, vestibulum vel molestie vitae, mollis vitae eros. Sed lacinia scelerisque diam, a varius urna iaculis ut. Nam lacinia, velit consequat venenatis pellentesque, leo tortor porttitor est, sit amet accumsan ex lectus eget ipsum. Quisque luctus, ex ac fringilla tincidunt, risus mauris sagittis mauris, at iaculis mauris purus eget neque. Donec viverra in ex sed ullamcorper. In ac nisi vel enim accumsan feugiat et sed augue. Donec nisl metus, sollicitudin eu tempus a, scelerisque sed diam.

16 | 17 |

18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /word-count-web-component/main.js: -------------------------------------------------------------------------------- 1 | // Create a class for the element 2 | class WordCount extends HTMLParagraphElement { 3 | constructor() { 4 | // Always call super first in constructor 5 | super(); 6 | 7 | // count words in element's parent element 8 | const wcParent = this.parentNode; 9 | 10 | function countWords(node){ 11 | const text = node.innerText || node.textContent; 12 | return text.trim().split(/\s+/g).filter(a => a.trim().length > 0).length; 13 | } 14 | 15 | const count = `Words: ${countWords(wcParent)}`; 16 | 17 | // Create a shadow root 18 | const shadow = this.attachShadow({mode: 'open'}); 19 | 20 | // Create text node and add word count to it 21 | const text = document.createElement('span'); 22 | text.textContent = count; 23 | 24 | // Append it to the shadow root 25 | shadow.appendChild(text); 26 | 27 | // Update count when element content changes 28 | this.parentNode.addEventListener('input', () => { 29 | text.textContent = `Words: ${countWords(wcParent)}`; 30 | }); 31 | } 32 | } 33 | 34 | // Define the new element 35 | customElements.define('word-count', WordCount, { extends: 'p' }); 36 | --------------------------------------------------------------------------------