# Custom Elements Documentation

Woby provides a powerful API for creating custom HTML elements that integrate seamlessly with the framework's observable system. These custom elements can be used both in JSX/TSX and directly in HTML. This documentation covers both the API reference and best practices for creating robust custom elements.

## Table of Contents

- [customElement](#customelement)
  - [Observed Attributes](#observed-attributes)
- [ElementAttributes](#elementattributes)
  - [Benefits of ElementAttributes](#benefits-of-elementattributes)
- [JSX Type Augmentation](#jsx-type-augmentation)
  - [Example from Counter Demo](#example-from-counter-demo)
  - [Best Practices for Type Augmentation](#best-practices-for-type-augmentation)
  - [Method 1: In the same file as the component (Recommended)](#method-1-in-the-same-file-as-the-component-recommended)
  - [Method 2: In a separate declaration file](#method-2-in-a-separate-declaration-file)
  - [Advanced Type Augmentation Example](#advanced-type-augmentation-example)
- [Attribute to Prop Mapping](#attribute-to-prop-mapping)
- [Type Conversion](#type-conversion)
- [Nested Properties](#nested-properties)
- [Style Properties](#style-properties)
- [Shadow DOM](#shadow-dom)
- [Context Support](#context-support)
- [HTML Attribute Serialization](#html-attribute-serialization)
- [Function Properties](#function-properties)
- [Object and Date Serialization](#object-and-date-serialization)
- [HTML vs JSX Usage](#html-vs-jsx-usage)
- [Component Defaults and Two-Way Synchronization](#component-defaults-and-two-way-synchronization)
- [The defaults Function](#the-defaults-function)
- [How Two-Way Synchronization Works](#how-two-way-synchronization-works)
- [Key Requirements for Synchronization](#key-requirements-for-synchronization)
- [Handling Different Prop Sources](#handling-different-prop-sources)
- [Functions and Complex Objects](#functions-and-complex-objects)
- [Without Two-Way Synchronization](#without-two-way-synchronization)
- [Best Practices](#best-practices)

## customElement

Creates a custom HTML element with reactive properties that integrate with Woby's observable system.

### Syntax

```typescript
const CustomElementClass = customElement(tagName, component)
```

### Parameters

- `tagName`: The HTML tag name for the custom element (must contain a hyphen)
- `component`: The component function that renders the element's content

### Requirements

- Component must have default props defined using the `defaults` helper
- Component props that are observables will be updated with type conversion
- Tag name must contain a hyphen (per HTML custom element specification)

### Observed Attributes

When you register a custom element, Woby automatically observes all props defined in your component's `def()` function. This means that HTML attributes corresponding to these props will be automatically synchronized with the component's props. For example:

```tsx
// In your component definition
const def = () => ({
  value: $(0, { type: 'number' } as const),
  title: $('Counter'),
  disabled: $(false, { type: 'boolean' } as const)
})

// When registered as a custom element, these props become observed attributes:
// - 'value': The counter value (converted from string to number)
// - 'title': The counter title (string)
// - 'disabled': Disabled state (converted from string to boolean)
// - 'class': CSS classes
// - 'style-*': Style properties (e.g., style$font-size in HTML, style-font-size in JSX)
// - 'nested-*': Nested properties (e.g., nested$nested$text in HTML, nested-nested-text in JSX)
```

The automatic observation of attributes ensures that changes to HTML attributes are immediately reflected in your component's props, and vice versa.

### Example

```tsx
import { $, customElement, defaults } from 'woby'

// Define a component with default props
const Counter = defaults(() => ({
  value: $(0, { type: 'number' } as const),
  title: $('Counter')
}), ({ value, title }) => (
  <div>
    <h1>{title}</h1>
    <p>Count: {value}</p>
    <button onClick={() => value(prev => prev + 1)}>+</button>
  </div>
))

// Register as a custom element
customElement('counter-element', Counter)
```

## ElementAttributes

A type helper that combines HTML attributes with component-specific props for better TypeScript support.

### Usage

```tsx
import { ElementAttributes } from 'woby'

const Counter = defaults(() => ({
  value: $(0)
}), ({ value }) => <div>Count: {value}</div>)

// Type definition for the custom element
declare module 'woby' {
  namespace JSX {
    interface IntrinsicElements {
      'counter-element': ElementAttributes<typeof Counter>
    }
  }
}
```

### Benefits of ElementAttributes

The `ElementAttributes` type helper provides several key benefits:

1. **Automatic HTML Attribute Support**: Automatically includes all standard HTML attributes like `class`, `id`, `style`, etc.
2. **Component Prop Typing**: Provides type safety for component-specific props
3. **Style Property Support**: Enables type checking for style properties using the `style-*` pattern
4. **Nested Property Support**: Supports type checking for nested properties using the `nested-*` pattern
5. **IDE Autocomplete**: Enables full IDE autocomplete support for both HTML attributes and component props

When you use `ElementAttributes<typeof YourComponent>`, TypeScript will automatically understand:
- All standard HTML attributes that can be applied to the element
- The specific props your component accepts
- Style properties that can be set using the `style$property-name` syntax in HTML or `style-property-name` in JSX
- Nested properties that can be set using the `nested$property$name` syntax in HTML or `nested-property-name` in JSX

This provides a seamless development experience with full type safety and IDE support for your custom elements.

## JSX Type Augmentation

To get proper TypeScript support and IDE autocomplete for your custom elements in JSX/TSX, you need to augment the JSX namespace. This can be done in two ways:

### Example from Counter Demo

The following example from the Counter demo shows advanced type augmentation with multiple custom elements and complex props:

```tsx
/**
 * Extend JSX namespace to include the custom element
 * 
 * This allows TypeScript to recognize the custom element in JSX.
 */
declare module 'woby' {
  namespace JSX {
    interface IntrinsicElements {
      /**
       * Counter custom element
       * 
       * HTML element that displays a counter with increment/decrement buttons.
       * 
       * The ElementAttributes<typeof Counter> type automatically includes:
       * - All HTML attributes
       * - Component-specific props from CounterProps
       * - Style properties via the style-* pattern (style$font-size in HTML, style-font-size in JSX)
       * - Nested properties via the nested-* pattern (nested$nested$text in HTML, nested-nested-text in JSX)
       */
      'counter-element': ElementAttributes<typeof Counter>
      'context-value': ElementAttributes<typeof ContextValue>
      'my-上下文-值': ElementAttributes<typeof ContextValue>
      'processed-context-value': ElementAttributes<typeof ProcessedContextValue>
    }
  }
}
```

This example demonstrates several advanced patterns:

1. **Multiple Custom Elements**: Type definitions for multiple custom elements in a single declaration
2. **Unicode Tag Names**: Support for Unicode characters in custom element tag names (`my-上下文-值`)
3. **Complex Props**: Automatic type inference for complex props including nested objects, functions, and observables
4. **Context Integration**: Type support for custom elements that use context

The `ElementAttributes<typeof Counter>` type automatically provides type safety for:
- Standard HTML attributes like `class`, `id`, `style`
- Component-specific props like `value`, `title`, `disabled`
- Style properties using `style$property-name` syntax
- Nested properties using `nested$property$name` syntax
- Object and Date serialization props with custom `toHtml`/`fromHtml` options

### Method 1: In the same file as the component (Recommended)

Add the augmentation directly in the same file where you define your component:

```tsx
import { $, customElement, defaults, ElementAttributes } from 'woby'

const MyComponent = defaults(() => ({
  value: $(0)
}), ({ value }) => <div>Value: {value}</div>)

// Register as a custom element
customElement('my-component', MyComponent)

// Augment JSX intrinsic elements for better TypeScript support
declare module 'woby' {
  namespace JSX {
    interface IntrinsicElements {
      'my-component': ElementAttributes<typeof MyComponent>
    }
  }
}
```

### Best Practices for Type Augmentation

When augmenting types for custom elements, follow these best practices:

1. **Place Type Augmentations Close to Component Definitions**: Include the type augmentation in the same file as your component definition when possible
2. **Use JSDoc Comments**: Add descriptive comments to your type definitions for better IDE support
3. **Be Specific with Tag Names**: Use descriptive, hyphenated tag names that clearly indicate the element's purpose
4. **Include All Custom Elements**: Define types for all custom elements you create in your application
5. **Use Unicode Carefully**: While Unicode tag names are supported, use them judiciously for better compatibility
declare module 'woby' {
  namespace JSX {
    interface IntrinsicElements {
      'my-component': ElementAttributes<typeof MyComponent>
    }
  }
}
```

### Method 2: In a separate declaration file

Create a separate `.d.ts` file (e.g., `jsx.d.ts`) in your project:

```ts
import { MyComponent } from './MyComponent'
import { ElementAttributes } from 'woby'

declare module 'woby' {
  namespace JSX {
    interface IntrinsicElements {
      'my-component': ElementAttributes<typeof MyComponent>
    }
  }
}
```

Make sure your `tsconfig.json` includes the declaration file in the compilation.

### Best Practices for Type Augmentation

When augmenting types for custom elements, follow these best practices:

1. **Place Type Augmentations Close to Component Definitions**: Include the type augmentation in the same file as your component definition when possible
2. **Use JSDoc Comments**: Add descriptive comments to your type definitions for better IDE support
3. **Be Specific with Tag Names**: Use descriptive, hyphenated tag names that clearly indicate the element's purpose
4. **Include All Custom Elements**: Define types for all custom elements you create in your application
5. **Use Unicode Carefully**: While Unicode tag names are supported, use them judiciously for better compatibility

### Advanced Type Augmentation Example

For more complex components with multiple custom elements, you can define comprehensive type augmentations as shown in this example:

```tsx
import { $, customElement, defaults, ElementAttributes } from 'woby'

// Define components with default props
const Counter = defaults(() => ({
  value: $(0, { type: 'number' } as const),
  title: $('Counter'),
  disabled: $(false, { type: 'boolean' } as const)
}), ({ value, title, disabled }) => (
  <div>
    <h1>{title}</h1>
    <p>Count: {value}</p>
    <button disabled={disabled} onClick={() => value(prev => prev + 1)}>+</button>
  </div>
))

const ContextValue = defaults(() => ({}), () => {
  const context = useMountedContext(SomeContext)
  return <span>Context Value: {context}</span>
})

// Register as custom elements
customElement('counter-element', Counter)
customElement('context-value', ContextValue)

declare module 'woby' {
  namespace JSX {
    interface IntrinsicElements {
      /**
       * Counter custom element
       * 
       * HTML element that displays a counter with increment/decrement buttons.
       * 
       * The ElementAttributes<typeof Counter> type automatically includes:
       * - All HTML attributes
       * - Component-specific props from CounterProps
       * - Style properties via the style-* pattern (style$font-size in HTML, style-font-size in JSX)
       * - Nested properties via the nested-* pattern (nested$nested$text in HTML, nested-nested-text in JSX)
       */
      'counter-element': ElementAttributes<typeof Counter>
      'context-value': ElementAttributes<typeof ContextValue>
    }
  }
}
```

This approach provides full TypeScript support for your custom elements in JSX, including proper typing for all HTML attributes, component-specific props, style properties, and nested properties.

### Complex Prop Patterns from Counter Demo

The Counter demo showcases several advanced patterns for handling complex props in custom elements:

1. **Function Storage in Observables**: Functions are stored in observables using array notation to hide them from HTML attributes:
   ```tsx
   const def = () => ({
     // Store function in observable array to hide it from HTML attributes
     increment: $([() => { value($$(value) + 10) }], { toHtml: o => undefined })
   })
   ```

2. **Object Serialization**: Complex objects are serialized using custom `toHtml` and `fromHtml` options:
   ```tsx
   const def = () => ({
     // Object with custom serialization
     obj: $({ nested: { text: 'abc' } }, { 
       toHtml: o => JSON.stringify(o), 
       fromHtml: o => JSON.parse(o) 
     })
   })
   ```

3. **Date Serialization**: Dates are serialized using custom `toHtml` and `fromHtml` options:
   ```tsx
   const def = () => ({
     // Date with custom serialization
     date: $(new Date(), { 
       toHtml: o => o.toISOString(), 
       fromHtml: o => new Date(o) 
     })
   })
   ```

4. **Nested Properties**: Complex nested properties are supported:
   ```tsx
   const def = () => ({
     nested: { nested: { text: $('abc') } }
   })
   ```

These patterns ensure proper type safety and serialization for complex data structures while maintaining the ability to use the components both in HTML and JSX contexts.

```tsx
import { $, customElement, defaults, ElementAttributes } from 'woby'

// Define components with default props
const Counter = defaults(() => ({
  value: $(0, { type: 'number' } as const),
  title: $('Counter'),
  disabled: $(false, { type: 'boolean' } as const)
}), ({ value, title, disabled }) => (
  <div>
    <h1>{title}</h1>
    <p>Count: {value}</p>
    <button disabled={disabled} onClick={() => value(prev => prev + 1)}>+</button>
  </div>
))

const ContextValue = defaults(() => ({}), () => {
  const context = useMountedContext(SomeContext)
  return <span>Context Value: {context}</span>
})

// Register as custom elements
customElement('counter-element', Counter)
customElement('context-value', ContextValue)

declare module 'woby' {
  namespace JSX {
    interface IntrinsicElements {
      /**
       * Counter custom element
       * 
       * HTML element that displays a counter with increment/decrement buttons.
       * 
       * The ElementAttributes<typeof Counter> type automatically includes:
       * - All HTML attributes
       * - Component-specific props from CounterProps
       * - Style properties via the style-* pattern (style$font-size in HTML, style-font-size in JSX)
       * - Nested properties via the nested-* pattern (nested$nested$text in HTML, nested-nested-text in JSX)
       */
      'counter-element': ElementAttributes<typeof Counter>
      'context-value': ElementAttributes<typeof ContextValue>
    }
  }
}
```

This approach provides full TypeScript support for your custom elements in JSX, including proper typing for all HTML attributes, component-specific props, style properties, and nested properties.

## Attribute to Prop Mapping

Custom elements automatically map HTML attributes to component props:

### Simple Properties

```html
<!-- HTML -->
<counter-element value="5" title="My Counter"></counter-element>

<!-- Maps to props -->
{
  value: $(5), // with type conversion
  title: $('My Counter')
}
```

### Dynamic Updates

Attributes can be updated dynamically, and the corresponding props will be updated:

```javascript
const element = document.querySelector('counter-element')
element.setAttribute('value', '10') // Updates the observable prop
```

## Type Conversion

Custom elements automatically convert string attributes to appropriate types based on observable options:

### Number

```tsx
const Counter = defaults(() => ({
  value: $(0, { type: 'number' } as const) // Specifies number type
}), ({ value }) => <div>Count: {value}</div>)

customElement('counter-element', Counter)

// HTML usage:
// <counter-element value="42"></counter-element>
// value prop will be Number(42)
```

### Boolean

```tsx
const Toggle = defaults(() => ({
  enabled: $(false, { type: 'boolean' as const }) // Specifies boolean type
}), ({ enabled }) => <div>Enabled: {enabled ? 'Yes' : 'No'}</div>)

customElement('toggle-element', Toggle)

// HTML usage:
// <toggle-element enabled="true"></toggle-element>
// <toggle-element enabled="1"></toggle-element>
// <toggle-element enabled=""></toggle-element>
// All set enabled prop to true
```

### Other Types

- `bigint`: Converted using `BigInt()`, falls back to string on error
- `object`: Parsed using `JSON.parse()`, falls back to string on error
- `symbol`: Created using `Symbol()`
- Other types: Treated as strings

## HTML Utility Types

Woby provides a set of HTML utility types that make it easier to work with common HTML attribute patterns. These utilities implement the `ObservableOptions` interface and provide consistent conversion between JavaScript values and HTML attributes.

### HtmlBoolean

Handles boolean values with automatic conversion from HTML string attributes:

```tsx
import { HtmlBoolean } from 'woby'

const Toggle = defaults(() => ({
  enabled: $(false, HtmlBoolean)
}), ({ enabled }) => <div>Enabled: {enabled ? 'Yes' : 'No'}</div>)

customElement('toggle-element', Toggle)

// HTML usage:
// <toggle-element enabled="true"></toggle-element>  // enabled = $(true)
// <toggle-element enabled="1"></toggle-element>     // enabled = $(true)
// <toggle-element enabled=""></toggle-element>      // enabled = $(true)
// <toggle-element enabled="false"></toggle-element> // enabled = $(false)
```

### HtmlNumber

Handles numeric values with automatic conversion from HTML string attributes:

```tsx
import { HtmlNumber } from 'woby'

const Counter = defaults(() => ({
  value: $(0, HtmlNumber)
}), ({ value }) => <div>Count: {value}</div>)

customElement('counter-element', Counter)

// HTML usage:
// <counter-element value="42"></counter-element>  // value = $(42)
// <counter-element value="-5"></counter-element>  // value = $(-5)
// <counter-element value="3.14"></counter-element> // value = $(3.14)
```

### HtmlDate

Handles Date values with ISO string serialization:

```tsx
import { HtmlDate } from 'woby'

const DatePicker = defaults(() => ({
  selectedDate: $(new Date(), HtmlDate)
}), ({ selectedDate }) => <div>Date: {() => $$(selectedDate).toString()}</div>)

customElement('date-picker', DatePicker)

// HTML usage:
// <date-picker selected-date="2023-01-01T00:00:00.000Z"></date-picker>
// selectedDate = $(new Date("2023-01-01T00:00:00.000Z"))
```

### HtmlBigInt

Handles BigInt values with automatic conversion:

```tsx
import { HtmlBigInt } from 'woby'

const BigIntComponent = defaults(() => ({
  largeNumber: $(BigInt(0), HtmlBigInt)
}), ({ largeNumber }) => <div>Number: {() => $$(largeNumber).toString()}</div>)

customElement('bigint-component', BigIntComponent)

// HTML usage:
// <bigint-component large-number="12345678901234567890"></bigint-component>
// largeNumber = $(BigInt("12345678901234567890"))
```

### HtmlObject

Handles Object values with JSON serialization:

```tsx
import { HtmlObject } from 'woby'

const ObjectComponent = defaults(() => ({
  config: $({} as any, HtmlObject)
}), ({ config }) => <div>Config: {() => JSON.stringify($$(config))}</div>)

customElement('object-component', ObjectComponent)

// HTML usage:
// <object-component config='{"key":"value","nested":{"num":42}}'></object-component>
// config = $({ key: "value", nested: { num: 42 } })
```

### HtmlLength

Handles CSS length values (px, em, rem, %, etc.):

```tsx
import { HtmlLength } from 'woby'

const SizeComponent = defaults(() => ({
  width: $('100px', HtmlLength),
  height: $('auto', HtmlLength)
}), ({ width, height }) => (
  <div style={() => ({ width: $$(width), height: $$(height) })}>
    Sized content
  </div>
))

customElement('size-component', SizeComponent)

// HTML usage:
// <size-component width="50%" height="200px"></size-component>
// width = $("50%"), height = $("200px")
```

### HtmlBox

Handles CSS box values (margin, padding, border, etc.):

```tsx
import { HtmlBox } from 'woby'

const BoxComponent = defaults(() => ({
  margin: $('10px', HtmlBox),
  padding: $('5px 10px', HtmlBox)
}), ({ margin, padding }) => (
  <div style={() => ({ margin: $$(margin), padding: $$(padding) })}>
    Box content
  </div>
))

customElement('box-component', BoxComponent)

// HTML usage:
// <box-component margin="1em" padding="5px 10px 15px 20px"></box-component>
```

### HtmlColor

Handles CSS color values (hex, rgb, etc.):

```tsx
import { HtmlColor } from 'woby'

const ColorComponent = defaults(() => ({
  backgroundColor: $('#ffffff', HtmlColor),
  textColor: $('rgb(0, 0, 0)', HtmlColor)
}), ({ backgroundColor, textColor }) => (
  <div style={() => ({ backgroundColor: $$(backgroundColor), color: $$(textColor) })}>
    Colored content
  </div>
))

customElement('color-component', ColorComponent)

// HTML usage:
// <color-component background-color="#ff0000" text-color="blue"></color-component>
// backgroundColor = $("#ff0000"), textColor = $("blue")
```

### HtmlStyle

Handles CSS style values (objects and strings):

```tsx
import { HtmlStyle } from 'woby'

const StyleComponent = defaults(() => ({
  customStyles: $({} as any, HtmlStyle)
}), ({ customStyles }) => (
  <div style={() => $$(customStyles)}>
    Styled content
  </div>
))

customElement('style-component', StyleComponent)

// HTML usage:
// <style-component custom-styles="color: red; font-size: 1.5em;"></style-component>
// customStyles = $({ color: "red", fontSize: "1.5em" })
```

### Using HTML Utility Types

All HTML utility types follow a consistent pattern:

1. **Import the utility**: Import the desired HTML utility from 'woby'
2. **Apply to observables**: Use the utility as the second parameter to `$()`
3. **Automatic conversion**: The utility handles conversion between HTML strings and JavaScript values

``tsx
import { $, defaults, customElement, HtmlBoolean, HtmlNumber, HtmlColor } from 'woby'

const StyledCounter = defaults(() => ({
  count: $(0, HtmlNumber),
  enabled: $(true, HtmlBoolean),
  color: $('#000000', HtmlColor)
}), ({ count, enabled, color }) => (
  <div style={() => ({ color: $$(color) })}>
    <span>Count: {count}</span>
    <span>Status: {enabled ? 'Enabled' : 'Disabled'}</span>
  </div>
))

customElement('styled-counter', StyledCounter)
```

### Benefits of HTML Utility Types

1. **Type Safety**: Each utility provides proper type conversion between HTML attributes and JavaScript values
2. **Consistency**: All utilities follow the same pattern and behavior
3. **Automatic Serialization**: Complex values are automatically serialized to/from HTML attributes
4. **Error Handling**: Utilities handle edge cases and invalid values gracefully
5. **Empty String Handling**: All utilities treat empty strings as `undefined` for consistent behavior
6. **Equality Checking**: Each utility implements proper equality checking for value comparison

## Nested Properties

Custom elements support nested properties using both dash-separated attribute names and dot notation:

### Definition

```tsx
const UserCard = defaults(() => ({
  user: {
    name: $('John'),
    details: {
      age: $(30, { type: 'number' as const })
    }
  }
}), ({ user }) => (
  <div>
    <h2>{user.name}</h2>
    <p>Age: {user.details.age}</p>
  </div>
))

customElement('user-card', UserCard)
```

### Usage

```html
<!-- Maps to user.name -->
<user-card user-name="Jane"></user-card>

<!-- Maps to user.details.age -->
<user-card user-details-age="25"></user-card>

<!-- Combined -->
<user-card user-name="Jane" user-details-age="25"></user-card>
```

### Advanced Nested Property Syntax

Woby supports both `$` and `.` notation for nested properties in HTML attributes:

```html
<!-- Using $ notation (works in both HTML and JSX) -->
<counter-element 
  nested$nested$text="Hello World"
  style$font-size="1.5em"
  style$color="red">
</counter-element>

<!-- Using . notation (HTML only) -->
<counter-element 
  nested.nested.text="Hello World"
  style.font-size="1.5em"
  style.color="red">
</counter-element>
```

**Note**: In JSX, only `$` notation is supported to maintain compatibility with JavaScript object property syntax.

### Nested Property Benefits

This feature provides several advantages over traditional frameworks like React and Solid:

1. **Direct Property Setting**: Unlike React where you need to pass complex objects as props, Woby allows you to directly set nested properties through HTML attributes
2. **HTML Compatibility**: You can set deeply nested properties directly in HTML without any JavaScript
3. **Automatic Kebab-Case Conversion**: Property names are automatically converted from kebab-case to camelCase
4. **Framework Uniqueness**: This level of nested property support is not available in React or SolidJS

## Style Properties

Custom elements support CSS style properties using the `style.*` prefix:

### Usage

```html
<counter-element 
  style.color="red"
  style.font-size="1.5em"
  style.background-color="lightblue">
</counter-element>
```

### Mapping

- `style.color` → `element.style.color = 'red'`
- `style.font-size` → `element.style.fontSize = '1.5em'`
- `style.background-color` → `element.style.backgroundColor = 'lightblue'`

### Advanced Style Property Syntax

Woby supports both `$` and `.` notation for style properties:

```html
<!-- Using $ notation -->
<counter-element style$font-size="2em" style$color="blue"></counter-element>

<!-- Using . notation -->
<counter-element style.font-size="2em" style.color="blue"></counter-element>
```

## Shadow DOM

Custom elements use Shadow DOM for encapsulation:

### Implementation Details

1. When a custom element is created, it attaches a shadow root:
   ```javascript
   const shadowRoot = this.attachShadow({ mode: 'open' })
   ```

2. The component is rendered into the shadow root:
   ```javascript
   render(createElement(component, this.props), shadowRoot)
   ```

3. This provides style and DOM encapsulation for the component.

### Slots

Custom elements support the `<slot>` element for content projection:

```tsx
const Card = defaults(() => ({}), ({ children }) => (
  <div class="card">
    <header>
      <slot name="header">Default Header</slot>
    </header>
    <main>
      {children}
    </main>
    <footer>
      <slot name="footer">Default Footer</slot>
    </footer>
  </div>
))

customElement('card-element', Card)
```

```html
<card-element>
  <span slot="header">Custom Header</span>
  <p>Main content</p>
  <span slot="footer">Custom Footer</span>
</card-element>
```

## Context Support

Custom elements have special support for accessing context values from parent components using the `useMountedContext` hook.

### Usage

```tsx
// Create a context
const ValueContext = createContext(0)

// Create a custom element that uses context for rendering only (mount is auto taken care of)
const ContextValue = defaults(() => ({}), () => {
  const context = useMountedContext(ValueContext) //direct use
  return <span>(Context Value = <b>{context}</b>)</span>
})

// Create a custom element that uses context and requires manual mounting
const ProcessedContextValue = defaults(() => ({}), () => {
  const [context, m] = useMountedContext(ValueContext)
  // Must put in {m} mounting component manually to receive context
  return <span>(Processed Context Value = <b>{useMemo(() => $$($$(context)) + ' Processed')}</b>){m}</span>
})

customElement('context-value', ContextValue)
customElement('processed-context-value', ProcessedContextValue)
```

### How it works

1. When a context Provider is used, the context value is stored on the first child node of the parent element
2. Child custom elements can access this context value using `useMountedContext`
3. The `useMountedContext` hook returns a tuple `[context, mount]` where:
   - `context` is the context value
   - `mount` is a comment element that serves as a mounting point for context traversal

### Example

```html
<value-context-provider value="42">
  <value-display></value-display>
</value-context-provider>
```

In this example, the `value-display` custom element will receive the context value of `42` from its parent provider.

### Context Usage Patterns

When using context in custom elements, there are two main patterns:

1. **Direct usage**: When you only need the context value for rendering, you can use `useMountedContext` directly
2. **Manual mounting**: When you need to process the context value or perform other operations, you must manually include the mounting element `{m}` in your JSX

The output of context usage in JSX looks like:

```tsx
<context-value><span>(Context Value = <b>0<!----></b>)</span></context-value>
<span>(Context Value = <b>0<!----></b>)</span>
```

Note that `<Counter>` components cannot be used directly in HTML, only custom elements can be used in HTML.

## HTML Attribute Serialization

Custom elements support custom serialization of observable values to and from HTML attributes using the `toHtml` and `fromHtml` options:

### Hiding Properties from HTML Attributes

To prevent a property from appearing in HTML attributes, use the `toHtml` option with a function that returns `undefined`:

```tsx
const Counter = defaults(() => ({
  value: $(0, { type: 'number' as const }),
  increment: $([() => { value($$(value) + 10) }], { toHtml: o => undefined }), //hide this from html attributes
  title: $('Counter')
}), ({ value, increment, title }) => (
  <div>
    <h1>{title}</h1>
    <p>Count: {value}</p>
    <button onClick={() => increment[0]()}>+</button>
  </div>
))
```

### Object Serialization

To serialize complex objects to and from HTML attributes, use the `toHtml` and `fromHtml` options:

```tsx
const Component = defaults(() => ({
  obj: $({ nested: { text: 'abc' } }, { 
    toHtml: o => JSON.stringify(o), 
    fromHtml: o => JSON.parse(o) 
  }),
  date: $(new Date(), { 
    toHtml: o => o.toISOString(), 
    fromHtml: o => new Date(o) 
  })
}), ({ obj, date }) => (
  <div>
    <p>Object: {() => JSON.stringify($$(obj))}</p>
    <p>Date: {() => $$(date).toString()}</p>
  </div>
))
```

These serialization options allow complex JavaScript objects and Date instances to be properly converted to and from HTML attribute strings, enabling two-way synchronization between HTML attributes and component props.

## Component Defaults and Two-Way Synchronization

This section explains the `defaults` pattern for creating Woby components with proper two-way synchronization between HTML attributes and component props.

When creating custom elements in Woby, the `defaults` function provides seamless two-way synchronization between HTML attributes and component props:

1. **HTML Attributes → Component Props**: When a custom element is used in HTML, attributes are automatically converted to component props
2. **Component Props → HTML Attributes**: When props change programmatically, the corresponding HTML attributes are updated

This synchronization only works when components are properly wrapped with `defaults`. Without this pattern, attributes and props are not synchronized.

**Important**: Custom elements have key differences from ordinary functional components.

## The defaults Function

The `defaults` function wraps a component and attaches default props information, enabling proper custom element behavior.

### Syntax

```typescript
defaults<P>(
  def: () => Partial<P>,
  component: (props: P) => JSX.Element
): ComponentWithDefaults<P>
```

### Parameters

- `def`: A function that returns default props
- `component`: The component function to wrap

### Example

```typescript
import { $, defaults } from 'woby'

interface CounterProps {
  value?: Observable<number>
  increment?: () => void
  decrement?: () => void
  label?: string
}

function def() {
  const value = $(0, { type: 'number' } as const)
  return {
    value,
    increment: () => value(prev => $(prev) + 1),
    decrement: () => value(prev => $(prev) - 1),
    label: 'Counter'
  }
}

const Counter = defaults(def, (props: CounterProps): JSX.Element => {
  // The merge functionality is handled internally by the defaults function
  const { value, increment, decrement, label } = props
  
  return (
    <div>
      <span>{label}: </span>
      <span>{value}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  )
})
```

## How Two-Way Synchronization Works

### HTML Attributes to Component Props

When a custom element is used in HTML:

```html
<counter-element value="5" label="My Counter" obj='{"nested":{"text":"xyz"}}'></counter-element>
```

The attributes are automatically converted to component props:
- `value="5"` becomes `value: $(5)` (with proper type conversion)
- `label="My Counter"` becomes `label: "My Counter"`
- `obj='{"nested":{"text":"xyz"}}'` becomes `obj: $({nested: {text: "xyz"}})` (with JSON parsing)

### Component Props to HTML Attributes

When props change programmatically, the corresponding HTML attributes are updated:

```typescript
const count = $(10)
// When count changes, the HTML attribute is automatically updated
```

## Key Requirements for Synchronization

### 1. Use Observables for Synchronized Props

Only observables can synchronize with HTML attributes:

```typescript
// ✅ Correct - observable for synchronization
function def() {
  return {
    count: $(0, { type: 'number' } as const)  // Will sync both ways
  }
}

// ❌ Incorrect - primitive value won't sync to HTML attributes
function def() {
  return {
    count: 0  // Will NOT sync to HTML attributes
  }
}
```

### 2. Specify Type Information

For proper type conversion, specify the `type` option:

```typescript
// ✅ Correct - with type information
function def() {
  return {
    count: $(0, { type: 'number' } as const),     // Converts "5" to 5
    enabled: $(false, { type: 'boolean' } as const), // Converts "true" to true
    data: $({} as any, { type: 'object' } as const)  // Converts JSON string to object
  }
}

// ❌ Incorrect - no type information
function def() {
  return {
    count: $(0),      // Will be treated as string
    enabled: $(true), // Will be treated as string
    data: $({})       // Will be treated as string
  }
}
```

### 3. Use defaults() Function

Always use the `defaults` function for proper synchronization:

```typescript
// ✅ Correct - with defaults
const Counter = defaults(def, (props: CounterProps): JSX.Element => {
  const { value } = props  // Enables synchronization
  // ...
})
// ❌ Incorrect - no defaults
const Counter = (props: CounterProps): JSX.Element => {
  const { value } = props  // No synchronization
  // ...
}
```

## Handling Different Prop Sources

### Props from Custom Elements (HTML attributes)

When a custom element is instantiated from HTML, the props come from parsed HTML attributes:

```html
<counter-element value="5" label="My Counter" obj='{"nested":{"text":"xyz"}}'></counter-element>
```

In this case:
- Props are created by parsing HTML string attributes
- The `defaults` function combines these parsed props with the defaults from `def()`
- HTML attributes take precedence over defaults

### Props from JSX/TSX Components

When a component is used directly in JSX/TSX:

```typescript
const App = () => {
  const count = $(10)
  return <Counter value={count} label="App Counter" />
}
```

In this case:
- Props are passed directly as JavaScript values
- The `defaults` function combines these props with the defaults from `def()`
- JSX props take precedence over defaults

### Inline Parameter Initialization Conflicts

Using inline parameter initialization can conflict with the `def()` pattern:

```typescript
// ❌ Potential conflict - inline initialization
const Counter = defaults(def, ({ value = $(0) }: CounterProps): JSX.Element => {
  // ...
})
// ✅ Recommended approach - use defaults
const Counter = defaults(def, (props: CounterProps): JSX.Element => {
  const { value } = props
  // ...
})
```

When inline parameters are used:
1. The inline default `$(0)` is applied before the defaults function processes the props
2. This can override the intended behavior from `def()`
3. Custom element synchronization may not work correctly

## Functions and Complex Objects

Functions and complex objects don't appear in HTML attributes and should not be expected to synchronize:

```typescript
interface ComponentProps {
  value: Observable<number>
  // Functions don't appear in HTML attributes - no synchronization
  increment: () => void
  decrement: () => void
  // Complex objects don't appear in HTML attributes - no synchronization
  callbacks: {
    onComplete: () => void
  }
}

function def() {
  return {
    value: $(0, { type: 'number' } as const),
    // Functions are not synchronized
    increment: () => {},
    decrement: () => {},
    // Complex objects are not synchronized
    callbacks: {
      onComplete: () => {}
    }
  }
}
```

For functions that need to be passed to custom elements, store them in observables using array notation:

```typescript
function def() {
  const value = $(0, { type: 'number' } as const)
  return {
    value,
    increment: $([() => { value($$(value) + 10) }], { toHtml: o => undefined }), //hide this from html attributes
  }
}
```

To store a function in an observable, use the array notation `$([() => { /* function body */ }])`. This allows functions to be passed as props to custom elements while keeping them hidden from HTML attributes when the `toHtml: o => undefined` option is used.

## Without Two-Way Synchronization

Components that don't use the `defaults` pattern will not have attribute synchronization:

```typescript
// ❌ No synchronization - attributes and props are not linked
const SimpleCounter = ({ value = $(0) }: { value?: Observable<number> }) => (
  <div>Count: {value}</div>
)

customElement('simple-counter', SimpleCounter)

// In HTML: <simple-counter value="5"></simple-counter>
// The value attribute will NOT be synchronized with the component prop
```

## Complete Example: Counter Component

Here's a complete example showing proper custom element implementation:

```typescript
import { $, $$, defaults, merge, customElement } from 'woby'
import type { Observable, ObservableMaybe } from 'woby'

/**
 * Counter Component Properties
 */
interface CounterProps {
  // Use ObservableMaybe for values that can come from HTML attributes
  count?: ObservableMaybe<number>
  // Functions don't appear in HTML attributes
  increment?: () => void
  decrement?: () => void
  // Regular strings can be used for labels, etc.
  label?: string
  // Complex objects with custom serialization
  obj?: Observable<{ nested: { text: string } }>
  // Dates with custom serialization
  date?: Observable<Date>
}

/**
 * Default props function - required for custom elements
 * This is where you define the type information for synchronization
 */
function def() {
  const value = $(0, { type: 'number' } as const)
  return {
    // Typed observable for two-way synchronization
    count: value,
    // Functions are not synchronized, hidden from HTML attributes
    increment: $([() => { value($$(value) + 1) }], { toHtml: o => undefined }),
    decrement: $([() => { value($$(value) - 1) }], { toHtml: o => undefined }),
    // Regular string (no synchronization needed)
    label: 'Counter',
    // Complex object with custom serialization
    obj: $({ nested: { text: 'abc' } }, { 
      toHtml: o => JSON.stringify(o), 
      fromHtml: o => JSON.parse(o) 
    }),
    // Date with custom serialization
    date: $(new Date(), { 
      toHtml: o => o.toISOString(), 
      fromHtml: o => new Date(o) 
    })
  }
}

/**
 * Counter Component
 * Must use defaults() and merge() for proper custom element behavior
 */
const Counter = defaults(def, (props: CounterProps): JSX.Element => {
  // Critical: Merge props with defaults for two-way synchronization
  const mergedProps = merge(props, def())
  
  const { count, increment, decrement, label, obj, date } = mergedProps
  
  return (
    <div>
      <label>{label}: </label>
      <span>{count}</span>
      <button onClick={() => increment[0]()}>+</button>
      <button onClick={() => decrement[0]()}>-</button>
      <p>Object: {() => JSON.stringify($$(obj))}</p>
      <p>Date: {() => $$(date).toString()}</p>
    </div>
  )
})

// Register as custom element
customElement('counter-element', Counter)

export default Counter
```

## Usage Patterns

### 1. HTML Usage (with synchronization)
```html
<!-- HTML attributes will be converted and synchronized -->
<counter-element 
  count="5" 
  label="My Counter"
  obj='{"nested":{"text":"xyz"}}'
  date="2023-01-01T00:00:00.000Z">
</counter-element>
```

### 2. JSX Usage (with synchronization)
```tsx
const App = () => {
  const count = $(10)
  
  return (
    <counter-element 
      count={count} 
      label="App Counter"
      obj={$({ nested: { text: 'xyz' } }, { 
        toHtml: o => JSON.stringify(o), 
        fromHtml: o => JSON.parse(o) 
      })}
      date={$(new Date())}>
    </counter-element>
  )
}
```

### 3. Pure Component Usage (no synchronization)
```tsx
const App = () => {
  const count = $(10)
  
  return (
    <Counter 
      count={count} 
      label="Direct Component">
    </Counter>
  )
}
```

## Common Pitfalls and Solutions

### 1. Not Using merge() Function
```typescript
// ❌ Wrong - no synchronization
const Counter = defaults(def, (props: CounterProps): JSX.Element => {
  const { count, increment, decrement } = props  // Direct destructuring
  // ...
})
// ✅ Correct - with synchronization
const Counter = defaults(def, (props: CounterProps): JSX.Element => {
  const mergedProps = merge(props, def())  // Merge for synchronization
  const { count, increment, decrement } = mergedProps
  // ...
}, def)
```

### 2. Inline Parameter Initialization
```typescript
// ❌ Wrong - inline initialization breaks custom element behavior
const Counter = defaults(def, ({ count = $(0) }: CounterProps): JSX.Element => {
  // ...
})
// ✅ Correct - use def() function pattern
const Counter = defaults(def, (props: CounterProps): JSX.Element => {
  const mergedProps = merge(props, def())
  const { count } = mergedProps
  // ...
})
```

### 3. Missing Type Information
```typescript
// ❌ Wrong - no type information
function def() {
  return {
    count: $(0)  // Will be treated as string
  }
}

// ✅ Correct - with type information
function def() {
  return {
    count: $(0, { type: 'number' } as const)  // Will be treated as number
  }
}
```

### 4. Not Hiding Functions from HTML Attributes
```typescript
// ❌ Wrong - functions will appear in HTML attributes
function def() {
  return {
    increment: $([() => { /* increment logic */ }]) // Will appear as "[object Object]" in HTML
  }
}

// ✅ Correct - hide functions from HTML attributes
function def() {
  return {
    increment: $([() => { /* increment logic */ }], { toHtml: o => undefined }) // Hidden from HTML
  }
}
```

## Best Practices

1. **Always use the `defaults` pattern** for custom elements
2. **Specify type information** for non-string observables
3. **Use `as const`** with type options for better TypeScript inference
4. **Don't use inline parameter initialization** in custom elements
5. **Only functions and complex objects** should not be synchronized
6. **Test both HTML and JSX usage** to ensure proper synchronization
7. **Use `toHtml: () => undefined`** to hide functions from HTML attributes
8. **Use `toHtml` and `fromHtml`** for complex object serialization
9. **Store functions in observables using array notation** when they need to be passed to custom elements
10. **Properly augment JSX types** for full TypeScript support and IDE autocomplete
11. **Use descriptive JSDoc comments** in type augmentations for better documentation
12. **Include all custom elements** in your type augmentations for comprehensive type safety

### Type Synchronization Reference

| Type | HTML Attribute | Component Prop | Notes |
|------|----------------|----------------|-------|
| number | `"5"` | `$(5)` | Use `{ type: 'number' }` |
| boolean | `"true"` | `$(true)` | Use `{ type: 'boolean' }` |
| string | `"text"` | `"text"` | Default behavior |
| object | `'{"a":1}'` | `$( {a:1} )` | Use `{ type: 'object' }` |
| function | N/A | Function | Not synchronized, use `toHtml: () => undefined` |
| undefined | N/A | `undefined` | Not synchronized |
| Date | `"2023-01-01T00:00:00.000Z"` | `$(new Date(...))` | Use `toHtml` and `fromHtml` options |
| Complex Object | `'{"nested":{"text":"abc"}}'` | `$( {nested: {text: "abc"}} )` | Use `toHtml` and `fromHtml` options |

This approach ensures that your custom elements work seamlessly in both HTML and JSX contexts with proper two-way synchronization.