# PropertyEditor

A dynamic property editor component that renders form fields based on metadata configuration. Features include multiple field types, nested object support, automatic component selection, and internationalization. Perfect for admin panels, form builders, and configuration interfaces.

## Installation

```bash
npm install @ticatec/uniface-element
```

## Import

```typescript
import PropertyEditor from "@ticatec/uniface-element/PropertyEditor";
import { MetaEditorType, type PropertyField } from "@ticatec/uniface-element/PropertyEditor";
```

## Basic Usage

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let userData = {
    name: "John Doe",
    age: 30,
    email: "john@example.com",
    isActive: true
  };
  
  const userFields = [
    {
      name: "name",
      label: "Full Name",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "Enter full name",
        maxLength: 50
      }
    },
    {
      name: "age",
      label: "Age",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        max: 120
      }
    },
    {
      name: "email",
      label: "Email Address",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "Enter email",
        type: "email"
      }
    },
    {
      name: "isActive",
      label: "Active Status",
      type: MetaEditorType.Boolean
    }
  ];
</script>

<PropertyEditor fields={userFields} bind:data={userData} />
```

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `fields` | `Array<PropertyField>` | Required | Array of field definitions |
| `data` | `any` | Required | Data object to edit |

## Type Definitions

### PropertyField
```typescript
interface PropertyField {
  name: string;           // Field name (supports nested with dot notation)
  label: string;          // Display label for the field
  type: MetaEditorType;   // Editor type
  attrs?: any;            // Additional attributes for the editor component
}
```

### MetaEditorType
```typescript
enum MetaEditorType {
  Text = 'Text',                    // Text input
  CommonText = 'CommonText',        // Text input with suggestions
  Number = 'Number',                // Number input
  Date = 'Date',                    // Date picker
  DateTime = 'DateTime',            // Date and time picker
  Options = 'Options',              // Select dropdown
  Boolean = 'Boolean'               // Boolean (true/false)
}
```

## Field Types

### Text Fields

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let textData = {
    title: "",
    description: "",
    keywords: ""
  };
  
  const textFields = [
    {
      name: "title",
      label: "Title",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "Enter title",
        maxLength: 100
      }
    },
    {
      name: "description",
      label: "Description",
      type: MetaEditorType.Text,
      attrs: {
        placeholder: "Enter description",
        rows: 3
      }
    },
    {
      name: "keywords",
      label: "Keywords",
      type: MetaEditorType.CommonText,
      attrs: {
        words: ["javascript", "svelte", "typescript", "frontend"],
        placeholder: "Enter keywords"
      }
    }
  ];
</script>

<div class="text-editor-demo">
  <h3>Text Field Examples</h3>
  <PropertyEditor fields={textFields} bind:data={textData} />
  
  <div class="output">
    <h4>Current Values:</h4>
    <pre>{JSON.stringify(textData, null, 2)}</pre>
  </div>
</div>

<style>
  .text-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .output {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .output pre {
    margin: 8px 0 0 0;
    font-size: 12px;
    overflow-x: auto;
  }
</style>
```

### Number Fields

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let productData = {
    price: 99.99,
    quantity: 10,
    weight: 0,
    rating: 5
  };
  
  const numberFields = [
    {
      name: "price",
      label: "Price ($)",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        precision: 2,
        step: 0.01
      }
    },
    {
      name: "quantity",
      label: "Quantity",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        precision: 0,
        step: 1
      }
    },
    {
      name: "weight",
      label: "Weight (kg)",
      type: MetaEditorType.Number,
      attrs: {
        min: 0,
        precision: 3,
        step: 0.001
      }
    },
    {
      name: "rating",
      label: "Rating",
      type: MetaEditorType.Number,
      attrs: {
        min: 1,
        max: 5,
        precision: 0
      }
    }
  ];
</script>

<div class="number-editor-demo">
  <h3>Number Field Examples</h3>
  <PropertyEditor fields={numberFields} bind:data={productData} />
  
  <div class="summary">
    <h4>Product Summary</h4>
    <p>Price: ${productData.price}</p>
    <p>Quantity: {productData.quantity} items</p>
    <p>Weight: {productData.weight} kg</p>
    <p>Rating: {productData.rating}/5 stars</p>
  </div>
</div>

<style>
  .number-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .summary {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .summary h4 {
    margin: 0 0 12px 0;
  }
  
  .summary p {
    margin: 4px 0;
    font-weight: 500;
  }
</style>
```

### Date and DateTime Fields

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let eventData = {
    startDate: null,
    endDate: null,
    registrationDeadline: null,
    createdAt: new Date().toISOString()
  };
  
  const dateFields = [
    {
      name: "startDate",
      label: "Start Date",
      type: MetaEditorType.Date,
      attrs: {
        placeholder: "Select start date"
      }
    },
    {
      name: "endDate",
      label: "End Date",
      type: MetaEditorType.Date,
      attrs: {
        placeholder: "Select end date"
      }
    },
    {
      name: "registrationDeadline",
      label: "Registration Deadline",
      type: MetaEditorType.DateTime,
      attrs: {
        placeholder: "Select deadline"
      }
    },
    {
      name: "createdAt",
      label: "Created At",
      type: MetaEditorType.DateTime,
      attrs: {
        readonly: true
      }
    }
  ];
</script>

<div class="date-editor-demo">
  <h3>Date Field Examples</h3>
  <PropertyEditor fields={dateFields} bind:data={eventData} />
  
  <div class="event-info">
    <h4>Event Information</h4>
    {#if eventData.startDate && eventData.endDate}
      <p>Duration: {eventData.startDate} to {eventData.endDate}</p>
    {/if}
    {#if eventData.registrationDeadline}
      <p>Registration closes: {eventData.registrationDeadline}</p>
    {/if}
    <p>Created: {new Date(eventData.createdAt).toLocaleDateString()}</p>
  </div>
</div>

<style>
  .date-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .event-info {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .event-info h4 {
    margin: 0 0 12px 0;
  }
  
  .event-info p {
    margin: 4px 0;
  }
</style>
```

### Options and Boolean Fields

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let settingsData = {
    theme: "light",
    language: "en",
    notifications: true,
    autoSave: false,
    privacy: "public"
  };
  
  const settingsFields = [
    {
      name: "theme",
      label: "Theme",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "light", text: "Light Theme" },
          { code: "dark", text: "Dark Theme" },
          { code: "auto", text: "Auto (System)" }
        ]
      }
    },
    {
      name: "language",
      label: "Language",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "en", text: "English" },
          { code: "zh", text: "中文" },
          { code: "es", text: "Español" },
          { code: "fr", text: "Français" }
        ]
      }
    },
    {
      name: "notifications",
      label: "Email Notifications",
      type: MetaEditorType.Boolean
    },
    {
      name: "autoSave",
      label: "Auto Save",
      type: MetaEditorType.Boolean
    },
    {
      name: "privacy",
      label: "Profile Visibility",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "public", text: "Public" },
          { code: "friends", text: "Friends Only" },
          { code: "private", text: "Private" }
        ]
      }
    }
  ];
</script>

<div class="settings-editor-demo">
  <h3>Options and Boolean Fields</h3>
  <PropertyEditor fields={settingsFields} bind:data={settingsData} />
  
  <div class="settings-preview">
    <h4>Current Settings</h4>
    <ul>
      <li>Theme: {settingsData.theme}</li>
      <li>Language: {settingsData.language}</li>
      <li>Email Notifications: {settingsData.notifications ? 'Enabled' : 'Disabled'}</li>
      <li>Auto Save: {settingsData.autoSave ? 'Enabled' : 'Disabled'}</li>
      <li>Profile: {settingsData.privacy}</li>
    </ul>
  </div>
</div>

<style>
  .settings-editor-demo {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .settings-preview {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .settings-preview h4 {
    margin: 0 0 12px 0;
  }
  
  .settings-preview ul {
    margin: 0;
    padding-left: 20px;
  }
  
  .settings-preview li {
    margin: 6px 0;
  }
</style>
```

### Nested Object Support

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let profileData = {
    personal: {
      firstName: "John",
      lastName: "Doe",
      birthDate: null
    },
    contact: {
      email: "john@example.com",
      phone: "",
      address: {
        street: "",
        city: "",
        country: "US"
      }
    },
    preferences: {
      newsletter: true,
      theme: "light"
    }
  };
  
  const nestedFields = [
    {
      name: "personal.firstName",
      label: "First Name",
      type: MetaEditorType.Text,
      attrs: { placeholder: "Enter first name" }
    },
    {
      name: "personal.lastName",
      label: "Last Name",
      type: MetaEditorType.Text,
      attrs: { placeholder: "Enter last name" }
    },
    {
      name: "personal.birthDate",
      label: "Birth Date",
      type: MetaEditorType.Date
    },
    {
      name: "contact.email",
      label: "Email",
      type: MetaEditorType.Text,
      attrs: { type: "email", placeholder: "Enter email" }
    },
    {
      name: "contact.phone",
      label: "Phone",
      type: MetaEditorType.Text,
      attrs: { placeholder: "Enter phone number" }
    },
    {
      name: "contact.address.street",
      label: "Street Address",
      type: MetaEditorType.Text,
      attrs: { placeholder: "Enter street address" }
    },
    {
      name: "contact.address.city",
      label: "City",
      type: MetaEditorType.Text,
      attrs: { placeholder: "Enter city" }
    },
    {
      name: "contact.address.country",
      label: "Country",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "US", text: "United States" },
          { code: "CA", text: "Canada" },
          { code: "UK", text: "United Kingdom" },
          { code: "DE", text: "Germany" },
          { code: "FR", text: "France" }
        ]
      }
    },
    {
      name: "preferences.newsletter",
      label: "Subscribe to Newsletter",
      type: MetaEditorType.Boolean
    },
    {
      name: "preferences.theme",
      label: "Preferred Theme",
      type: MetaEditorType.Options,
      attrs: {
        options: [
          { code: "light", text: "Light" },
          { code: "dark", text: "Dark" }
        ]
      }
    }
  ];
</script>

<div class="nested-editor-demo">
  <h3>Nested Object Editor</h3>
  <PropertyEditor fields={nestedFields} bind:data={profileData} />
  
  <div class="profile-summary">
    <h4>Profile Summary</h4>
    <div class="section">
      <h5>Personal Information</h5>
      <p>Name: {profileData.personal.firstName} {profileData.personal.lastName}</p>
      {#if profileData.personal.birthDate}
        <p>Birth Date: {profileData.personal.birthDate}</p>
      {/if}
    </div>
    
    <div class="section">
      <h5>Contact Information</h5>
      <p>Email: {profileData.contact.email}</p>
      {#if profileData.contact.phone}
        <p>Phone: {profileData.contact.phone}</p>
      {/if}
      <p>Address: {profileData.contact.address.street}, {profileData.contact.address.city}, {profileData.contact.address.country}</p>
    </div>
    
    <div class="section">
      <h5>Preferences</h5>
      <p>Newsletter: {profileData.preferences.newsletter ? 'Subscribed' : 'Not subscribed'}</p>
      <p>Theme: {profileData.preferences.theme}</p>
    </div>
  </div>
</div>

<style>
  .nested-editor-demo {
    max-width: 700px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .profile-summary {
    margin-top: 20px;
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .profile-summary h4 {
    margin: 0 0 16px 0;
  }
  
  .section {
    margin-bottom: 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid #dee2e6;
  }
  
  .section:last-child {
    border-bottom: none;
    margin-bottom: 0;
  }
  
  .section h5 {
    margin: 0 0 8px 0;
    color: #495057;
    font-size: 14px;
    font-weight: 600;
    text-transform: uppercase;
  }
  
  .section p {
    margin: 4px 0;
    font-size: 14px;
  }
</style>
```

### Dynamic Form Builder

```svelte
<script>
  import PropertyEditor, { MetaEditorType } from "@ticatec/uniface-element/PropertyEditor";
  
  let formData = {};
  let formFields = [
    {
      name: "name",
      label: "Name",
      type: MetaEditorType.Text,
      attrs: { placeholder: "Enter your name" }
    }
  ];
  
  const availableFieldTypes = [
    { type: MetaEditorType.Text, label: "Text Input" },
    { type: MetaEditorType.Number, label: "Number Input" },
    { type: MetaEditorType.Date, label: "Date Picker" },
    { type: MetaEditorType.Boolean, label: "Boolean (Yes/No)" },
    { type: MetaEditorType.Options, label: "Dropdown List" }
  ];
  
  function addField() {
    const fieldName = prompt("Enter field name:");
    if (!fieldName) return;
    
    const fieldLabel = prompt("Enter field label:");
    if (!fieldLabel) return;
    
    const fieldType = prompt(`Enter field type (${availableFieldTypes.map(t => t.type).join(', ')}):`);
    const selectedType = availableFieldTypes.find(t => t.type === fieldType);
    
    if (!selectedType) {
      alert("Invalid field type");
      return;
    }
    
    const newField = {
      name: fieldName,
      label: fieldLabel,
      type: selectedType.type,
      attrs: {}
    };
    
    // Add default options for Options type
    if (selectedType.type === MetaEditorType.Options) {
      newField.attrs.options = [
        { code: "option1", text: "Option 1" },
        { code: "option2", text: "Option 2" }
      ];
    }
    
    formFields = [...formFields, newField];
  }
  
  function removeField(index) {
    formFields = formFields.filter((_, i) => i !== index);
  }
  
  function exportFormData() {
    const exportData = {
      fields: formFields,
      data: formData
    };
    
    const blob = new Blob([JSON.stringify(exportData, null, 2)], {
      type: 'application/json'
    });
    
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'form-data.json';
    a.click();
    URL.revokeObjectURL(url);
  }
</script>

<div class="form-builder-demo">
  <div class="builder-header">
    <h3>Dynamic Form Builder</h3>
    <div class="builder-actions">
      <button on:click={addField}>Add Field</button>
      <button on:click={exportFormData}>Export Data</button>
    </div>
  </div>
  
  <div class="builder-content">
    <div class="field-list">
      <h4>Form Fields</h4>
      {#each formFields as field, index}
        <div class="field-item">
          <span>{field.label} ({field.type})</span>
          <button on:click={() => removeField(index)}>Remove</button>
        </div>
      {/each}
    </div>
    
    <div class="form-preview">
      <h4>Form Preview</h4>
      <PropertyEditor fields={formFields} bind:data={formData} />
    </div>
  </div>
  
  <div class="form-data">
    <h4>Form Data</h4>
    <pre>{JSON.stringify(formData, null, 2)}</pre>
  </div>
</div>

<style>
  .form-builder-demo {
    max-width: 900px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
  }
  
  .builder-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding-bottom: 12px;
    border-bottom: 1px solid #eee;
  }
  
  .builder-header h3 {
    margin: 0;
  }
  
  .builder-actions {
    display: flex;
    gap: 8px;
  }
  
  .builder-actions button {
    padding: 8px 16px;
    border: 1px solid #007bff;
    background: white;
    color: #007bff;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .builder-actions button:hover {
    background: #007bff;
    color: white;
  }
  
  .builder-content {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 20px;
    margin-bottom: 20px;
  }
  
  .field-list h4,
  .form-preview h4 {
    margin: 0 0 12px 0;
    font-size: 16px;
  }
  
  .field-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 12px;
    background: #f8f9fa;
    border-radius: 4px;
    margin-bottom: 8px;
  }
  
  .field-item button {
    padding: 4px 8px;
    background: #dc3545;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
  }
  
  .form-preview {
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .form-data {
    padding: 16px;
    background: #f8f9fa;
    border-radius: 4px;
  }
  
  .form-data h4 {
    margin: 0 0 12px 0;
  }
  
  .form-data pre {
    margin: 0;
    font-size: 12px;
    overflow-x: auto;
    max-height: 200px;
    overflow-y: auto;
  }
</style>
```

## Features

- **Multiple Field Types**: Text, Number, Date, DateTime, Options, Boolean, and CommonText
- **Nested Object Support**: Use dot notation for deep object property editing
- **Dynamic Component Selection**: Automatic component selection based on field type
- **Flexible Attributes**: Pass custom attributes to underlying editor components
- **Two-way Data Binding**: Real-time synchronization with data object
- **Internationalization**: Built-in i18n support for labels
- **Extensible**: Easy to add new field types and components

## Field Type Details

### Text Fields
- **Type**: `MetaEditorType.Text`
- **Component**: TextEditor
- **Attributes**: placeholder, maxLength, type, etc.

### CommonText Fields
- **Type**: `MetaEditorType.CommonText`
- **Component**: PromptsTextEditor
- **Attributes**: words (array of suggestions), placeholder

### Number Fields
- **Type**: `MetaEditorType.Number`
- **Component**: NumberEditor
- **Attributes**: min, max, precision, step

### Date Fields
- **Type**: `MetaEditorType.Date`
- **Component**: DatePicker
- **Attributes**: format, placeholder, readonly

### DateTime Fields
- **Type**: `MetaEditorType.DateTime`
- **Component**: DateTimePicker
- **Attributes**: format, placeholder, readonly

### Options Fields
- **Type**: `MetaEditorType.Options`
- **Component**: OptionsSelector
- **Attributes**: options (array of {code, text} objects)

### Boolean Fields
- **Type**: `MetaEditorType.Boolean`
- **Component**: OptionsSelector (with true/false options)
- **Attributes**: Automatically configured with boolean options

## Styling

The PropertyEditor component uses a table-like layout:

```css
/* Property editor container */
.uniface-property-editor {
  /* Your custom styles */
}

/* Header styling */
.table-header {
  display: grid;
  grid-template-columns: 1fr 2fr;
  /* Header styles */
}

/* Content area */
.table-content {
  /* Content styles */
}

/* Individual property rows */
.property_label,
.property_control {
  /* Property row styles */
}
```

## Best Practices

1. **Field Definition**: Keep field definitions in separate constants for reusability
2. **Validation**: Implement validation logic in parent components
3. **Nested Objects**: Use dot notation consistently for nested properties
4. **Type Safety**: Use TypeScript for better type checking
5. **Performance**: Consider memoization for large field arrays
6. **Accessibility**: Ensure proper labels and ARIA attributes

## Browser Support

- Modern browsers with full event support
- Compatible with Svelte 5+
- Grid layout support
- Full TypeScript support

## Related Components

- `TextEditor` - Text input component
- `NumberEditor` - Number input component
- `DatePicker` - Date selection component
- `OptionsSelect` - Dropdown selection component