Ponys

repository·main·Indexed 40 days ago

https://github.com/jhuddle/ponys

A library for the declarative creation of browser-native web components. Ponys simplifies custom element boilerplate by allowing developers to define components using HTML <template> tags, strings, or external files, with built-in support for encapsulated styles and modular JavaScript logic via <script setup>.

Tokens
2.3K
Snippets
7
Records
8
Agent score
0%

What's inside Ponys

  1. How Ponys component selectors work

    main

    Ponys provides convenience methods for querying elements within a component's Shadow Root. These methods are available on the component class (the host) and on any element within the template.

    • this.$('selector'): Returns the first element matching the selector within the shadow root (shorthand for this.shadowRoot.querySelector).
    • this.$$('selector'): Returns all elements matching the selector within the shadow root (shorthand for this.shadowRoot.querySelectorAll).
    • this.host: A reference to the host element itself.

    These methods allow elements inside a template to easily reference each other or the host.

  2. Add interactivity and styling to Ponys components

    main

    Ponys components support encapsulated styling and modular logic using <script setup> and <style> tags within the template.

    Interactivity with <script setup>

    Inside your template, add a <script setup> tag. The class exported as default will be used as the custom element's logic. This script is treated as an ESM module, allowing you to import external libraries.

    Key features:

    • No constructor required: Define properties and methods directly on the class.
    • Convenience selectors:
      • this.$('#id'): Equivalent to this.shadowRoot.querySelector('#id').
      • this.$$('selector'): Equivalent to this.shadowRoot.querySelectorAll('selector').
      • this.host: A property pointing directly to the host element.
    • Element-level access: Every element inside the template also has access to this.$(), this.$$(), and host.

    Encapsulated Styling with <style>

    Add a <style> tag inside your template. Ponys will place the component's elements behind a Shadow Root whenever possible, ensuring CSS encapsulation.

    Example: Counter Button

    <template name="counter-button" extends="button">
      Count: <b id="count"></b>
    
      <script setup>
        export default class extends HTMLButtonElement {
          count = 0
    
          update() {
            this.$('#count').textContent = this.count;
          }
    
          increment() {
            this.count++;
            this.update();
          }
    
          connectedCallback() {
            this.onclick = event => this.increment();
            this.update();
          }
        }
      </script>
    
      <style>
        /* Styles are encapsulated */
      </style>
    </template>
    <template name="my-component">
      <div id="content">Hello</div>
      
      <script setup>
        export default class extends HTMLElement {
          connectedCallback() {
            console.log(this.$('#content').textContent);
          }
        }
      </script>
    
      <style>
        #content { color: blue; }
      </style>
    </template>
  3. Prevent Flash of Unstyled Content (FOUC)

    main

    To prevent the Flash of Unstyled Content (FOUC) while custom elements are being defined, add the following CSS to your global stylesheet. This hides any element that has not yet been upgraded to its defined custom element class.

    :not(:defined) {
      display: none;
    }
  4. Integrate Ponys with reactive frameworks like petite-vue

    main

    Ponys allows you to build custom elements that leverage reactive ecosystems like petite-vue or Alpine.js. You can define your component logic within a <script setup> block inside an HTML template, then use Ponys.import() to register the component. Inside the component class, you can use PetiteVue.createApp(...).mount(host.$('table')) to bind reactive data to specific elements within your component's template.

    <!-- csv-table-vue.html -->
    <table v-if="data">
      <thead v-if="header">
        <tr v-for="value, col in data[0]">
          <th @click="sort(col)">{{ value }}</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in data.slice(header)">
          <td v-for="value in row">{{ value }}</td>
        </tr>
      </tbody>
    </table>
    
    <script setup>
      import * as PetiteVue from 'https://cdn.jsdelivr.net/npm/petite-vue/+esm';
    
      export default class extends HTMLElement {
        connectedCallback() {
          const host = this;
          PetiteVue.createApp({
            data: [...],
            sort(col) { ... }
          }).mount(host.$('table'));
        }
      }
    </script>
    /* app.js */
    import Ponys from './ponys.js';
    
    Ponys.import(
      'csv-table-vue',
      import.meta.resolve('./components/csv-table-vue.html')
    );
    <csv-table-vue header border>
      Character;Kind;Element of Harmony
      Twilight Sparkle;Alicorn;Magic
    </csv-table-vue>
  5. Observe attribute changes in Ponys components

    main

    To trigger logic when an attribute is changed on your custom element, use the standard Web Component observedAttributes static property and the attributeChangedCallback method. This allows you to react to changes in the element's state via its HTML attributes.

    <script setup>
      export default class extends HTMLElement {
    
        static observedAttributes = ['data-color', 'data-has-cutie-mark']
    
        attributeChangedCallback(name, previousValue, value) {
          switch (name) {
            case 'data-color'          : return this.changeColor(value)
            case 'data-has-cutie-mark' : return this.setCutieMark(value)
          }
        }
    
        changeColor(value) { ... }
        setCutieMark(value) { ... }
    
        ...
    
      }
    </script>
  6. Define custom elements with Ponys

    main

    Ponys provides three ways to promote templates or strings to browser-native custom elements:

    1. Automatic detection of <template> elements

    Use Ponys.defineAll() to automatically promote any <template> element in your HTML that has a name attribute to a custom element.

    <script type="module">
      import Ponys from './ponys.js';
      Ponys.defineAll();
    </script>
    
    <template name="hello-world">
      Hello, <slot>world</slot>!
    </template>

    2. Define via name and string content

    Use Ponys.define(name, content) to define an element using a string for its inner HTML.

    import Ponys from './ponys.js';
    
    Ponys.define('hello-world', 'Hello, <slot>world</slot>!');

    3. Import from a separate file

    You can import component content from an external file using Ponys.import(name, url) in JavaScript, or by using the src attribute on a named <template> in HTML.

    Via JavaScript:

    import Ponys from './ponys.js';
    
    Ponys.import(
      'hello-world',
      import.meta.resolve('./components/hello-world.html')
    );

    Via HTML:

    <script type="module">
      import Ponys from './ponys.js';
      Ponys.defineAll();
    </script>
    
    <template name="hello-world" src="./components/hello-world.html"></template>
    import Ponys from './ponys.js';
    
    // Method 1: Define by string
    Ponys.define('hello-world', 'Hello, <slot>world</slot>!');
    
    // Method 2: Import from file
    Ponys.import(
      'hello-world',
      import.meta.resolve('./components/hello-world.html')
    );
  7. Disable the Shadow Root in Ponys components

    main

    By default, Ponys components use a shadow root. To prevent the creation of a shadow root and use the light DOM instead, define a static disabledFeatures array containing 'shadow' within your component class.

    <script setup>
      export default class extends HTMLElement {
    
        static disabledFeatures = ['shadow']
    
        ...
    
      }
    </script>