zs3-ui-components 1.1.11 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of zs3-ui-components might be problematic. Click here for more details.

package/README.md CHANGED
@@ -1,35 +1,19 @@
1
1
  # ZS3 UI Components
2
2
 
3
- **Biblioteca de components web moderna amb sistema de temes, internacionalitzacio i suport per CSS variables.**
3
+ **v1.2.0** · Modern Web Components library with built-in theming, i18n, reactive state, HTTP client, and more.
4
4
 
5
- | | |
6
- |------------|----------------------------------------------|
7
- | **Paquet** | `zs3-ui-components` |
8
- | **Versio** | 1.1.9 |
9
- | **Llicencia** | ISC |
10
- | **Autor** | Manuel Candeal |
11
- | **Repositori** | [github.com/manuelcandeal/zs3-ui-components](https://github.com/manuelcandeal/zs3-ui-components) |
12
- | **Tipus** | ES Module |
5
+ ZS3 UI Components is a zero-dependency UI library based on native **Web Components** (Custom Elements + Shadow DOM). It provides a complete set of UI components and utilities that work in any framework or in plain HTML.
13
6
 
14
7
  ---
15
8
 
16
- ## Taula de continguts
9
+ ## Table of Contents
17
10
 
18
- - [Installacio i importacions](#installacio-i-importacions)
19
- - [Classe ZS3 (`$`)](#classe-zs3-)
20
- - [Utilitats](#utilitats)
21
- - [$Parameters (params)](#parameters-params)
22
- - [$Log (log)](#log-log)
23
- - [$Debug (debug)](#debug-debug)
24
- - [$Storage (storage)](#storage-storage)
25
- - [$Store](#store)
26
- - [HttpClient](#httpclient)
27
- - [Sistema de Temes (ThemeManager)](#sistema-de-temes-thememanager)
28
- - [CSS Variables](#css-variables)
29
- - [Internacionalitzacio ($I18n)](#internacionalitzacio-i18n)
30
- - [Bus d'Events (EventBus)](#bus-devents-eventbus)
31
- - [Contenidor de Dependencies (DIContainer)](#contenidor-de-dependencies-dicontainer)
32
- - [Router (SPA)](#router-spa)
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Dev Setup](#dev-setup)
14
+ - [Two Usage Approaches](#two-usage-approaches)
15
+ - [i18n — Internationalisation](#i18n--internationalisation)
16
+ - [Theme System](#theme-system)
33
17
  - [Components](#components)
34
18
  - [zs3-button](#zs3-button)
35
19
  - [zs3-icon](#zs3-icon)
@@ -37,2749 +21,2186 @@
37
21
  - [zs3-notification](#zs3-notification)
38
22
  - [zs3-modal](#zs3-modal)
39
23
  - [zs3-modal-dialog](#zs3-modal-dialog)
40
- - [zs3-window](#zs3-window)
41
24
  - [zs3-form](#zs3-form)
42
25
  - [zs3-login-form](#zs3-login-form)
43
26
  - [zs3-register-form](#zs3-register-form)
44
27
  - [zs3-recover-password-form](#zs3-recover-password-form)
45
- - [zs3-select-theme](#zs3-select-theme)
28
+ - [zs3-window](#zs3-window)
46
29
  - [zs3-select-locale](#zs3-select-locale)
47
- - [Inline Event Handlers](#inline-event-handlers)
48
- - [Guia de desenvolupament de nous components](#guia-de-desenvolupament-de-nous-components)
49
- - [$BaseComponent](#basecomponent)
50
- - [Cicle de vida](#cicle-de-vida)
51
- - [Metodes disponibles](#metodes-disponibles)
52
- - [Decoradors](#decoradors)
53
- - [Efecte Float](#efecte-float)
54
- - [Efecte Move](#efecte-move)
55
- - [Exemples complets](#exemples-complets)
56
- - [Estructura del projecte](#estructura-del-projecte)
57
- - [Suport i llicencia](#suport-i-llicencia)
30
+ - [zs3-select-theme](#zs3-select-theme)
31
+ - [Utilities](#utilities)
32
+ - [$Store — Reactive State](#store--reactive-state)
33
+ - [EventBus](#eventbus)
34
+ - [$Storage — LocalStorage](#storage--localstorage)
35
+ - [HttpClient](#httpclient)
36
+ - [$Log — Logging](#log--logging)
37
+ - [DIContainer — Dependency Injection](#dicontainer--dependency-injection)
38
+ - [$ — DOM Helper](#--dom-helper)
39
+ - [Decorators](#decorators)
40
+ - [CSS Variables](#css-variables)
58
41
 
59
42
  ---
60
43
 
61
- ## Installacio i importacions
44
+ ## Features
45
+
46
+ - **Web Components natifs** — Shadow DOM, Custom Elements, cap dependència de framework
47
+ - **i18n complet** — traduccions per clau, fallback, detecció de idioma del navegador, actualització automàtica de tots els components
48
+ - **Temes** — light, dark, high-contrast, i temes personalitzats via CSS variables
49
+ - **$Store** — gestió d'estat reactiva amb reducers i subscripcions (similar a Redux)
50
+ - **EventBus** — comunicació desacoblada entre components
51
+ - **HttpClient** — client HTTP amb interceptors, timeout i tipatge TypeScript
52
+ - **DIContainer** — contenidor d'injecció de dependències
53
+ - **$Storage** — wrapper de LocalStorage amb serialització automàtica
54
+ - **$Log** — logging avançat amb nivells i formatació
55
+ - **$ Helper** — selecció i manipulació del DOM
56
+ - **Formularis** — `$Form` genèric i `$LoginForm`, `$RegisterForm`, `$RecoverPasswordForm` preconstruïts
57
+
58
+ ---
62
59
 
63
- ### Installacio
60
+ ## Installation
64
61
 
65
62
  ```bash
66
63
  npm install zs3-ui-components
67
64
  ```
68
65
 
69
- ### Importacio global
70
-
71
- Importa tot el paquet (registra automaticament tots els custom elements):
66
+ Importa els components i el CSS:
72
67
 
73
- ```typescript
74
- import 'zs3-ui-components'
68
+ ```ts
69
+ import 'zs3-ui-components' // registra tots els Custom Elements
70
+ import 'zs3-ui-components/dist/zs3.css' // variables CSS i estils base
75
71
  ```
76
72
 
77
- ### Importacio individual
73
+ O importa selectivament:
78
74
 
79
- Importa nomes el que necessitis:
80
-
81
- ```typescript
82
- // Utilitats
83
- import { $, params, log, debug, storage, $env } from 'zs3-ui-components'
75
+ ```ts
76
+ import { $Button, $Form, $ModalDialog, i18n, $Store, themeManager } from 'zs3-ui-components'
77
+ ```
84
78
 
85
- // Sistema de temes
86
- import { themeManager, getCSSVariable, setCSSVariable, initThemeSystem } from 'zs3-ui-components'
79
+ ---
87
80
 
88
- // Internacionalitzacio
89
- import { i18n } from 'zs3-ui-components'
81
+ ## Dev Setup
90
82
 
91
- // Store (gestió d'estat global)
92
- import { $Store } from 'zs3-ui-components'
93
- import type { Listener, Reducer } from 'zs3-ui-components'
94
-
95
- // Bus d'events i contenidor de dependencies
96
- import { eventBus, EventBus, diContainer, DIContainer } from 'zs3-ui-components'
97
-
98
- // Router SPA
99
- import { Router } from 'zs3-ui-components'
100
- import type { Route, RouteGuard, RouteContext } from 'zs3-ui-components'
101
-
102
- // Components (classes)
103
- import {
104
- $Button,
105
- $Icon,
106
- $Toolbar,
107
- $Notification,
108
- $Modal,
109
- $ModalDialog,
110
- $Window,
111
- $Form,
112
- $LoginForm,
113
- $RegisterForm,
114
- $RecoverPasswordForm,
115
- $SelectTheme,
116
- $SelectLocale,
117
- } from 'zs3-ui-components'
118
-
119
- // Efectes
120
- import { float, move, disableMove, isMoveEnabled, getMoveDirection } from 'zs3-ui-components'
121
-
122
- // Base component i decoradors (per crear nous components)
123
- import { $BaseComponent, Component, ObservedAttributes, Float, Move, Attr } from 'zs3-ui-components'
124
-
125
- // Tipus
126
- import type {
127
- ThemeType,
128
- I18nConfig,
129
- EventDescription,
130
- BaseComponentOptions,
131
- AttributeChangeCallback,
132
- FloatPosition,
133
- FloatConfig,
134
- FloatEventDetail,
135
- MoveDirection,
136
- MoveConfig,
137
- MoveBoundary,
138
- MoveEventDetail,
139
- ButtonConfig,
140
- IconConfig,
141
- ToolbarConfig,
142
- NotificationConfig,
143
- NotificationPosition,
144
- NotificationType,
145
- ModalConfig,
146
- ModalSize,
147
- ModalDialogConfig,
148
- DialogAction,
149
- WindowConfig,
150
- FormFieldType,
151
- FormFieldOption,
152
- FormFieldValidation,
153
- FormFieldConfig,
154
- FormConfig,
155
- ValidationResult,
156
- } from 'zs3-ui-components'
83
+ ```bash
84
+ git clone https://github.com/your-org/zs3-ui-components
85
+ cd zs3-ui-components
86
+ npm install
87
+ npm run dev # servidor de dev a http://localhost:5173
88
+ npm run build # build de la llibreria a dist/
89
+ npm run lint # ESLint + stylelint
157
90
  ```
158
91
 
159
- ---
160
-
161
- ## Classe ZS3 (`$`)
92
+ El directori `dev/` conté tres pàgines de demo:
162
93
 
163
- La classe `ZS3` proporciona una API similar a jQuery per a la manipulacio d'elements DOM.
94
+ | Pàgina | Descripció |
95
+ |--------|------------|
96
+ | `dev/index.html` | Demo original (layout amb navegació) |
97
+ | `dev/index2.html` | **Demo completa** — HTML inline + JavaScript |
98
+ | `dev/index3.html` | **Demo completa** — tot creat des de TypeScript |
164
99
 
165
- ### Selectors
100
+ ---
166
101
 
167
- ```typescript
168
- // Seleccionar elements amb la funcio $
169
- const element = $('selector') // Retorna ZS3 | null (cerca des de document)
170
- const element = $('selector', rootEl) // Cerca dins d'un HTMLElement, Document o ShadowRoot
171
- const element = new ZS3('selector') // Constructor directe (cerca des de document)
172
- const element = new ZS3('selector', rootEl) // Constructor amb element arrel (HTMLElement, Document o ShadowRoot)
102
+ ## Two Usage Approaches
173
103
 
174
- // Crear ZS3 des d'un HTMLElement
175
- const zs3 = ZS3.fromElement(htmlElement)
176
- ```
104
+ ZS3 UI suporta dos estils d'ús que es poden combinar lliurement.
177
105
 
178
- El segon parametre (`root`) es opcional i per defecte es `document`. Accepta `Document`, `HTMLElement` o `ShadowRoot`, la qual cosa permet limitar la cerca a un subarbre del DOM concret, incloent shadow roots de Web Components:
106
+ ### Enfocament 1 HTML Inline (`index2.html` / `index2.ts`)
179
107
 
180
- ```typescript
181
- const container = document.getElementById('app')
182
- const buttons = $('button', container) // Nomes els botons dins de #app
108
+ Defineix els components directament a l'HTML. Útil quan treballes amb plantilles HTML, SSR o CMS.
183
109
 
184
- // Dins d'un Web Component (shadow DOM)
185
- const btn = $('button', this.root) // this.root pot ser ShadowRoot o HTMLElement
186
- btn?.addEvent('click', handler)
110
+ **`index2.html`:**
111
+ ```html
112
+ <!doctype html>
113
+ <html lang="ca">
114
+ <head>
115
+ <meta charset="UTF-8" />
116
+ <title>Demo 2 — Inline</title>
117
+ </head>
118
+ <body>
119
+ <header>
120
+ <zs3-select-locale zs3-position="position:relative;"></zs3-select-locale>
121
+ <zs3-select-theme zs3-position="position:relative;"></zs3-select-theme>
122
+ </header>
123
+
124
+ <!-- Component declarat en HTML -->
125
+ <zs3-button variant="primary" icon="save" position="left"
126
+ zs3-click="document.getElementById('out').textContent = 'Clicat!'">
127
+ Guardar
128
+ </zs3-button>
129
+ <div id="out"></div>
130
+
131
+ <!-- Modal declarat en HTML -->
132
+ <zs3-modal id="my-modal" size="medium" backdrop close-on-escape>
133
+ <div style="padding:1.5rem">
134
+ <h3>Modal</h3>
135
+ <p>Contingut del modal.</p>
136
+ <zs3-button variant="primary"
137
+ zs3-click="document.getElementById('my-modal').$.hide()">
138
+ Tancar
139
+ </zs3-button>
140
+ </div>
141
+ </zs3-modal>
142
+
143
+ <zs3-button zs3-click="document.getElementById('my-modal').$.show()">
144
+ Obrir modal
145
+ </zs3-button>
146
+
147
+ <script type="module" src="./src/js/index2.ts"></script>
148
+ </body>
149
+ </html>
187
150
  ```
188
151
 
189
- ### Propietats
152
+ **`index2.ts`** — inicialitza i18n, $Store, HttpClient, i exposa funcions globals:
153
+ ```ts
154
+ import '../../../src/css/zs3.css'
155
+ import { i18n, themeManager, log, storage, eventBus,
156
+ $Store, HttpClient, diContainer,
157
+ $ModalDialog, $Notification, $Icon } from '../../../src'
158
+ import translations from '../../i18n/i18n.json'
190
159
 
191
- | Propietat | Tipus | Descripcio |
192
- |-----------|-------|------------|
193
- | `$` | `HTMLElement` | Primer element seleccionat |
194
- | `$$` | `HTMLElement[]` | Tots els elements seleccionats |
160
+ // Inicialitza i18n
161
+ i18n.loadMultiple(translations)
162
+ i18n.init({ locale: 'ca', fallbackLocale: 'en' })
195
163
 
196
- ### Metodes
164
+ // Exposa funcions globals per als handlers inline de l'HTML
165
+ window.showDialogAlert = async () => {
166
+ await $ModalDialog.alertI18n({
167
+ i18nTitle: 'section.dialog.alert.title',
168
+ i18nMessage: 'section.dialog.alert.message',
169
+ })
170
+ }
197
171
 
198
- #### Contingut
172
+ window.addEventListener('DOMContentLoaded', () => {
173
+ log.info('Demo 2 iniciada')
174
+ setupIconsGrid()
175
+ })
199
176
 
200
- | Metode | Signatura | Descripcio |
201
- |--------|-----------|------------|
202
- | `html()` | `(content?: string) => ZS3 \| string` | Get/set innerHTML. Sense argument retorna el contingut HTML; amb argument l'estableix i retorna ZS3 per encadenar. |
203
- | `text()` | `(content?: string) => ZS3 \| string` | Get/set innerText. |
204
- | `content()` | `(cont?: string) => ZS3 \| string \| null` | Get/set textContent. |
177
+ function setupIconsGrid(): void {
178
+ const grid = document.getElementById('icons-grid')
179
+ if (!grid) return
180
+ $Icon.getAvailableIcons().forEach((name) => {
181
+ const card = document.createElement('div')
182
+ card.className = 'demo-icon-card'
183
+ card.innerHTML = `<zs3-icon name="${name}" size="medium"></zs3-icon><span>${name}</span>`
184
+ card.addEventListener('click', () =>
185
+ $Notification.info(`Icona: ${name}`, { position: 'bottom-right', duration: 2000 })
186
+ )
187
+ grid.appendChild(card)
188
+ })
189
+ }
190
+ ```
205
191
 
206
- #### Visibilitat i estat
192
+ ### Enfocament 2 — TypeScript Programàtic (`index3.html` / `index3.ts`)
207
193
 
208
- | Metode | Signatura | Descripcio |
209
- |--------|-----------|------------|
210
- | `hide()` | `() => ZS3` | Amaga els elements (`display: none`). Guarda el display original. |
211
- | `show()` | `() => ZS3` | Mostra els elements. Restaura el display original. |
212
- | `hidden()` | `() => boolean` | Retorna `true` si el primer element esta amagat. |
213
- | `visible()` | `() => boolean` | Retorna `true` si el primer element es visible. |
214
- | `disable()` | `() => ZS3` | Desactiva els elements (disabled + aria-disabled + classe .disabled). |
215
- | `enable()` | `() => ZS3` | Activa els elements. |
194
+ Crea tots els components i la interfície des de TypeScript. Ideal per SPA, aplicacions dinàmiques o quan es vol control total sense HTML preescrit.
216
195
 
217
- #### Classes CSS
196
+ **`index3.html`** mínim:
197
+ ```html
198
+ <!doctype html>
199
+ <html lang="ca">
200
+ <head>
201
+ <meta charset="UTF-8" />
202
+ <title>Demo 3 — TypeScript</title>
203
+ </head>
204
+ <body>
205
+ <div id="app"></div>
206
+ <script type="module" src="./src/js/index3.ts"></script>
207
+ </body>
208
+ </html>
209
+ ```
218
210
 
219
- | Metode | Signatura | Descripcio |
220
- |--------|-----------|------------|
221
- | `addClass()` | `(className: string) => ZS3` | Afegeix una classe CSS. |
222
- | `removeClass()` | `(className: string) => ZS3` | Elimina una classe CSS. |
223
- | `toggleClass()` | `(className: string) => ZS3` | Alterna una classe CSS. |
224
- | `hasClass()` | `(className: string) => boolean` | Verifica si el primer element te la classe. |
211
+ **`index3.ts`** construeix tota la UI programàticament:
212
+ ```ts
213
+ import '../../../src/css/zs3.css'
214
+ import { i18n, $Button, $Icon, $Form, $ModalDialog,
215
+ $Notification, $Store, HttpClient, eventBus,
216
+ storage, log, diContainer, themeManager,
217
+ $Modal, $Toolbar, $LoginForm, $RegisterForm,
218
+ $RecoverPasswordForm, $Window, $ } from '../../../src'
219
+ import translations from '../../i18n/i18n.json'
225
220
 
226
- #### Events
221
+ i18n.loadMultiple(translations)
222
+ i18n.init({ locale: 'ca', fallbackLocale: 'en' })
227
223
 
228
- | Metode | Signatura | Descripcio |
229
- |--------|-----------|------------|
230
- | `addEvent()` | `(event: string, callback: (e: Event) => void) => ZS3` | Afegeix un event listener (capture: true). |
231
- | `removeEvent()` | `(event: string, callback: (e: Event) => void) => ZS3` | Elimina un event listener. |
232
- | `once()` | `(event: string, callback: (e: Event) => void) => ZS3` | Afegeix un listener que s'executa nomes una vegada. |
224
+ // Helper per crear elements HTML tipats
225
+ function el<K extends keyof HTMLElementTagNameMap>(
226
+ tag: K,
227
+ attrs: Record<string, string> = {},
228
+ ...children: (Node | string)[]
229
+ ): HTMLElementTagNameMap[K] {
230
+ const e = document.createElement(tag)
231
+ for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v)
232
+ for (const c of children) e.append(c)
233
+ return e
234
+ }
233
235
 
234
- #### Valors i dimensions
236
+ // Helper per crear botons
237
+ function btn(text: string, variant?: string, icon?: string, position?: string): $Button {
238
+ const b = new $Button()
239
+ b.textContent = text
240
+ if (variant) b.variant = variant as $Button['variant']
241
+ if (icon) b.icon = icon
242
+ if (position) b.position = position as 'left' | 'right'
243
+ return b
244
+ }
235
245
 
236
- | Metode | Signatura | Descripcio |
237
- |--------|-----------|------------|
238
- | `value()` | `(val?: string) => ZS3 \| string` | Get/set value per a inputs. |
239
- | `rect()` | `(n?: number) => DOMRect \| null` | Obte el bounding rect d'un element (per index). |
240
- | `height()` | `(value?: string) => ZS3 \| number` | Get/set altura. Sense argument retorna pixels (number). |
241
- | `width()` | `(value?: string) => ZS3 \| number` | Get/set amplada. |
242
- | `top()` | `(value?: string) => ZS3 \| string` | Get/set propietat CSS top. |
243
- | `left()` | `(value?: string) => ZS3 \| string` | Get/set propietat CSS left. |
244
- | `scrollTop()` | `(value?: number) => ZS3 \| number` | Get/set scrollTop. |
245
- | `scrollLeft()` | `(value?: number) => ZS3 \| number` | Get/set scrollLeft. |
246
- | `scrollHeight()` | `(n?: number) => number` | Obte scrollHeight d'un element. |
247
- | `clientHeight()` | `(n?: number) => number` | Obte clientHeight d'un element. |
246
+ window.addEventListener('DOMContentLoaded', () => {
247
+ injectStyles()
248
+ const app = document.getElementById('app')!
249
+ app.append(buildHeader())
250
+
251
+ const main = el('main', { class: 'demo-main' })
252
+ main.append(
253
+ buildButtons(),
254
+ buildIcons(),
255
+ buildToolbar(),
256
+ buildNotifications(),
257
+ buildModal(),
258
+ buildModalDialog(),
259
+ buildForm(),
260
+ buildAuthForms(),
261
+ buildWindow(),
262
+ buildUtilities(),
263
+ )
264
+ app.append(main)
265
+ })
266
+ ```
248
267
 
249
- #### Manipulacio DOM
268
+ ---
250
269
 
251
- | Metode | Signatura | Descripcio |
252
- |--------|-----------|------------|
253
- | `append()` | `(child: HTMLElement \| string) => ZS3` | Afegeix un fill al final. Accepta HTML string o HTMLElement. |
254
- | `prepend()` | `(sibling: HTMLElement \| string) => ZS3` | Afegeix un element al principi. |
255
- | `appendChild()` | `(htmlElement: HTMLElement) => ZS3` | Afegeix un HTMLElement com a fill. |
256
- | `remove()` | `() => ZS3` | Elimina els elements del DOM. |
257
- | `blur()` | `() => ZS3` | Treu el focus dels elements. |
258
- | `focus()` | `() => ZS3` | Dona focus al primer element. |
259
- | `clone()` | `(deep?: boolean) => HTMLElement \| null` | Clona el primer element (deep per defecte). |
270
+ ## i18n Internationalisation
271
+
272
+ El sistema i18n de ZS3 UI és complet i integrat a tots els components. Gestiona traduccions per clau, locale de fallback, persistència a localStorage, detecció automàtica del navegador, i actualització automàtica de tots els components en canviar d'idioma.
273
+
274
+ ### Fitxer de traduccions
275
+
276
+ Les traduccions s'organitzen per idioma en un fitxer JSON:
277
+
278
+ ```json
279
+ {
280
+ "ca": {
281
+ "app.title": "ZS3 Components",
282
+ "section.auth.login": "Iniciar sessió",
283
+ "section.auth.register": "Registrar-se",
284
+ "section.auth.recover": "Recuperar contrasenya",
285
+ "form.title": "Formulari de registre",
286
+ "form.description": "Completa el formulari per registrar-te.",
287
+ "form.field.name": "Nom",
288
+ "form.field.name.placeholder": "Introdueix el teu nom",
289
+ "form.field.email": "Correu electrònic",
290
+ "form.field.country": "País",
291
+ "form.field.country.placeholder": "Selecciona un país",
292
+ "form.country.spain": "Espanya",
293
+ "form.country.france": "França",
294
+ "section.dialog.alert.title": "Títol de l'alerta",
295
+ "section.dialog.alert.message": "Aquest és un missatge d'alerta important."
296
+ },
297
+ "en": {
298
+ "app.title": "ZS3 Components",
299
+ "section.auth.login": "Sign in",
300
+ "section.auth.register": "Register",
301
+ "section.auth.recover": "Recover password",
302
+ "form.title": "Registration form",
303
+ "form.description": "Complete the form to register.",
304
+ "form.field.name": "Name",
305
+ "form.field.name.placeholder": "Enter your name",
306
+ "form.field.email": "Email",
307
+ "form.field.country": "Country",
308
+ "form.field.country.placeholder": "Select a country",
309
+ "form.country.spain": "Spain",
310
+ "form.country.france": "France",
311
+ "section.dialog.alert.title": "Alert title",
312
+ "section.dialog.alert.message": "This is an important alert message."
313
+ }
314
+ }
315
+ ```
260
316
 
261
- #### Atributs i dades
317
+ ### Inicialització
262
318
 
263
- | Metode | Signatura | Descripcio |
264
- |--------|-----------|------------|
265
- | `attr()` | `(name: string, value?: string) => ZS3 \| string \| null` | Get/set un atribut HTML. |
266
- | `removeAttr()` | `(name: string) => ZS3` | Elimina un atribut. |
267
- | `hasAttr()` | `(name: string) => boolean` | Verifica si el primer element te un atribut. |
268
- | `data()` | `(key: string, value?: string) => ZS3 \| string \| null` | Get/set data attributes (`dataset`). |
319
+ ```ts
320
+ import { i18n } from 'zs3-ui-components'
321
+ import translations from './i18n.json'
269
322
 
270
- #### Estils
323
+ // Carrega totes les traduccions d'una vegada
324
+ i18n.loadMultiple(translations)
271
325
 
272
- | Metode | Signatura | Descripcio |
273
- |--------|-----------|------------|
274
- | `style()` | `(styles: Record<string,string> \| string, value?: string) => ZS3 \| string` | Get/set estils CSS. Accepta objecte d'estils, clau+valor, o clau sola (getter). |
326
+ // Inicialitza amb locale per defecte i fallback
327
+ i18n.init({
328
+ locale: 'ca',
329
+ fallbackLocale: 'en',
330
+ })
331
+ ```
275
332
 
276
- #### Navegacio
333
+ `init()` també accepta `translations` directament:
277
334
 
278
- | Metode | Signatura | Descripcio |
279
- |--------|-----------|------------|
280
- | `parent()` | `() => ZS3 \| null` | Obte l'element pare. |
281
- | `children()` | `() => ZS3 \| null` | Obte els fills. |
282
- | `find()` | `(selector: string) => ZS3 \| null` | Cerca elements dins dels seleccionats. |
335
+ ```ts
336
+ i18n.init({
337
+ locale: 'ca',
338
+ fallbackLocale: 'en',
339
+ translations: {
340
+ ca: { 'hello': 'Hola' },
341
+ en: { 'hello': 'Hello' },
342
+ },
343
+ })
344
+ ```
283
345
 
284
- #### Col-leccio
346
+ ### API de l'objecte `i18n`
285
347
 
286
- | Metode | Signatura | Descripcio |
287
- |--------|-----------|------------|
288
- | `length()` | `() => number` | Retorna el nombre d'elements seleccionats. |
289
- | `eq()` | `(index: number) => ZS3 \| null` | Obte un element per index com a ZS3. |
290
- | `forEach()` | `(callback: (element: ZS3, index: number) => void) => ZS3` | Itera elements com a instancies ZS3. |
291
- | `each()` | `(callback: (element: HTMLElement, index: number) => void) => ZS3` | Itera elements com a HTMLElement. |
292
- | `[Symbol.iterator]` | `*() => Iterator<ZS3>` | Fa ZS3 iterable amb `for...of` i spread operator. |
293
-
294
- ### Metodes estatics
348
+ | Mètode | Descripció |
349
+ |--------|------------|
350
+ | `i18n.init(config)` | Inicialitza amb locale, fallback i traduccions |
351
+ | `i18n.loadMultiple(obj)` | Carrega un objecte `{ locale: { clau: valor } }` |
352
+ | `i18n.t('clau')` | Retorna la traducció de la clau |
353
+ | `i18n.setLocale('en')` | Canvia l'idioma i actualitza tots els components |
354
+ | `i18n.getLocale()` | Retorna l'idioma actiu |
355
+ | `i18n.updateElements()` | Força l'actualització de tots els elements del document |
356
+ | `i18n.updateRoot(shadowRoot)` | Actualitza els elements dins d'un shadow root específic |
295
357
 
296
- | Metode | Signatura | Descripcio |
297
- |--------|-----------|------------|
298
- | `ZS3.version()` | `() => string` | Retorna la versio de la biblioteca. |
299
- | `ZS3.fromElement()` | `(element: HTMLElement) => ZS3` | Crea una instancia ZS3 des d'un HTMLElement. |
300
-
301
- ### `$env()`
302
-
303
- Retorna informacio de l'entorn d'execucio:
304
-
305
- ```typescript
306
- const env = $env()
307
- // { userAgent: '...', language: 'ca', platform: 'Win32', ZS3: '1.0.6' }
308
- ```
358
+ ```ts
359
+ // Traduir una clau
360
+ const text = i18n.t('form.field.name') // "Nom"
309
361
 
310
- ---
362
+ // Canviar idioma (actualitza tots els components automàticament)
363
+ i18n.setLocale('en')
311
364
 
312
- ## Utilitats
365
+ // Obtenir l'idioma actiu
366
+ console.log(i18n.getLocale()) // → "en"
367
+ ```
313
368
 
314
- ### $Parameters (params)
369
+ ### Escolta el canvi d'idioma
315
370
 
316
- Gestio de parametres d'URL.
371
+ ```ts
372
+ // Enfocament HTML/inline (index2.ts)
373
+ window.addEventListener('zs3-locale-change', () => {
374
+ log.info(`Idioma canviat a: ${i18n.getLocale()}`)
375
+ })
317
376
 
318
- ```typescript
319
- import { params } from 'zs3-ui-components'
377
+ // Actualitzar text de botons programàtics quan canvia l'idioma
378
+ window.addEventListener('zs3-locale-change', () => {
379
+ bLogin.setText(i18n.t('section.auth.login'))
380
+ bRegister.setText(i18n.t('section.auth.register'))
381
+ })
320
382
  ```
321
383
 
322
- | Metode | Signatura | Descripcio |
323
- |--------|-----------|------------|
324
- | `getAll()` | `() => Record<string, string>` | Obte tots els parametres d'URL com a objecte. |
325
- | `get()` | `(key: string) => string \| Record<string,string>` | Obte el valor d'un parametre. Si `key` es buit, retorna tots. |
326
- | `set()` | `(key: string, value: string) => void` | Estableix un parametre d'URL (pushState). |
327
- | `delete()` | `(key: string) => void` | Elimina un parametre d'URL. |
328
- | `has()` | `(key: string) => boolean` | Verifica si existeix un parametre. |
384
+ ### `data-i18n` Traduccions en HTML
329
385
 
330
- **Exemple:**
386
+ Afegeix `data-i18n="clau"` a qualsevol element i el seu `textContent` s'actualitzarà automàticament en canviar l'idioma:
331
387
 
332
- ```typescript
333
- params.set('page', '2')
334
- const page = params.get('page') // '2'
335
- params.has('page') // true
336
- params.delete('page')
337
- const all = params.getAll() // { ... }
388
+ ```html
389
+ <!-- El text s'actualitza sol en canviar l'idioma -->
390
+ <h1 data-i18n="app.title">ZS3 Components</h1>
391
+ <p data-i18n="form.description">Text per defecte</p>
338
392
  ```
339
393
 
340
- ### $Log (log)
394
+ Per traduir **atributs** (aria-label, placeholder, title…):
341
395
 
342
- Sistema de logging amb nivells, timestamps i colors a la consola.
396
+ ```html
397
+ <!-- Tradueix l'atribut aria-label -->
398
+ <zs3-icon name="bell" data-i18n-attr='{"aria-label":"nav.notifications"}'></zs3-icon>
343
399
 
344
- ```typescript
345
- import { log } from 'zs3-ui-components'
400
+ <!-- Tradueix el placeholder -->
401
+ <input data-i18n-attr='{"placeholder":"form.field.name.placeholder"}' />
346
402
  ```
347
403
 
348
- | Metode | Signatura | Descripcio |
349
- |--------|-----------|------------|
350
- | `info()` | `(message: string, obj?: unknown) => void` | Missatge informatiu (verd). |
351
- | `warn()` | `(message: string, obj?: unknown) => void` | Advertencia (taronja). |
352
- | `error()` | `(message: string, obj?: unknown) => void` | Error (vermell). |
353
- | `debug()` | `(message: string, obj?: unknown) => void` | Debug (blau). |
354
- | `success()` | `(message: string, obj?: unknown) => void` | Exit (lima). |
355
- | `setEnabled()` | `(enabled: boolean) => void` | Activa/desactiva el logging. |
356
- | `setLevel()` | `(level: 'all' \| 'info' \| 'warn' \| 'error') => void` | Estableix el nivell minim de logging. |
404
+ Tots dos sistemes funcionen dins de **Shadow DOM** — `i18n.updateElements()` escaneja automàticament els shadow roots de tots els components registrats.
357
405
 
358
- **Exemple:**
406
+ ### i18n en components TypeScript
359
407
 
360
- ```typescript
361
- log.info('Aplicacio iniciada')
362
- log.warn('Memoria alta', { used: '90%' })
363
- log.error('No es pot connectar')
364
- log.debug('Valor de x:', { x: 42 })
365
- log.success('Operacio completada')
408
+ Tots els components que extenen `$BaseComponent` exposen el mètode `this.t(key)`:
366
409
 
367
- log.setLevel('warn') // Nomes mostra warn i error
368
- log.setEnabled(false) // Desactiva tot el logging
410
+ ```ts
411
+ // Dins d'un component personalitzat
412
+ class MyComponent extends $BaseComponent {
413
+ protected render(): void {
414
+ const label = this.t('my.label.key') // traducció automàtica
415
+ this.setHTML(`<button>${label}</button>`)
416
+ }
417
+ }
369
418
  ```
370
419
 
371
- ### $Debug (debug)
420
+ ### i18n en atributs de components
372
421
 
373
- Utilitat per a debugging d'esdeveniments DOM en temps real.
422
+ Tots els components accepten atributs `i18n-*` per les seves propietats de text:
374
423
 
375
- ```typescript
376
- import { debug } from 'zs3-ui-components'
424
+ ```html
425
+ <!-- HTML inline -->
426
+ <zs3-form
427
+ i18n-title="form.title"
428
+ i18n-description="form.description"
429
+ i18n-accept-text="zs3.form.accept"
430
+ i18n-cancel-text="zs3.form.cancel">
431
+ </zs3-form>
432
+
433
+ <zs3-modal-dialog
434
+ i18n-title="section.dialog.confirm.title"
435
+ i18n-message="section.dialog.confirm.message"
436
+ i18n-accept-text="zs3.dialog.accept"
437
+ i18n-cancel-text="zs3.dialog.cancel">
438
+ </zs3-modal-dialog>
439
+
440
+ <zs3-window i18n-title="form.title"></zs3-window>
441
+ <zs3-notification i18n-title="section.notification.success.title"
442
+ i18n-message="section.notification.success.message">
443
+ </zs3-notification>
377
444
  ```
378
445
 
379
- El mateix parell `selector:eventType` pot estar actiu en múltiples roots simultàniament.
446
+ ```ts
447
+ // TypeScript programàtic (index3.ts)
448
+ const form = new $Form()
449
+ form.setAttribute('i18n-title', 'form.title')
450
+ form.setAttribute('i18n-description', 'form.description')
380
451
 
381
- | Metode | Signatura | Descripcio |
382
- |--------|-----------|------------|
383
- | `start()` | `(selector: string, eventType: keyof HTMLElementEventMap, root?: Document \| HTMLElement \| ShadowRoot) => void` | Inicia el debug. Es poden registrar múltiples roots per al mateix selector+event. |
384
- | `stop()` | `(selector: string, eventType: keyof HTMLElementEventMap, root?: Document \| HTMLElement \| ShadowRoot) => void` | Atura el debug per al root indicat. Si hi ha més roots actius per al mateix selector+event, els altres continuen. |
385
- | `stopAll()` | `() => void` | Atura tot el debugging, per a tots els selectors, events i roots actius. |
386
- | `list()` | `() => string[]` | Llista tots els debugs actius en format `selector:eventType`. Si hi ha múltiples roots per al mateix parell, apareix una entrada per cada root. |
452
+ const dialog = new $ModalDialog()
453
+ dialog.setAttribute('i18n-title', 'section.dialog.confirm.title')
454
+ dialog.setAttribute('i18n-message', 'section.dialog.confirm.message')
455
+ dialog.setAttribute('i18n-accept-text', 'zs3.dialog.accept')
387
456
 
388
- **Exemple:**
457
+ const win = new $Window()
458
+ win.setAttribute('i18n-title', 'form.title')
459
+ ```
389
460
 
390
- ```typescript
391
- const containerA = document.getElementById('sectionA')!
392
- const containerB = document.getElementById('sectionB')!
461
+ ### Traduccions internes del framework
393
462
 
394
- debug.start('.btn', 'click', containerA) // debug de .btn dins de sectionA
395
- debug.start('.btn', 'click', containerB) // debug de .btn dins de sectionB (root diferent)
396
- debug.start('.btn', 'click', containerA) // ignorat, ja estava registrat
463
+ El framework inclou traduccions per defecte per als textos interns dels components (botons, etiquetes, etc.):
397
464
 
398
- debug.list() // ['.btn:click', '.btn:click'] (una entrada per root actiu)
465
+ ```ts
466
+ import { defaultTranslations } from 'zs3-ui-components'
399
467
 
400
- debug.stop('.btn', 'click', containerA) // atura nomes containerA, containerB segueix actiu
401
- debug.stopAll() // atura tot
468
+ // Les claus internes del framework comencen per "zs3."
469
+ // zs3.form.accept, zs3.form.cancel
470
+ // zs3.dialog.accept, zs3.dialog.cancel, zs3.dialog.close
471
+ // zs3.login.title, zs3.login.email, zs3.login.password, zs3.login.submit
472
+ // zs3.register.title, zs3.register.submit
473
+ // zs3.recover.title, zs3.recover.submit
402
474
  ```
403
475
 
404
- ### $Storage (storage)
476
+ Pots sobreescriure qualsevol clau interna carregant les teves pròpies traduccions amb `i18n.loadMultiple()`.
405
477
 
406
- Wrapper per localStorage amb serialitzacio JSON automatica.
478
+ ### Detecció automàtica del navegador
407
479
 
408
- ```typescript
409
- import { storage } from 'zs3-ui-components'
480
+ Si no s'especifica locale a `init()`, el sistema detecta l'idioma del navegador:
481
+
482
+ ```ts
483
+ // Detecta automàticament: navigator.language → 'ca-ES' → 'ca'
484
+ i18n.init({ fallbackLocale: 'en' })
410
485
  ```
411
486
 
412
- | Metode | Signatura | Descripcio |
413
- |--------|-----------|------------|
414
- | `set()` | `(key: string, value: unknown) => void` | Guarda un valor (serialitzat amb JSON.stringify). |
415
- | `get()` | `<T>(key: string) => T \| null` | Obte un valor (deserialitzat amb JSON.parse). |
416
- | `remove()` | `(key: string) => void` | Elimina un valor. |
417
- | `clear()` | `() => void` | Neteja tot localStorage. |
418
- | `has()` | `(key: string) => boolean` | Verifica si existeix una clau. |
419
- | `keys()` | `() => string[]` | Obte totes les claus. |
487
+ ### Selector d'idioma integrat
420
488
 
421
- **Exemple:**
489
+ El component `<zs3-select-locale>` mostra un selector d'idioma que crida `i18n.setLocale()` automàticament:
422
490
 
423
- ```typescript
424
- storage.set('user', { name: 'Joan', role: 'admin' })
425
- const user = storage.get<{ name: string; role: string }>('user')
426
- // { name: 'Joan', role: 'admin' }
491
+ ```html
492
+ <!-- HTML -->
493
+ <zs3-select-locale zs3-position="position:relative;"></zs3-select-locale>
494
+ ```
427
495
 
428
- storage.has('user') // true
429
- storage.keys() // ['user', 'zs3-theme', 'zs3-locale', ...]
430
- storage.remove('user')
431
- storage.clear()
496
+ ```ts
497
+ // TypeScript (index3.ts)
498
+ const locale = document.createElement('zs3-select-locale')
499
+ locale.setAttribute('zs3-position', 'position:relative;')
500
+ header.append(locale)
432
501
  ```
433
502
 
434
503
  ---
435
504
 
436
- ## $Store
505
+ ## Theme System
506
+
507
+ ### Temes disponibles
437
508
 
438
- Sistema de gestió d'estat global inspirat en Redux, sense dependències externes. Segueix el flux `dispatch(action)` → reducer → nou estat → notificació als subscriptors.
509
+ | Tema | Valor |
510
+ |------|-------|
511
+ | Clar (default) | `light` |
512
+ | Fosc | `dark` |
513
+ | Alt contrast | `high-contrast` |
439
514
 
440
- ```typescript
441
- import { $Store } from 'zs3-ui-components'
442
- import type { Listener, Reducer } from 'zs3-ui-components'
515
+ ### API `themeManager`
516
+
517
+ ```ts
518
+ import { themeManager } from 'zs3-ui-components'
519
+
520
+ themeManager.setTheme('dark') // canvia el tema
521
+ themeManager.getTheme() // → 'dark'
522
+ themeManager.isDark() // → true
523
+ themeManager.toggleDark() // alterna light/dark
443
524
  ```
444
525
 
445
- ### Flux de dades
526
+ ### Escolta canvis de tema
527
+
528
+ ```ts
529
+ // HTML/inline (index2.ts)
530
+ window.addEventListener('zs3-theme-change', () => {
531
+ const theme = themeManager.getTheme()
532
+ const iconEl = document.querySelector('#theme-indicator zs3-icon')
533
+ iconEl?.setAttribute('name', themeManager.isDark() ? 'moon' : 'sun')
534
+ })
446
535
 
536
+ // TypeScript (index3.ts)
537
+ window.addEventListener('zs3-theme-change', () => {
538
+ if (nameEl) nameEl.textContent = themeManager.getTheme()
539
+ if (iconEl) iconEl.setAttribute('name', themeManager.isDark() ? 'moon' : 'sun')
540
+ })
447
541
  ```
448
- [Component] → dispatch(action, payload)
449
-
450
- [Store] executa el reducer
451
-
452
- estat = { ...estat, ...canvis }
453
-
454
- notifica listeners de l'acció + listeners '*'
455
-
456
- [Components subscrits] → setState() → re-render
542
+
543
+ ### Selector de tema integrat
544
+
545
+ ```html
546
+ <zs3-select-theme zs3-position="position:relative;"></zs3-select-theme>
457
547
  ```
458
548
 
459
- ### Inicialització
549
+ ---
460
550
 
461
- El patró recomanat és registrar el Store com a singleton al contenidor de dependències (DIContainer):
551
+ ## Components
462
552
 
463
- ```typescript
464
- import { $Store } from 'zs3-ui-components'
465
- import { diContainer } from 'zs3-ui-components'
466
- import { registerReducers } from './actions'
553
+ ### zs3-button
467
554
 
468
- interface AppState {
469
- theme: 'light' | 'dark'
470
- user: User | null
471
- loading: boolean
472
- }
555
+ Botó versàtil amb variants, mides, icones, estats i events.
473
556
 
474
- const initialState: AppState = { theme: 'light', user: null, loading: false }
557
+ **Atributs:**
475
558
 
476
- diContainer.registerSingleton('store', () => {
477
- const store = new $Store<AppState>(initialState)
478
- registerReducers(store) // registra tots els reducers
479
- return store
480
- })
559
+ | Atribut | Valors | Descripció |
560
+ |---------|--------|------------|
561
+ | `variant` | `primary` `secondary` `success` `danger` `warning` `info` | Color del botó |
562
+ | `size` | `sm` `md` `lg` | Mida |
563
+ | `icon` | nom de la icona | Icona SVG integrada |
564
+ | `position` | `left` `right` | Posició de la icona respecte al text |
565
+ | `disabled` | booleà | Desactiva el botó |
566
+ | `loading` | booleà | Mostra spinner de càrrega |
567
+ | `rounded` | booleà | Forma arrodonida (perfecta per icones soles) |
481
568
 
482
- // Qualsevol component el pot obtenir amb:
483
- const store = diContainer.resolve<$Store<AppState>>('store')
484
- ```
569
+ **Esdeveniments:**
485
570
 
486
- ### Fitxer actions
571
+ | Esdeveniment | Descripció |
572
+ |-------------|------------|
573
+ | `zs3-click` | Es dispara en fer clic (respecta `disabled` i `loading`) |
487
574
 
488
- Cal crear un fitxer `actions.ts` que centralitzi les constants d'acció i el registre de reducers:
575
+ #### HTML inline (index2.html)
489
576
 
490
- ```typescript
491
- // actions.ts
492
- import type { $Store } from 'zs3-ui-components'
577
+ ```html
578
+ <!-- Variants -->
579
+ <zs3-button>Default</zs3-button>
580
+ <zs3-button variant="primary">Primary</zs3-button>
581
+ <zs3-button variant="secondary">Secondary</zs3-button>
582
+ <zs3-button variant="success">Success</zs3-button>
583
+ <zs3-button variant="danger">Danger</zs3-button>
584
+ <zs3-button variant="warning">Warning</zs3-button>
585
+ <zs3-button variant="info">Info</zs3-button>
586
+
587
+ <!-- Mides -->
588
+ <zs3-button variant="primary" size="sm">Small</zs3-button>
589
+ <zs3-button variant="primary">Medium</zs3-button>
590
+ <zs3-button variant="primary" size="lg">Large</zs3-button>
591
+
592
+ <!-- Amb icona -->
593
+ <zs3-button icon="save" variant="primary" position="left">Guardar</zs3-button>
594
+ <zs3-button icon="edit">Editar</zs3-button>
595
+ <zs3-button icon="trash" variant="danger" position="left">Eliminar</zs3-button>
596
+ <zs3-button icon="download" variant="info" position="left">Descarregar</zs3-button>
597
+
598
+ <!-- Rounded (icona sola) -->
599
+ <zs3-button icon="settings" rounded></zs3-button>
600
+ <zs3-button icon="bell" variant="warning" rounded></zs3-button>
601
+ <zs3-button icon="star" variant="success" size="lg" rounded>Favorit</zs3-button>
602
+
603
+ <!-- Estats -->
604
+ <zs3-button variant="primary" disabled>Disabled</zs3-button>
605
+ <zs3-button variant="success" loading>Loading...</zs3-button>
606
+
607
+ <!-- Event inline — `this` és el botó, `event` és el CustomEvent -->
608
+ <zs3-button id="btn-toggle" variant="info"
609
+ zs3-click="toggleLoading(this)">
610
+ Toggle Loading
611
+ </zs3-button>
612
+
613
+ <!-- Event inline simple -->
614
+ <zs3-button variant="primary" icon="plus"
615
+ zs3-click="document.getElementById('out').textContent = 'Clicat: ' + new Date().toLocaleTimeString()">
616
+ Clica'm
617
+ </zs3-button>
618
+ <div id="out">Clica un botó...</div>
619
+ ```
620
+
621
+ #### TypeScript programàtic (index3.ts)
622
+
623
+ ```ts
624
+ import { $Button } from 'zs3-ui-components'
625
+
626
+ // Crear un botó
627
+ const b = new $Button()
628
+ b.textContent = 'Guardar' // text (es captura a beforeMount)
629
+ b.variant = 'primary'
630
+ b.icon = 'save'
631
+ b.position = 'left'
632
+ document.body.append(b)
633
+
634
+ // Mida i estat
635
+ b.size = 'lg'
636
+ b.disabled = true
637
+ b.loading = true
638
+
639
+ // Rounded (icona sola)
640
+ const bRound = new $Button()
641
+ bRound.icon = 'settings'
642
+ bRound.rounded = true
643
+
644
+ // Toggle loading programàtic
645
+ const bToggle = new $Button()
646
+ bToggle.textContent = 'Toggle Loading'
647
+ bToggle.variant = 'info'
648
+ bToggle.addEventListener('zs3-click', () => {
649
+ bToggle.setLoading(!bToggle.loading)
650
+ })
493
651
 
494
- // Constants per evitar strings màgics escampats pel codi
495
- export const Actions = {
496
- AUTH_LOGIN: 'auth/login',
497
- AUTH_LOGOUT: 'auth/logout',
498
- THEME_TOGGLE: 'theme/toggle',
499
- LOADING_SET: 'loading/set',
500
- } as const
652
+ // Actualitzar text després de muntar (quan el component ja és al DOM)
653
+ bToggle.setText('Nou text')
501
654
 
502
- // Registre centralitzat de tots els reducers
503
- export function registerReducers(store: $Store<AppState>): void {
504
- store.registerReducer(Actions.AUTH_LOGIN, (_state, payload) => ({
505
- user: payload as User,
506
- loading: false,
507
- }))
655
+ // Canviar text amb i18n (actualitza en canvi d'idioma)
656
+ window.addEventListener('zs3-locale-change', () => {
657
+ bLogin.setText(i18n.t('section.auth.login'))
658
+ })
659
+ ```
508
660
 
509
- store.registerReducer(Actions.AUTH_LOGOUT, () => ({
510
- user: null,
511
- }))
661
+ **API de `$Button`:**
512
662
 
513
- store.registerReducer(Actions.THEME_TOGGLE, (state) => ({
514
- theme: state.theme === 'light' ? 'dark' : 'light',
515
- }))
663
+ | Propietat / Mètode | Tipus | Descripció |
664
+ |-------------------|-------|------------|
665
+ | `variant` | string | Variant de color |
666
+ | `size` | `'sm'` \| `'md'` \| `'lg'` | Mida |
667
+ | `icon` | string | Nom de la icona |
668
+ | `position` | `'left'` \| `'right'` | Posició icona |
669
+ | `disabled` | boolean | Desactivat |
670
+ | `loading` | boolean | Estat de càrrega |
671
+ | `rounded` | boolean | Forma arrodonida |
672
+ | `setText(text)` | mètode | Actualitza el text (quan ja és al DOM) |
673
+ | `setLoading(bool)` | mètode | Activa/desactiva l'estat loading |
516
674
 
517
- store.registerReducer(Actions.LOADING_SET, (_state, payload) => ({
518
- loading: payload as boolean,
519
- }))
520
- }
521
- ```
675
+ ---
522
676
 
523
- ### API
677
+ ### zs3-icon
524
678
 
525
- | Mètode | Signatura | Descripció |
526
- |--------|-----------|------------|
527
- | `constructor` | `(initialState: T)` | Crea el store. L'estat inicial es copia en profunditat (`structuredClone`). |
528
- | `getState()` | `() => Readonly<T>` | Retorna una còpia immutable (frozen) de l'estat actual. |
529
- | `registerReducer()` | `(actionType: string, reducer: Reducer<T>) => void` | Associa un reducer a un tipus d'acció. |
530
- | `dispatch()` | `(actionType: string, payload?: unknown) => void` | Executa l'acció, actualitza l'estat i notifica els listeners. |
531
- | `subscribe()` | `(actionType: string, listener: Listener<T>) => () => void` | Subscriu un listener. Retorna la funció d'unsubscribe. |
679
+ Icona SVG integrada amb suport de mida, color, rotació, flip i clics.
532
680
 
533
- ### Patrons d'ús en components
681
+ **Atributs:**
534
682
 
535
- **1. Llegir estat inicial en `onMount()`:**
683
+ | Atribut | Valors | Descripció |
684
+ |---------|--------|------------|
685
+ | `name` | nom de la icona | Icona a mostrar |
686
+ | `size` | `small` `medium` `large` o número en px | Mida |
687
+ | `color` | `primary` `secondary` `success` `danger` `warning` `info` | Color |
688
+ | `rotate` | `90` `180` `270` | Graus de rotació |
689
+ | `flip` | `horizontal` `vertical` | Volteig |
690
+ | `clickable` | booleà | Activa l'event `zs3-icon-click` |
691
+ | `stroke-width` | número | Amplada del traç SVG |
692
+ | `aria-label` | text | Label d'accessibilitat |
693
+ | `data-i18n-attr` | JSON | Traducció de l'atribut aria-label |
536
694
 
537
- ```typescript
538
- const state = this.store.getState()
539
- this.setState({ theme: state.theme })
540
- ```
695
+ **Icones disponibles:** `increase`, `decrease`, `error`, `warning`, `information`, `close`, `left-arrow`, `right-arrow`, `up-arrow`, `down-arrow`, `menu`, `accept`, `cancel`, `search`, `delete`, `dark`, `sun`, `moon`, `users`, `copy`, `trash`, `upload`, `logout`, `login`, `modal`, `save`, `edit`, `download`, `settings`, `home`, `star`, `heart`, `filter`, `refresh`, `lock`, `unlock`, `bell`, `mail`, `phone`, `calendar`, `clock`, `eye`, `eye-off`, `plus`, `minus`, `check`, `x`
541
696
 
542
- **2. Subscriure's a canvis específics en `onMount()`:**
697
+ #### HTML inline (index2.html)
543
698
 
544
- ```typescript
545
- // Escoltar una acció concreta
546
- this.addSubscription(
547
- this.store.subscribe(Actions.THEME_TOGGLE, (state) => {
548
- this.setState({ theme: state.theme })
549
- })
550
- )
699
+ ```html
700
+ <!-- Bàsica -->
701
+ <zs3-icon name="star"></zs3-icon>
702
+
703
+ <!-- Mides -->
704
+ <zs3-icon name="star" size="small"></zs3-icon>
705
+ <zs3-icon name="star" size="medium"></zs3-icon>
706
+ <zs3-icon name="star" size="large"></zs3-icon>
707
+ <zs3-icon name="star" size="48"></zs3-icon>
708
+
709
+ <!-- Colors -->
710
+ <zs3-icon name="heart" size="large" color="primary"></zs3-icon>
711
+ <zs3-icon name="heart" size="large" color="danger"></zs3-icon>
712
+ <zs3-icon name="heart" size="large" color="success"></zs3-icon>
713
+
714
+ <!-- Rotació i flip -->
715
+ <zs3-icon name="right-arrow" size="large" rotate="90"></zs3-icon>
716
+ <zs3-icon name="right-arrow" size="large" rotate="180"></zs3-icon>
717
+ <zs3-icon name="right-arrow" size="large" flip="horizontal"></zs3-icon>
718
+
719
+ <!-- Clickable amb id per als event listeners de index2.ts -->
720
+ <zs3-icon id="icon-star" name="star" size="large" color="warning" clickable></zs3-icon>
721
+ <zs3-icon id="icon-heart" name="heart" size="large" color="danger" clickable></zs3-icon>
722
+ <zs3-icon id="icon-bell" name="bell" size="large" color="info" clickable></zs3-icon>
723
+ <div id="icon-click-output">Clica una icona...</div>
724
+
725
+ <!-- i18n en atribut aria-label -->
726
+ <zs3-icon name="bell" data-i18n-attr='{"aria-label":"nav.notifications"}'></zs3-icon>
727
+ ```
728
+
729
+ ```ts
730
+ // index2.ts — escolta events de les icones clickables
731
+ function setupIconClickListeners(): void {
732
+ const output = document.getElementById('icon-click-output')
733
+ ;['icon-star', 'icon-heart', 'icon-bell'].forEach((id) => {
734
+ const el = document.getElementById(id)
735
+ el?.addEventListener('zs3-icon-click', (e: Event) => {
736
+ const name = (e.target as Element).getAttribute('name') || id
737
+ if (output) output.textContent = `zs3-icon-click: icona "${name}" clicada`
738
+ })
739
+ })
740
+ }
551
741
 
552
- // Escoltar TOTS els canvis (canal wildcard '*')
553
- this.addSubscription(
554
- this.store.subscribe('*', (state) => {
555
- this.setState({ appState: state as AppState })
556
- })
557
- )
742
+ // index2.ts genera la graella de totes les icones
743
+ function setupIconsGrid(): void {
744
+ const grid = document.getElementById('icons-grid')
745
+ if (!grid) return
746
+ $Icon.getAvailableIcons().forEach((name) => {
747
+ const card = document.createElement('div')
748
+ card.className = 'demo-icon-card'
749
+ card.innerHTML = `<zs3-icon name="${name}" size="medium"></zs3-icon><span>${name}</span>`
750
+ card.addEventListener('click', () =>
751
+ $Notification.info(`Icona: ${name}`, { position: 'bottom-right', duration: 2000 })
752
+ )
753
+ grid.appendChild(card)
754
+ })
755
+ }
558
756
  ```
559
757
 
560
- > **Nota:** El mètode `addSubscription()` de `$BaseComponent` s'encarrega de cridar l'unsubscribe automàticament quan el component es destrueix.
758
+ #### TypeScript programàtic (index3.ts)
561
759
 
562
- **3. Disparar accions en `bindEvents()`:**
760
+ ```ts
761
+ import { $Icon } from 'zs3-ui-components'
563
762
 
564
- ```typescript
565
- this.addEvent('click', () => {
566
- this.store.dispatch(Actions.THEME_TOGGLE)
567
- })
763
+ // Icona bàsica
764
+ const ic = new $Icon()
765
+ ic.setAttribute('name', 'star')
766
+ ic.setAttribute('size', 'large')
767
+ ic.setAttribute('color', 'warning')
768
+ document.body.append(ic)
568
769
 
569
- this.addEvent('submit', () => {
570
- this.store.dispatch(Actions.AUTH_LOGIN, { id: 1, name: 'Joan' })
770
+ // Clickable
771
+ ic.setAttribute('clickable', '')
772
+ ic.addEventListener('zs3-icon-click', (e) => {
773
+ console.log('Icona clicada:', (e as CustomEvent).detail)
571
774
  })
572
- ```
573
-
574
- ### Canal wildcard `'*'`
575
775
 
576
- El canal especial `'*'` rep notificacions de **qualsevol** acció que es dispari. És útil per a components de monitoratge o per reagir a qualsevol canvi d'estat:
776
+ // Totes les icones disponibles
777
+ const names = $Icon.getAvailableIcons() // string[]
577
778
 
578
- ```typescript
579
- store.subscribe('*', (state) => {
580
- console.log('Estat actualitzat:', state)
779
+ // Afegir icona personalitzada
780
+ $Icon.addIcon('custom', `<path d="M12 2L2 22h20L12 2z"/>`)
781
+ $Icon.addIcons({
782
+ 'logo-a': '<path .../>',
783
+ 'logo-b': '<path .../>',
581
784
  })
582
785
  ```
583
786
 
584
- ### Tipus auxiliars
585
-
586
- | Tipus | Signatura | Descripció |
587
- |-------|-----------|------------|
588
- | `Listener<T>` | `(state: Readonly<T>) => void` | Funció que rep l'estat quan hi ha un canvi. |
589
- | `Reducer<T>` | `(state: T, payload?: unknown) => Partial<T>` | Funció que transforma l'estat. Retorna només els camps que canvien. |
590
-
591
787
  ---
592
788
 
593
- ## HttpClient
594
-
595
- Client HTTP basat en `fetch` amb suport per a interceptors, reintentos automàtics i timeout. Cada instància té la seva pròpia configuració, per la qual cosa no s'exposa com a singleton global.
789
+ ### zs3-toolbar
596
790
 
597
- ```typescript
598
- import { HttpClient, HttpError } from 'zs3-ui-components'
599
- import type { HttpClientConfig, HttpResponse } from 'zs3-ui-components'
600
- ```
791
+ Contenidor de botons amb suport per orientació, alineació, variants compactes i posicionament flotant.
601
792
 
602
- ### Configuració
793
+ **Atributs:**
603
794
 
604
- ```typescript
605
- const api = new HttpClient({
606
- baseUrl: 'https://api.exemple.com', // Base de totes les URLs relatives
607
- timeout: 10_000, // ms abans de cancel·lar (per defecte 10000)
608
- retries: 2, // Reintentos automàtics en errors 5xx/timeout
609
- retryDelay: 1_000, // ms entre reintent (es duplica cada vegada)
610
- headers: { // Capçaleres per defecte (Content-Type i Accept: application/json ja inclosos)
611
- Authorization: 'Bearer token',
612
- },
613
- })
614
- ```
795
+ | Atribut | Valors | Descripció |
796
+ |---------|--------|------------|
797
+ | `direction` | `row` `column` | Orientació |
798
+ | `align` | `start` `center` `end` | Alineació dels elements |
799
+ | `variant` | `default` `compact` | Estil visual |
800
+ | `float` | `top` `bottom` `left` `right` | Posicionament flotant |
801
+ | `move` | `x` `y` `xy` | Permet arrossegar la toolbar |
802
+ | `left` `top` `right` `bottom` | posició en px | Posició inicial |
615
803
 
616
- ### Metodes
804
+ #### HTML inline (index2.html)
617
805
 
618
- | Metode | Signatura | Descripcio |
619
- |--------|-----------|------------|
620
- | `get()` | `<T>(url: string, params?: Record<string, string>) => Promise<HttpResponse<T>>` | Peticio GET. Els `params` s'afegeixen com a query string. |
621
- | `post()` | `<T>(url: string, body?: unknown) => Promise<HttpResponse<T>>` | Peticio POST amb body JSON. |
622
- | `put()` | `<T>(url: string, body?: unknown) => Promise<HttpResponse<T>>` | Peticio PUT. |
623
- | `patch()` | `<T>(url: string, body?: unknown) => Promise<HttpResponse<T>>` | Peticio PATCH. |
624
- | `delete()` | `<T>(url: string) => Promise<HttpResponse<T>>` | Peticio DELETE. |
625
- | `request()` | `<T>(config: HttpRequestConfig) => Promise<HttpResponse<T>>` | Peticio completament configurable. |
806
+ ```html
807
+ <!-- Horitzontal -->
808
+ <zs3-toolbar direction="row">
809
+ <zs3-button icon="home">Inici</zs3-button>
810
+ <zs3-button icon="users">Usuaris</zs3-button>
811
+ <zs3-button icon="settings">Configuració</zs3-button>
812
+ <zs3-button icon="bell" variant="warning">Notificacions</zs3-button>
813
+ <zs3-button icon="logout" variant="danger">Sortir</zs3-button>
814
+ </zs3-toolbar>
626
815
 
627
- ### Resposta
816
+ <!-- Vertical -->
817
+ <zs3-toolbar direction="column" style="width:fit-content">
818
+ <zs3-button icon="edit" variant="primary" position="left" size="sm">Editar</zs3-button>
819
+ <zs3-button icon="copy" position="left" size="sm">Copiar</zs3-button>
820
+ <zs3-button icon="trash" variant="danger" position="left" size="sm">Eliminar</zs3-button>
821
+ </zs3-toolbar>
628
822
 
629
- Tots els metodes retornen `Promise<HttpResponse<T>>`:
823
+ <!-- Compact + align center (botons de navegació) -->
824
+ <zs3-toolbar direction="row" align="center" variant="compact">
825
+ <zs3-button icon="left-arrow" size="sm" rounded></zs3-button>
826
+ <zs3-button icon="refresh" size="sm" rounded></zs3-button>
827
+ <zs3-button icon="right-arrow" size="sm" rounded></zs3-button>
828
+ </zs3-toolbar>
630
829
 
631
- ```typescript
632
- interface HttpResponse<T> {
633
- data: T // Cos de la resposta (parseig JSON automatic)
634
- status: number
635
- statusText: string
636
- headers: Headers
637
- config: HttpRequestConfig
638
- }
830
+ <!-- Flotant + arrossegable horitzontalment -->
831
+ <zs3-toolbar direction="row" float="bottom" move="x" left="80px">
832
+ <zs3-button icon="save" variant="success" size="sm">Guardar</zs3-button>
833
+ <zs3-button icon="cancel" variant="danger" size="sm">Cancel·lar</zs3-button>
834
+ </zs3-toolbar>
639
835
  ```
640
836
 
641
- ### Interceptors
837
+ #### TypeScript programàtic (index3.ts)
642
838
 
643
- Els interceptors permeten modificar totes les peticions o respostes de forma centralitzada. Retornen una funció per cancel·lar-los.
839
+ ```ts
840
+ import { $Toolbar, $Button } from 'zs3-ui-components'
644
841
 
645
- ```typescript
646
- // Interceptor de peticio: afegir token a totes les crides
647
- const removeAuth = api.onRequest(config => ({
648
- ...config,
649
- headers: { ...config.headers, Authorization: `Bearer ${getToken()}` },
650
- }))
842
+ // Horitzontal
843
+ const toolbar = new $Toolbar()
844
+ toolbar.setAttribute('direction', 'row')
651
845
 
652
- // Interceptor de resposta: transformar dades
653
- api.onResponse(response => ({
654
- ...response,
655
- data: transform(response.data),
656
- }))
846
+ const items: Array<[string, string, string | undefined]> = [
847
+ ['home', 'Inici', undefined],
848
+ ['users', 'Usuaris', undefined],
849
+ ['bell', 'Notificacions', 'warning'],
850
+ ['logout', 'Sortir', 'danger'],
851
+ ]
657
852
 
658
- // Interceptor d'error: gestio centralitzada
659
- api.onError(async error => {
660
- if (error.isUnauthorized) {
661
- await refreshToken()
662
- // Si es retorna un HttpResponse, la peticio es considera recuperada
663
- }
664
- throw error // Si es relanca, l'error arriba al catch del cridant
853
+ items.forEach(([icon, label, variant]) => {
854
+ const b = new $Button()
855
+ b.textContent = label
856
+ b.icon = icon
857
+ if (variant) b.variant = variant as $Button['variant']
858
+ toolbar.append(b)
859
+ })
860
+ document.body.append(toolbar)
861
+
862
+ // Compact amb botons arrodonits
863
+ const navToolbar = new $Toolbar()
864
+ navToolbar.setAttribute('direction', 'row')
865
+ navToolbar.setAttribute('align', 'center')
866
+ navToolbar.setAttribute('variant', 'compact')
867
+
868
+ ;['left-arrow', 'refresh', 'right-arrow'].forEach((icon) => {
869
+ const b = new $Button()
870
+ b.icon = icon
871
+ b.size = 'sm'
872
+ b.rounded = true
873
+ navToolbar.append(b)
665
874
  })
666
875
 
667
- // Cancel·lar un interceptor
668
- removeAuth()
876
+ // Flotant + movible
877
+ const floatToolbar = new $Toolbar()
878
+ floatToolbar.setAttribute('direction', 'row')
879
+ floatToolbar.setAttribute('float', 'bottom')
880
+ floatToolbar.setAttribute('move', 'x')
881
+ floatToolbar.setAttribute('left', '80px')
669
882
  ```
670
883
 
671
- ### Gestio d'errors
884
+ ---
885
+
886
+ ### zs3-notification
672
887
 
673
- Els errors HTTP (4xx, 5xx) i de xarxa es llancen com a `HttpError`:
888
+ Notificació emergent (toast) amb posicions, durades, barra de progrés i estat persistent.
674
889
 
675
- ```typescript
676
- try {
677
- const { data } = await api.get<Post[]>('/posts')
678
- } catch (error) {
679
- if (error instanceof HttpError) {
680
- console.log(error.status) // 404, 500...
681
- console.log(error.isNotFound) // true si 404
682
- console.log(error.isUnauthorized) // true si 401
683
- console.log(error.isServerError) // true si 5xx
684
- console.log(error.isTimeout) // true si timeout
685
- }
686
- }
687
- ```
890
+ **Atributs:**
688
891
 
689
- ### Exemple complet
892
+ | Atribut | Valors | Descripció |
893
+ |---------|--------|------------|
894
+ | `type` | `success` `error` `warning` `info` | Tipus/color |
895
+ | `title` | text | Títol (opcional) |
896
+ | `message` | text | Missatge |
897
+ | `position` | `top-left` `top-center` `top-right` `bottom-left` `bottom-center` `bottom-right` | Posició a la pantalla |
898
+ | `duration` | número en ms (0 = persistent) | Temps de visibilitat |
899
+ | `show-progress` | booleà | Mostra barra de progrés |
900
+ | `dismissible` | booleà | Mostra botó de tancament |
901
+ | `i18n-title` | clau i18n | Títol traduït |
902
+ | `i18n-message` | clau i18n | Missatge traduït |
690
903
 
691
- ```typescript
692
- import { HttpClient, HttpError } from 'zs3-ui-components'
693
- import { $ } from 'zs3-ui-components'
904
+ #### Mètodes estàtics (la manera més senzilla)
694
905
 
695
- interface Post { id: number; title: string; body: string }
906
+ ```ts
907
+ import { $Notification } from 'zs3-ui-components'
696
908
 
697
- const api = new HttpClient({
698
- baseUrl: 'https://jsonplaceholder.typicode.com',
699
- timeout: 8_000,
700
- retries: 1,
909
+ $Notification.success('Operació completada!', {
910
+ position: 'top-right',
911
+ duration: 3000,
912
+ showProgress: true,
701
913
  })
702
914
 
703
- // GET amb parametres de query
704
- const { data: posts } = await api.get<Post[]>('/posts', { _limit: '5' })
915
+ $Notification.error('Ha ocorregut un error.', {
916
+ title: 'Error crític',
917
+ position: 'top-right',
918
+ duration: 5000,
919
+ dismissible: true,
920
+ })
705
921
 
706
- // POST
707
- const { data: nou, status } = await api.post<Post>('/posts', {
708
- title: 'Nou post',
709
- body: 'Contingut',
710
- userId: 1,
922
+ $Notification.warning('Atenció: acció irreversible!', {
923
+ position: 'bottom-right',
924
+ duration: 4000,
711
925
  })
712
926
 
713
- // PATCH (modificacio parcial)
714
- await api.patch(`/posts/${nou.id}`, { title: 'Titol modificat' })
927
+ $Notification.info('3 nous missatges pendents.', {
928
+ title: 'Nous missatges',
929
+ position: 'top-right',
930
+ duration: 0, // 0 = persistent
931
+ dismissible: true,
932
+ })
933
+ ```
715
934
 
716
- // DELETE
717
- await api.delete(`/posts/${nou.id}`)
935
+ #### HTML inline (index2.html)
718
936
 
719
- // Integrat amb ZS3
720
- $('#btn-load')?.addEvent('click', async () => {
721
- try {
722
- const { data } = await api.get<Post[]>('/posts', { _limit: '3' })
723
- const html = data.map(p => `<li>${p.title}</li>`).join('')
724
- $('ul#llista')?.html(html)
725
- } catch (e) {
726
- if (e instanceof HttpError) $('p#error')?.html(`Error ${e.status}`)
727
- }
728
- })
937
+ ```html
938
+ <!-- Mètodes estàtics des de handlers inline -->
939
+ <zs3-button variant="success"
940
+ zs3-click="$Notification.success('Èxit!', { position: 'top-right', duration: 3000 })">
941
+ Success
942
+ </zs3-button>
943
+ <zs3-button variant="danger"
944
+ zs3-click="$Notification.error('Error!', { position: 'top-right', duration: 3000 })">
945
+ Error
946
+ </zs3-button>
947
+
948
+ <!-- Component inline amb show()/hide() -->
949
+ <zs3-notification
950
+ id="notif-inline"
951
+ type="success"
952
+ title="Títol"
953
+ message="Missatge de notificació"
954
+ position="top-center"
955
+ duration="0"
956
+ show-progress
957
+ dismissible>
958
+ </zs3-notification>
959
+ <zs3-button zs3-click="document.getElementById('notif-inline').$.show()">show()</zs3-button>
960
+ <zs3-button zs3-click="document.getElementById('notif-inline').$.hide()">hide()</zs3-button>
961
+
962
+ <!-- i18n -->
963
+ <zs3-notification
964
+ i18n-title="section.notification.success.title"
965
+ i18n-message="section.notification.success.message"
966
+ type="success"
967
+ position="top-right">
968
+ </zs3-notification>
729
969
  ```
730
970
 
731
- ---
971
+ #### TypeScript programàtic (index3.ts)
732
972
 
733
- ## Sistema de Temes (ThemeManager)
973
+ ```ts
974
+ // Component creat programàticament
975
+ const notif = document.createElement('zs3-notification') as HTMLElement & {
976
+ show(): void
977
+ hide(): void
978
+ }
979
+ notif.setAttribute('type', 'success')
980
+ notif.setAttribute('title', 'Notificació TypeScript')
981
+ notif.setAttribute('message', 'Creada programàticament des de TypeScript')
982
+ notif.setAttribute('position', 'top-center')
983
+ notif.setAttribute('duration', '0')
984
+ notif.setAttribute('show-progress', '')
985
+ notif.setAttribute('dismissible', '')
986
+ document.body.append(notif)
734
987
 
735
- El sistema de temes permet canviar l'aparenca visual de tota l'aplicacio.
988
+ const bShow = new $Button()
989
+ bShow.textContent = 'show()'
990
+ bShow.variant = 'primary'
991
+ bShow.addEventListener('zs3-click', () => notif.show())
736
992
 
737
- ```typescript
738
- import { themeManager } from 'zs3-ui-components'
993
+ const bHide = new $Button()
994
+ bHide.textContent = 'hide()'
995
+ bHide.addEventListener('zs3-click', () => notif.hide())
739
996
  ```
740
997
 
741
- ### Temes disponibles
998
+ ---
742
999
 
743
- | Tema | Descripcio |
744
- |------|------------|
745
- | `light` | Tema clar per defecte. Fons blanc, text fosc. |
746
- | `light-blue` | Tema clar amb accents blaus. |
747
- | `dark` | Tema fosc. Fons `#111827`, text clar. |
748
- | `elegant` | Tema elegant amb tons champagne/bronze. Font serif (Georgia). |
749
- | `modern` | Tema modern amb colors neon i indigo. Border-radius arrodonit. |
750
- | `high-contrast` | Alt contrast per accessibilitat. Fons negre, text blanc. |
751
- | `system` | Segueix la preferencia del sistema operatiu (`prefers-color-scheme`). |
752
- | `custom` | Tema personalitzat definit per l'usuari. |
753
-
754
- ### API
755
-
756
- | Metode | Signatura | Descripcio |
757
- |--------|-----------|------------|
758
- | `setTheme()` | `(theme: ThemeType) => void` | Estableix el tema actiu. Persisteix a localStorage. |
759
- | `getTheme()` | `() => ThemeType` | Obte el tema actual. |
760
- | `toggleTheme()` | `() => void` | Alterna entre `light` i `dark`. |
761
- | `isDark()` | `() => boolean` | Verifica si el tema actual es dark. |
762
- | `getAvailableThemes()` | `() => ThemeType[]` | Llista tots els temes disponibles. |
763
-
764
- ### Event
765
-
766
- Quan canvia el tema, s'emet un event global:
767
-
768
- ```typescript
769
- window.addEventListener('zs3-theme-change', (e: CustomEvent) => {
770
- console.log('Nou tema:', e.detail.theme)
771
- })
772
- ```
1000
+ ### zs3-modal
773
1001
 
774
- ### Funcions auxiliars
1002
+ Modal genèric contenidor de contingut amb mides, backdrop, tancament per ESC/backdrop i mode scrollable.
775
1003
 
776
- ```typescript
777
- import { getCSSVariable, setCSSVariable, initThemeSystem } from 'zs3-ui-components'
1004
+ **Atributs:**
778
1005
 
779
- // Llegir una CSS variable (sense el prefix --)
780
- getCSSVariable('zs3-primary-color') // '#3b82f6'
1006
+ | Atribut | Valors | Descripció |
1007
+ |---------|--------|------------|
1008
+ | `size` | `small` `medium` `large` `fullscreen` | Mida |
1009
+ | `backdrop` | booleà | Mostra el fons fosc |
1010
+ | `close-on-backdrop` | booleà | Tanca en clicar el backdrop |
1011
+ | `close-on-escape` | booleà | Tanca amb la tecla ESC |
1012
+ | `scrollable` | booleà | Permet scroll del contingut |
1013
+ | `centered` | booleà | Centra verticalment el modal |
781
1014
 
782
- // Establir una CSS variable globalment
783
- setCSSVariable('zs3-primary-color', '#ff0000')
1015
+ **Mètodes:** `show()`, `hide()`
784
1016
 
785
- // Inicialitzar el sistema de temes amb opcions
786
- initThemeSystem({ defaultTheme: 'dark' })
787
- ```
1017
+ #### HTML inline (index2.html)
788
1018
 
789
- ### Mixin `withThemeSupport`
1019
+ ```html
1020
+ <!-- Defineix el modal a qualsevol lloc del body -->
1021
+ <zs3-modal id="modal-medium" size="medium" backdrop close-on-backdrop close-on-escape>
1022
+ <div style="padding:1.5rem">
1023
+ <h3 style="margin-top:0">Modal Medium</h3>
1024
+ <p>Contingut del modal.</p>
1025
+ <zs3-button variant="primary"
1026
+ zs3-click="document.getElementById('modal-medium').$.hide()">
1027
+ Tancar
1028
+ </zs3-button>
1029
+ </div>
1030
+ </zs3-modal>
790
1031
 
791
- Per afegir suport de temes a un component personalitzat que no hereti de `$BaseComponent`:
1032
+ <zs3-modal id="modal-fullscreen" size="fullscreen" backdrop close-on-escape>
1033
+ <div style="padding:2rem">
1034
+ <h2>Modal Fullscreen</h2>
1035
+ <zs3-button variant="primary"
1036
+ zs3-click="document.getElementById('modal-fullscreen').$.hide()">
1037
+ Tancar
1038
+ </zs3-button>
1039
+ </div>
1040
+ </zs3-modal>
792
1041
 
793
- ```typescript
794
- import { withThemeSupport } from 'zs3-ui-components'
1042
+ <!-- Scrollable -->
1043
+ <zs3-modal id="modal-scroll" size="medium" backdrop close-on-backdrop close-on-escape scrollable>
1044
+ <div style="padding:1.5rem">
1045
+ <h3>Scrollable</h3>
1046
+ <!-- molts paràgrafs... -->
1047
+ <zs3-button variant="primary"
1048
+ zs3-click="document.getElementById('modal-scroll').$.hide()">
1049
+ Tancar
1050
+ </zs3-button>
1051
+ </div>
1052
+ </zs3-modal>
795
1053
 
796
- class MyElement extends withThemeSupport(HTMLElement) {
797
- render() { /* ... */ }
798
- }
1054
+ <zs3-button variant="primary" zs3-click="document.getElementById('modal-medium').$.show()">Obrir Medium</zs3-button>
1055
+ <zs3-button variant="secondary" zs3-click="document.getElementById('modal-fullscreen').$.show()">Fullscreen</zs3-button>
1056
+ <zs3-button variant="info" zs3-click="document.getElementById('modal-scroll').$.show()">Scrollable</zs3-button>
799
1057
  ```
800
1058
 
801
- ### CSS Variables
1059
+ #### TypeScript programàtic (index3.ts)
802
1060
 
803
- Totes les variables utilitzen el prefix `zs3-`.
1061
+ ```ts
1062
+ import { $Modal, $Button } from 'zs3-ui-components'
804
1063
 
805
- #### Variables globals (compartides per tots els temes)
1064
+ const modal = new $Modal()
1065
+ modal.setAttribute('size', 'medium')
1066
+ modal.setAttribute('backdrop', '')
1067
+ modal.setAttribute('close-on-backdrop', '')
1068
+ modal.setAttribute('close-on-escape', '')
806
1069
 
807
- ```css
808
- :root {
809
- /* Fonts */
810
- --zs3-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
811
- --zs3-font-family-mono: 'Courier New', Courier, monospace;
812
-
813
- /* Mides de font */
814
- --zs3-font-size-xs: 0.75rem;
815
- --zs3-font-size-sm: 0.875rem;
816
- --zs3-font-size-base: 1rem;
817
- --zs3-font-size-lg: 1.125rem;
818
- --zs3-font-size-xl: 1.25rem;
819
- --zs3-font-size-2xl: 1.5rem;
820
- --zs3-font-size-3xl: 1.875rem;
821
-
822
- /* Espaiat */
823
- --zs3-spacing-xs: 0.25rem;
824
- --zs3-spacing-sm: 0.5rem;
825
- --zs3-spacing-md: 1rem;
826
- --zs3-spacing-lg: 1.5rem;
827
- --zs3-spacing-xl: 2rem;
828
- --zs3-spacing-2xl: 3rem;
829
- }
830
- ```
1070
+ const content = document.createElement('div')
1071
+ content.style.padding = '1.5rem'
831
1072
 
832
- #### Variables per tema
833
-
834
- Cada tema defineix les seguents variables:
835
-
836
- | Variable | Descripcio | Exemple (light) |
837
- |----------|------------|-----------------|
838
- | `--zs3-bg-color` | Color de fons | `#ffffff` |
839
- | `--zs3-fg-color` | Color de text | `#111827` |
840
- | `--zs3-primary-color` | Color primari | `#3b82f6` |
841
- | `--zs3-primary-hover-color` | Color primari hover | `#2563eb` |
842
- | `--zs3-success-color` | Color d'exit | `#10b981` |
843
- | `--zs3-warning-color` | Color d'advertencia | `#f59e0b` |
844
- | `--zs3-error-color` | Color d'error | `#ef4444` |
845
- | `--zs3-info-color` | Color informatiu | `#06b6d4` |
846
- | `--zs3-border-color` | Color de vora | `#d1d5db` |
847
- | `--zs3-border-radius` | Radi de vora | `0.375rem` |
848
- | `--zs3-shadow-sm` | Ombra petita | `0 1px 2px 0 rgba(0,0,0,0.05)` |
849
- | `--zs3-shadow-md` | Ombra mitjana | `0 4px 6px -1px rgba(0,0,0,0.1)` |
850
- | `--zs3-shadow-lg` | Ombra gran | `0 10px 15px -3px rgba(0,0,0,0.1)` |
851
- | `--zs3-shadow-xl` | Ombra extra gran | `0 20px 25px -5px rgba(0,0,0,0.1)` |
852
- | `--zs3-transition-fast` | Transicio rapida | `150ms ease-in-out` |
853
- | `--zs3-transition-base` | Transicio base | `250ms ease-in-out` |
854
- | `--zs3-transition-slow` | Transicio lenta | `500ms ease-in-out` |
855
- | `--zs3-overlay` | Color del backdrop | `rgba(0,0,0,0.5)` |
856
-
857
- #### Valors per cada tema
858
-
859
- <details>
860
- <summary><strong>Light</strong></summary>
1073
+ const closeBtn = new $Button()
1074
+ closeBtn.textContent = 'Tancar'
1075
+ closeBtn.variant = 'primary'
1076
+ closeBtn.addEventListener('zs3-click', () => modal.hide())
1077
+ content.append(closeBtn)
861
1078
 
862
- ```css
863
- [data-theme='light'] {
864
- --zs3-bg-color: #ffffff;
865
- --zs3-fg-color: #111827;
866
- --zs3-primary-color: #3b82f6;
867
- --zs3-primary-hover-color: #2563eb;
868
- --zs3-success-color: #10b981;
869
- --zs3-warning-color: #f59e0b;
870
- --zs3-error-color: #ef4444;
871
- --zs3-info-color: #06b6d4;
872
- --zs3-border-color: #d1d5db;
873
- --zs3-border-radius: 0.375rem;
874
- }
1079
+ modal.append(content)
1080
+ document.body.append(modal) // els modals van al body directament
1081
+
1082
+ const openBtn = new $Button()
1083
+ openBtn.textContent = 'Obrir Modal'
1084
+ openBtn.variant = 'primary'
1085
+ openBtn.addEventListener('zs3-click', () => modal.show())
875
1086
  ```
876
- </details>
877
1087
 
878
- <details>
879
- <summary><strong>Light Blue</strong></summary>
1088
+ ---
880
1089
 
881
- ```css
882
- [data-theme='light-blue'] {
883
- --zs3-bg-color: #ffffffe0;
884
- --zs3-fg-color: #084cb1;
885
- --zs3-primary-color: #3b82f6;
886
- --zs3-primary-hover-color: #2563eb;
887
- --zs3-border-color: #3b82f6;
888
- }
889
- ```
890
- </details>
1090
+ ### zs3-modal-dialog
891
1091
 
892
- <details>
893
- <summary><strong>Dark</strong></summary>
1092
+ Diàleg modal amb títol, missatge, icona, botons d'acció configurables i mètodes estàtics per als casos d'ús comuns.
894
1093
 
895
- ```css
896
- [data-theme='dark'] {
897
- --zs3-bg-color: #111827;
898
- --zs3-fg-color: #f9fafb;
899
- --zs3-primary-color: #60a5fa;
900
- --zs3-primary-hover-color: #3b82f6;
901
- --zs3-success-color: #34d399;
902
- --zs3-warning-color: #fbbf24;
903
- --zs3-error-color: #f87171;
904
- --zs3-info-color: #22d3ee;
905
- --zs3-border-color: #4b5563;
906
- }
907
- ```
908
- </details>
1094
+ **Atributs:**
909
1095
 
910
- <details>
911
- <summary><strong>Elegant</strong></summary>
1096
+ | Atribut | Descripció |
1097
+ |---------|------------|
1098
+ | `title` / `i18n-title` | Títol del diàleg |
1099
+ | `message` / `i18n-message` | Missatge del cos |
1100
+ | `icon` | Icona al costat del títol |
1101
+ | `size` | Mida (`small`, `medium`, `large`) |
1102
+ | `show-accept` | Mostra el botó d'acceptar |
1103
+ | `show-cancel` | Mostra el botó de cancel·lar |
1104
+ | `show-close` | Mostra el botó de tancar |
1105
+ | `accept-text` / `i18n-accept-text` | Text del botó acceptar |
1106
+ | `cancel-text` / `i18n-cancel-text` | Text del botó cancel·lar |
1107
+ | `accept-variant` | Variant del botó acceptar |
1108
+ | `backdrop` | Activa el fons fosc |
1109
+ | `close-on-escape` | Tanca amb ESC |
1110
+
1111
+ **Esdeveniments:** `zs3-dialog-accept`, `zs3-dialog-cancel`, `zs3-dialog-close`
1112
+
1113
+ #### Mètodes estàtics (Promises)
1114
+
1115
+ ```ts
1116
+ import { $ModalDialog, i18n } from 'zs3-ui-components'
1117
+
1118
+ // Alert (es resol quan es tanca)
1119
+ await $ModalDialog.alert({
1120
+ title: 'Atenció',
1121
+ message: 'El fitxer ha estat guardat correctament.',
1122
+ })
912
1123
 
913
- ```css
914
- [data-theme='elegant'] {
915
- --zs3-bg-color: #faf8f5;
916
- --zs3-fg-color: #2d2520;
917
- --zs3-font-family: 'Georgia', 'Times New Roman', serif;
918
- --zs3-primary-color: #8b5a3c;
919
- --zs3-primary-hover-color: #6f4830;
920
- --zs3-success-color: #5d8a66;
921
- --zs3-warning-color: #d4a574;
922
- --zs3-error-color: #a65858;
923
- --zs3-info-color: #5a7d9a;
924
- --zs3-border-color: #c9baa5;
925
- --zs3-border-radius: 0.5rem;
926
- }
927
- ```
928
- </details>
1124
+ // Alert amb i18n
1125
+ await $ModalDialog.alertI18n({
1126
+ i18nTitle: 'section.dialog.alert.title',
1127
+ i18nMessage: 'section.dialog.alert.message',
1128
+ })
929
1129
 
930
- <details>
931
- <summary><strong>Modern</strong></summary>
1130
+ // Confirm (retorna true/false)
1131
+ const confirmed = await $ModalDialog.confirm({
1132
+ title: 'Confirmació',
1133
+ message: 'Estàs segur que vols continuar?',
1134
+ })
932
1135
 
933
- ```css
934
- [data-theme='modern'] {
935
- --zs3-bg-color: #fefefe;
936
- --zs3-fg-color: #beb0b0;
937
- --zs3-primary-color: #6366f1;
938
- --zs3-primary-hover-color: #4f46e5;
939
- --zs3-success-color: #14b8a6;
940
- --zs3-warning-color: #f97316;
941
- --zs3-error-color: #f43f5e;
942
- --zs3-info-color: #0ea5e9;
943
- --zs3-border-color: #e5e5e5;
944
- --zs3-border-radius: 0.75rem;
945
- }
946
- ```
947
- </details>
1136
+ // Confirm amb i18n
1137
+ const result = await $ModalDialog.confirmI18n({
1138
+ i18nTitle: 'section.dialog.confirm.title',
1139
+ i18nMessage: 'section.dialog.confirm.message',
1140
+ })
948
1141
 
949
- <details>
950
- <summary><strong>High Contrast</strong></summary>
1142
+ // Prompt (retorna el valor introduït o null si cancel·la)
1143
+ const value = await $ModalDialog.prompt({
1144
+ title: 'Introdueix un valor',
1145
+ placeholder: 'Escriu aquí...',
1146
+ })
1147
+ if (value !== null) { console.log('Valor:', value) }
951
1148
 
952
- ```css
953
- [data-theme='high-contrast'] {
954
- --zs3-bg-color: #000000;
955
- --zs3-fg-color: #ffffff;
956
- --zs3-primary-color: #0066ff;
957
- --zs3-primary-hover-color: #0052cc;
958
- --zs3-success-color: #00aa00;
959
- --zs3-warning-color: #ff9900;
960
- --zs3-error-color: #cc0000;
961
- --zs3-info-color: #0099cc;
962
- --zs3-border-color: #666666;
963
- --zs3-border-radius: 0.25rem;
964
- }
1149
+ // Prompt amb i18n
1150
+ const val = await $ModalDialog.promptI18n({
1151
+ i18nTitle: 'section.dialog.prompt.title',
1152
+ placeholder: i18n.t('section.dialog.prompt.message'),
1153
+ })
1154
+
1155
+ // Confirm Delete (diàleg especialitzat d'eliminació)
1156
+ const deleted = await $ModalDialog.confirmDelete('Element #42')
1157
+ if (deleted) { /* eliminar l'element */ }
965
1158
  ```
966
- </details>
967
1159
 
968
- <details>
969
- <summary><strong>System</strong></summary>
1160
+ #### HTML inline (index2.html)
970
1161
 
971
- Utilitza els mateixos valors que `light` per defecte, i canvia automaticament a valors de `dark` quan `prefers-color-scheme: dark` esta actiu.
972
- </details>
1162
+ ```html
1163
+ <!-- Diàleg personalitzat declarat en HTML -->
1164
+ <zs3-modal-dialog
1165
+ id="custom-dialog"
1166
+ i18n-title="section.dialog.confirm.title"
1167
+ i18n-message="section.dialog.confirm.message"
1168
+ icon="warning"
1169
+ show-accept
1170
+ show-cancel
1171
+ accept-variant="warning"
1172
+ i18n-accept-text="zs3.dialog.accept"
1173
+ i18n-cancel-text="zs3.dialog.cancel"
1174
+ backdrop
1175
+ close-on-escape>
1176
+ </zs3-modal-dialog>
1177
+
1178
+ <zs3-button variant="warning" icon="warning"
1179
+ zs3-click="document.getElementById('custom-dialog').$.show()">
1180
+ Obrir diàleg
1181
+ </zs3-button>
1182
+
1183
+ <!-- Mètodes estàtics des de handlers inline (definits a index2.ts) -->
1184
+ <zs3-button variant="primary" zs3-click="showDialogAlert()">Alert</zs3-button>
1185
+ <zs3-button variant="info" zs3-click="showDialogConfirm()">Confirm</zs3-button>
1186
+ <zs3-button variant="secondary" zs3-click="showDialogPrompt()">Prompt</zs3-button>
1187
+ <zs3-button variant="danger" icon="trash" zs3-click="showDialogDelete()">Confirm Delete</zs3-button>
1188
+ <div id="dialog-output">Resultat del diàleg...</div>
1189
+ ```
1190
+
1191
+ ```ts
1192
+ // index2.ts — handlers globals per als mètodes estàtics
1193
+ window.showDialogAlert = async () => {
1194
+ await $ModalDialog.alertI18n({
1195
+ i18nTitle: 'section.dialog.alert.title',
1196
+ i18nMessage: 'section.dialog.alert.message',
1197
+ })
1198
+ setOutput('dialog-output', 'Alert tancat.')
1199
+ }
973
1200
 
974
- ---
1201
+ window.showDialogConfirm = async () => {
1202
+ const result = await $ModalDialog.confirmI18n({
1203
+ i18nTitle: 'section.dialog.confirm.title',
1204
+ i18nMessage: 'section.dialog.confirm.message',
1205
+ })
1206
+ setOutput('dialog-output', result ? '✓ Confirmat!' : '✗ Cancel·lat')
1207
+ }
975
1208
 
976
- ## Internacionalitzacio ($I18n)
1209
+ window.showDialogPrompt = async () => {
1210
+ const value = await $ModalDialog.promptI18n({
1211
+ i18nTitle: 'section.dialog.prompt.title',
1212
+ placeholder: i18n.t('section.dialog.prompt.message'),
1213
+ })
1214
+ setOutput('dialog-output', value !== null ? `Valor: "${value}"` : 'Prompt cancel·lat')
1215
+ }
977
1216
 
978
- Sistema complet d'internacionalitzacio amb interpolacio de parametres i actualitzacio automatica del DOM.
1217
+ window.showDialogDelete = async () => {
1218
+ const result = await $ModalDialog.confirmDelete('Element #42')
1219
+ setOutput('dialog-output', result ? '🗑 Element eliminat!' : '✗ Cancel·lat')
1220
+ }
979
1221
 
980
- ```typescript
981
- import { i18n } from 'zs3-ui-components'
1222
+ // Escolta l'event del diàleg personalitzat (HTML)
1223
+ function setupCustomDialogListener(): void {
1224
+ const dialog = document.getElementById('custom-dialog')
1225
+ dialog?.addEventListener('zs3-dialog-accept', () => {
1226
+ setOutput('dialog-output', '✓ ACCEPTAT')
1227
+ $Notification.success('Acció confirmada!', { position: 'top-right', duration: 3000 })
1228
+ })
1229
+ dialog?.addEventListener('zs3-dialog-cancel', () => {
1230
+ setOutput('dialog-output', '✗ CANCEL·LAT')
1231
+ })
1232
+ }
982
1233
  ```
983
1234
 
984
- ### API
1235
+ #### TypeScript programàtic (index3.ts)
985
1236
 
986
- | Metode | Signatura | Descripcio |
987
- |--------|-----------|------------|
988
- | `init()` | `(config?: Partial<I18nConfig>) => void` | Inicialitza el sistema i18n. Carrega l'idioma guardat si existeix. |
989
- | `load()` | `(locale: string, translations: Record<string,string>) => void` | Carrega traduccions per un idioma. |
990
- | `loadMultiple()` | `(locales: Record<string, Record<string,string>>) => void` | Carrega multiples idiomes alhora. |
991
- | `t()` | `(key: string, params?: Record<string, string\|number>) => string` | Tradueix una clau. Suporta interpolacio amb `{param}`. |
992
- | `setLocale()` | `(locale: string, callback?: (locale: string) => void) => void` | Estableix un nou idioma. Persisteix a localStorage. |
993
- | `getLocale()` | `() => string` | Obte l'idioma actual. |
994
- | `getSaved()` | `() => string \| null` | Obte l'idioma guardat a localStorage. |
995
- | `getBrowserLocale()` | `() => string` | Detecta l'idioma del navegador. |
996
- | `setBrowserLocale()` | `() => void` | Estableix l'idioma segons el navegador (si te traduccions). |
997
- | `has()` | `(key: string) => boolean` | Verifica si existeix una traduccio. |
998
- | `getAvailableLocales()` | `() => string[]` | Llista tots els idiomes carregats. |
999
- | `getTranslations()` | `(locale?: string) => Record<string,string>` | Obte totes les traduccions d'un idioma. |
1237
+ ```ts
1238
+ import { $ModalDialog, $Button, $Notification } from 'zs3-ui-components'
1000
1239
 
1001
- ### Traduccions automatiques al DOM
1240
+ const dialog = new $ModalDialog()
1241
+ dialog.setAttribute('i18n-title', 'section.dialog.confirm.title')
1242
+ dialog.setAttribute('i18n-message', 'section.dialog.confirm.message')
1243
+ dialog.setAttribute('icon', 'warning')
1244
+ dialog.showAccept = true
1245
+ dialog.showCancel = true
1246
+ dialog.acceptVariant = 'warning'
1247
+ dialog.setAttribute('i18n-accept-text', 'zs3.dialog.accept')
1248
+ dialog.setAttribute('i18n-cancel-text', 'zs3.dialog.cancel')
1249
+ dialog.setAttribute('backdrop', '')
1250
+ dialog.setAttribute('close-on-escape', '')
1251
+ document.body.append(dialog) // va al body directament
1002
1252
 
1003
- Utilitzeu atributs `data-i18n` i `data-i18n-attr` per a traduccions automatiques:
1004
-
1005
- ```html
1006
- <!-- Tradueix el textContent -->
1007
- <h1 data-i18n="page.title">Titol per defecte</h1>
1253
+ dialog.addEventListener('zs3-dialog-accept', () => {
1254
+ $Notification.success('Acció confirmada!', { position: 'top-right', duration: 3000 })
1255
+ })
1256
+ dialog.addEventListener('zs3-dialog-cancel', () => {
1257
+ console.log('Diàleg cancel·lat')
1258
+ })
1008
1259
 
1009
- <!-- Tradueix atributs -->
1010
- <input data-i18n-attr='{"placeholder": "input.placeholder", "aria-label": "input.label"}' />
1260
+ const openBtn = new $Button()
1261
+ openBtn.textContent = 'Obrir diàleg'
1262
+ openBtn.variant = 'warning'
1263
+ openBtn.icon = 'warning'
1264
+ openBtn.addEventListener('zs3-click', () => dialog.show())
1011
1265
  ```
1012
1266
 
1013
- Quan canvieu l'idioma amb `setLocale()`, tots els elements amb `data-i18n` i `data-i18n-attr` s'actualitzen automaticament.
1267
+ ---
1014
1268
 
1015
- ### Event
1269
+ ### zs3-form
1016
1270
 
1017
- ```typescript
1018
- window.addEventListener('zs3-locale-change', (e: CustomEvent) => {
1019
- console.log('Nou idioma:', e.detail.locale)
1020
- console.log('Idioma anterior:', e.detail.previousLocale)
1021
- })
1022
- ```
1271
+ Formulari genèric amb camps configurables, validació, i18n complet i events de submit/cancel.
1023
1272
 
1024
- ### Exemple complet
1273
+ **Atributs:**
1025
1274
 
1026
- ```typescript
1027
- import { i18n } from 'zs3-ui-components'
1275
+ | Atribut | Descripció |
1276
+ |---------|------------|
1277
+ | `title` / `i18n-title` | Títol del formulari |
1278
+ | `description` / `i18n-description` | Descripció |
1279
+ | `accept-text` / `i18n-accept-text` | Text del botó d'enviar |
1280
+ | `cancel-text` / `i18n-cancel-text` | Text del botó de cancel·lar |
1281
+ | `accept-variant` | Variant del botó d'enviar |
1282
+ | `show-cancel` | Mostra el botó de cancel·lar |
1283
+
1284
+ **Tipus de camp `type`:** `text`, `email`, `password`, `number`, `tel`, `url`, `date`, `textarea`, `select`, `checkbox`
1285
+
1286
+ **Validació per camp:** `required`, `minLength`, `maxLength`, `min`, `max`, `pattern`
1287
+
1288
+ **Esdeveniments:** `zs3-form-submit` (amb `event.detail.values`), `zs3-form-cancel`
1289
+
1290
+ #### TypeScript programàtic (index3.ts) — recomanat
1291
+
1292
+ ```ts
1293
+ import { $Form } from 'zs3-ui-components'
1294
+
1295
+ const form = new $Form()
1296
+ form.setAttribute('i18n-title', 'form.title')
1297
+ form.setAttribute('i18n-description', 'form.description')
1298
+ form.showCancel = true
1299
+ form.acceptVariant = 'success'
1300
+
1301
+ form.setFields([
1302
+ {
1303
+ name: 'name',
1304
+ label: 'Nom',
1305
+ i18nLabel: 'form.field.name',
1306
+ type: 'text',
1307
+ required: true,
1308
+ placeholder: 'Nom',
1309
+ i18nPlaceholder: 'form.field.name.placeholder',
1310
+ validation: { minLength: 2, maxLength: 50 },
1311
+ },
1312
+ {
1313
+ name: 'email',
1314
+ label: 'Correu',
1315
+ i18nLabel: 'form.field.email',
1316
+ type: 'email',
1317
+ required: true,
1318
+ i18nPlaceholder: 'form.field.email.placeholder',
1319
+ },
1320
+ {
1321
+ name: 'age',
1322
+ label: 'Edat',
1323
+ i18nLabel: 'form.field.age',
1324
+ type: 'number',
1325
+ required: true,
1326
+ validation: { min: 18, max: 120 },
1327
+ },
1328
+ {
1329
+ name: 'birthdate',
1330
+ label: 'Data de naixement',
1331
+ type: 'date',
1332
+ required: false,
1333
+ },
1334
+ {
1335
+ name: 'country',
1336
+ label: 'País',
1337
+ i18nLabel: 'form.field.country',
1338
+ type: 'select',
1339
+ required: true,
1340
+ i18nPlaceholder: 'form.field.country.placeholder',
1341
+ options: [
1342
+ { value: 'es', label: 'Espanya', i18nLabel: 'form.country.spain' },
1343
+ { value: 'fr', label: 'França', i18nLabel: 'form.country.france' },
1344
+ { value: 'it', label: 'Itàlia', i18nLabel: 'form.country.italy' },
1345
+ { value: 'de', label: 'Alemanya', i18nLabel: 'form.country.germany' },
1346
+ { value: 'uk', label: 'Regne Unit', i18nLabel: 'form.country.uk' },
1347
+ ],
1348
+ },
1349
+ {
1350
+ name: 'bio',
1351
+ label: 'Biografia',
1352
+ i18nLabel: 'form.field.bio',
1353
+ type: 'textarea',
1354
+ required: false,
1355
+ i18nPlaceholder: 'form.field.bio.placeholder',
1356
+ rows: 3,
1357
+ validation: { maxLength: 500 },
1358
+ },
1359
+ {
1360
+ name: 'terms',
1361
+ label: 'Accepto els termes i condicions',
1362
+ i18nLabel: 'form.field.terms',
1363
+ type: 'checkbox',
1364
+ required: true,
1365
+ },
1366
+ ])
1028
1367
 
1029
- // Carregar traduccions
1030
- i18n.loadMultiple({
1031
- ca: { 'hello': 'Hola {name}!', 'btn.save': 'Desar' },
1032
- es: { 'hello': 'Hola {name}!', 'btn.save': 'Guardar' },
1033
- en: { 'hello': 'Hello {name}!', 'btn.save': 'Save' },
1368
+ form.addEventListener('zs3-form-submit', (e: Event) => {
1369
+ const { values } = (e as CustomEvent).detail
1370
+ console.log('Dades enviades:', values)
1371
+ // values → { name: 'Joan', email: 'joan@mail.com', country: 'es', terms: true, ... }
1034
1372
  })
1035
1373
 
1036
- // Inicialitzar
1037
- i18n.init({ locale: 'ca', fallbackLocale: 'en' })
1038
-
1039
- // Traduir amb interpolacio
1040
- i18n.t('hello', { name: 'Joan' }) // 'Hola Joan!'
1374
+ form.addEventListener('zs3-form-cancel', () => {
1375
+ form.reset()
1376
+ })
1041
1377
 
1042
- // Canviar idioma
1043
- i18n.setLocale('en')
1044
- i18n.t('hello', { name: 'Joan' }) // 'Hello Joan!'
1378
+ document.getElementById('app')!.append(form)
1045
1379
  ```
1046
1380
 
1047
1381
  ---
1048
1382
 
1049
- ## Bus d'Events (EventBus)
1050
-
1051
- Sistema de publicació/subscripció (pub/sub) per a la comunicació desacoblada entre components i mòduls. Inclou una instància global `eventBus` i la classe `EventBus` per crear busos addicionals independents.
1052
-
1053
- ```typescript
1054
- import { eventBus } from 'zs3-ui-components'
1055
- ```
1383
+ ### zs3-login-form
1056
1384
 
1057
- ### API
1385
+ Formulari de login preconstruït amb camps d'email i contrasenya, opció de botó de registre i traduccions integrades.
1058
1386
 
1059
- | Metode | Signatura | Descripcio |
1060
- |--------|-----------|------------|
1061
- | `on()` | `<T>(event: string, handler: (payload: T) => void) => () => void` | Subscriu un handler a un event. Retorna una funció per dessubscriure's. |
1062
- | `once()` | `<T>(event: string, handler: (payload: T) => void) => void` | Subscriu un handler d'un sol ús (s'elimina automàticament en ser cridat). |
1063
- | `emit()` | `<T>(event: string, payload?: T) => void` | Emet un event i passa el payload a tots els handlers registrats. |
1064
- | `off()` | `(event: string) => void` | Elimina tots els handlers d'un event concret. |
1065
- | `clear()` | `() => void` | Elimina tots els handlers de tots els events. |
1387
+ **Atributs:** `show-register`, `show-cancel`, `i18n-login-text`
1066
1388
 
1067
- ### Exemples
1389
+ **Esdeveniments:** `zs3-form-submit` (amb `detail.values.email` i `detail.values.password`), `zs3-login-register`
1068
1390
 
1069
- ```typescript
1070
- import { eventBus } from 'zs3-ui-components'
1391
+ #### HTML inline (index2.html) — en un modal
1071
1392
 
1072
- // Subscriure's a un event tipat
1073
- const unsub = eventBus.on<{ userId: number }>('user:login', ({ userId }) => {
1074
- console.log('Usuari autenticat:', userId)
1393
+ ```html
1394
+ <zs3-modal-dialog id="dlg-login" i18n-title="zs3.login.title" size="medium" backdrop close-on-escape>
1395
+ <zs3-login-form
1396
+ show-register
1397
+ zs3-form-submit="document.getElementById('dlg-login').$.hide(); document.getElementById('auth-out').textContent = 'Login: ' + JSON.stringify(event.detail.values)"
1398
+ zs3-login-register="document.getElementById('dlg-login').$.hide(); document.getElementById('dlg-register').$.show()">
1399
+ </zs3-login-form>
1400
+ </zs3-modal-dialog>
1401
+
1402
+ <zs3-button variant="primary" icon="login" position="left"
1403
+ zs3-click="document.getElementById('dlg-login').$.show()">
1404
+ Iniciar sessió
1405
+ </zs3-button>
1406
+ <div id="auth-out">Resultat auth...</div>
1407
+ ```
1408
+
1409
+ #### TypeScript programàtic (index3.ts)
1410
+
1411
+ ```ts
1412
+ import { $LoginForm, $ModalDialog } from 'zs3-ui-components'
1413
+
1414
+ const loginDialog = new $ModalDialog()
1415
+ loginDialog.setAttribute('i18n-title', 'zs3.login.title')
1416
+ loginDialog.setAttribute('size', 'medium')
1417
+ loginDialog.setAttribute('backdrop', '')
1418
+ loginDialog.setAttribute('close-on-escape', '')
1419
+ document.body.append(loginDialog)
1420
+
1421
+ const loginForm = new $LoginForm()
1422
+ loginForm.showRegister = true
1423
+
1424
+ loginForm.addEventListener('zs3-form-submit', (e: Event) => {
1425
+ const { values } = (e as CustomEvent).detail
1426
+ loginDialog.hide()
1427
+ console.log('Login:', values.email, values.password)
1075
1428
  })
1076
1429
 
1077
- // Emetre l'event
1078
- eventBus.emit('user:login', { userId: 42 })
1430
+ loginForm.addEventListener('zs3-login-register', () => {
1431
+ loginDialog.hide()
1432
+ registerDialog.show() // mostra el diàleg de registre
1433
+ })
1079
1434
 
1080
- // Dessubscriure's
1081
- unsub()
1435
+ loginDialog.append(loginForm)
1082
1436
 
1083
- // Handler d'un sol ús
1084
- eventBus.once('app:ready', () => {
1085
- console.log('Inicialitzacio completada')
1437
+ // Login inline (sense modal)
1438
+ const inlineLogin = new $LoginForm()
1439
+ inlineLogin.showRegister = true
1440
+ inlineLogin.showCancel = true
1441
+ inlineLogin.addEventListener('zs3-form-submit', (e: Event) => {
1442
+ const { values } = (e as CustomEvent).detail
1443
+ console.log('Login inline:', values)
1086
1444
  })
1445
+ document.getElementById('app')!.append(inlineLogin)
1446
+ ```
1087
1447
 
1088
- // Eliminar tots els handlers d'un event
1089
- eventBus.off('user:login')
1448
+ ---
1090
1449
 
1091
- // Netejar tots els events (util en tests)
1092
- eventBus.clear()
1093
- ```
1450
+ ### zs3-register-form
1094
1451
 
1095
- ### Crear un bus personalitzat
1452
+ Formulari de registre preconstruït amb camps nom, email, contrasenya i confirmació.
1096
1453
 
1097
- ```typescript
1098
- import { EventBus } from 'zs3-ui-components'
1454
+ **Atributs:** `show-cancel`, `i18n-register-text`
1099
1455
 
1100
- const myBus = new EventBus()
1101
- myBus.on('custom:event', (data) => console.log(data))
1102
- myBus.emit('custom:event', 'hola')
1103
- ```
1456
+ **Esdeveniments:** `zs3-form-submit`, `zs3-form-cancel`
1104
1457
 
1105
- ---
1106
-
1107
- ## Contenidor de Dependencies (DIContainer)
1108
-
1109
- Contenidor d'injecció de dependències lleuger. Permet registrar serveis com a **singleton** (instància compartida) o **transient** (nova instància en cada resolució), i recuperar-los per clau. Inclou una instància global `diContainer`.
1110
-
1111
- ```typescript
1112
- import { diContainer } from 'zs3-ui-components'
1113
- ```
1114
-
1115
- ### API
1116
-
1117
- | Metode | Signatura | Descripcio |
1118
- |--------|-----------|------------|
1119
- | `registerSingleton()` | `<T>(key: string, factory: () => T) => void` | Registra un servei singleton. La fàbrica s'executa una sola vegada i la instància es reutilitza. |
1120
- | `registerTransient()` | `<T>(key: string, factory: () => T) => void` | Registra un servei transient. La fàbrica s'executa en cada resolució retornant una nova instància. |
1121
- | `resolve()` | `<T>(key: string) => T` | Retorna la instància del servei associat a la clau. Llança `Error` si la clau no existeix. |
1122
- | `has()` | `(key: string) => boolean` | Comprova si un servei ha estat registrat. |
1123
- | `clear()` | `() => void` | Elimina tots els registres (util per a tests). |
1124
-
1125
- ### Exemples
1126
-
1127
- ```typescript
1128
- import { diContainer } from 'zs3-ui-components'
1458
+ #### HTML inline
1129
1459
 
1130
- // Registrar un singleton
1131
- diContainer.registerSingleton('apiClient', () => new ApiClient('https://api.example.com'))
1132
-
1133
- // La mateixa instancia es retorna sempre
1134
- const api1 = diContainer.resolve<ApiClient>('apiClient')
1135
- const api2 = diContainer.resolve<ApiClient>('apiClient')
1136
- console.log(api1 === api2) // true
1137
-
1138
- // Registrar un servei transient
1139
- diContainer.registerTransient('validator', () => new FormValidator())
1140
-
1141
- // Cada resolucio retorna una nova instancia
1142
- const v1 = diContainer.resolve<FormValidator>('validator')
1143
- const v2 = diContainer.resolve<FormValidator>('validator')
1144
- console.log(v1 === v2) // false
1145
-
1146
- // Comprovar si existeix
1147
- if (!diContainer.has('logger')) {
1148
- diContainer.registerSingleton('logger', () => new Logger())
1149
- }
1150
-
1151
- // Netejar (util en tests)
1152
- afterEach(() => {
1153
- diContainer.clear()
1460
+ ```html
1461
+ <zs3-modal-dialog id="dlg-register" i18n-title="zs3.register.title" size="medium" backdrop close-on-escape>
1462
+ <zs3-register-form
1463
+ show-cancel
1464
+ zs3-form-submit="document.getElementById('dlg-register').$.hide()"
1465
+ zs3-form-cancel="document.getElementById('dlg-register').$.hide()">
1466
+ </zs3-register-form>
1467
+ </zs3-modal-dialog>
1468
+ ```
1469
+
1470
+ #### TypeScript programàtic
1471
+
1472
+ ```ts
1473
+ import { $RegisterForm, $ModalDialog } from 'zs3-ui-components'
1474
+
1475
+ const registerDialog = new $ModalDialog()
1476
+ registerDialog.setAttribute('i18n-title', 'zs3.register.title')
1477
+ registerDialog.setAttribute('size', 'medium')
1478
+ registerDialog.setAttribute('backdrop', '')
1479
+ registerDialog.setAttribute('close-on-escape', '')
1480
+ document.body.append(registerDialog)
1481
+
1482
+ const registerForm = new $RegisterForm()
1483
+ registerForm.showCancel = true
1484
+
1485
+ registerForm.addEventListener('zs3-form-submit', (e: Event) => {
1486
+ const { values } = (e as CustomEvent).detail
1487
+ registerDialog.hide()
1488
+ console.log('Registre:', values)
1154
1489
  })
1155
- ```
1156
-
1157
- ### Crear un contenidor personalitzat
1158
-
1159
- ```typescript
1160
- import { DIContainer } from 'zs3-ui-components'
1161
-
1162
- const container = new DIContainer()
1163
- container.registerSingleton('config', () => ({ apiUrl: 'https://api.example.com' }))
1164
- const config = container.resolve<{ apiUrl: string }>('config')
1490
+ registerForm.addEventListener('zs3-form-cancel', () => registerDialog.hide())
1491
+ registerDialog.append(registerForm)
1165
1492
  ```
1166
1493
 
1167
1494
  ---
1168
1495
 
1169
- ## Router (SPA)
1170
-
1171
- Router client-side per a aplicacions **SPA (Single Page Application)**. Gestiona la navegació basada en `history.pushState`, intercepta clicks a enllaços `<a data-link>`, resol rutes amb paràmetres dinàmics i suporta guards per protegir l'accés a pàgines.
1172
-
1173
- ```typescript
1174
- import { Router } from 'zs3-ui-components'
1175
- import type { Route, RouteGuard, RouteContext } from 'zs3-ui-components'
1176
- ```
1177
-
1178
- ### Tipus
1179
-
1180
- #### `Route`
1181
-
1182
- | Camp | Tipus | Requerit | Descripcio |
1183
- |------|-------|----------|------------|
1184
- | `path` | `string` | Sí | Path de la ruta. Pot contenir segments dinàmics (`:param`). Usa `'*'` per a la ruta wildcard (pàgina 404). |
1185
- | `component` | `() => Promise<{ default: ... }>` | Sí | Funció de càrrega lazy del component de la pàgina. |
1186
- | `title` | `string` | No | Títol que s'aplica a `document.title` en activar la ruta. |
1187
- | `guard` | `RouteGuard` | No | Funció guard que controla l'accés a la ruta. |
1188
-
1189
- #### `RouteGuard`
1190
-
1191
- Funció executada abans de muntar el component d'una ruta. Rep un [`RouteContext`](#routecontext) i ha de retornar:
1192
-
1193
- - `true` — permet la navegació.
1194
- - `false` — cancel·la la navegació sense redirigir.
1195
- - `string` — redirigeix al path indicat.
1196
-
1197
- ```typescript
1198
- type RouteGuard = (context: RouteContext) => boolean | string
1199
- ```
1200
-
1201
- #### `RouteContext`
1202
-
1203
- | Camp | Tipus | Descripcio |
1204
- |------|-------|------------|
1205
- | `path` | `string` | Path complet de la URL actual. |
1206
- | `params` | `Record<string, string>` | Paràmetres dinàmics extrets del path (p.ex. `{ id: '42' }`). |
1207
- | `query` | `URLSearchParams` | Query string de la URL actual. |
1208
-
1209
- ### API de `Router`
1210
-
1211
- #### Constructor
1212
-
1213
- ```typescript
1214
- new Router(routes: Route[], outlet: HTMLElement)
1215
- ```
1216
-
1217
- | Paràmetre | Tipus | Descripcio |
1218
- |-----------|-------|------------|
1219
- | `routes` | `Route[]` | Llista de rutes de l'aplicació. |
1220
- | `outlet` | `HTMLElement` | Element del DOM on es muntaran els components de cada pàgina. |
1221
-
1222
- #### Mètodes públics
1223
-
1224
- | Metode | Signatura | Descripcio |
1225
- |--------|-----------|------------|
1226
- | `start()` | `() => void` | Inicia el router: escolta `popstate`, intercepta clicks a `<a data-link>` i resol la URL actual. Cridar una sola vegada a l'inici. |
1227
- | `navigate()` | `(path: string) => Promise<void>` | Navega programàticament a un path. No fa res si el path és el mateix que l'actual. |
1228
- | `back()` | `() => void` | Navega a la pàgina anterior de l'historial del navegador. |
1229
-
1230
- ### Exemple complet
1231
-
1232
- ```typescript
1233
- import { Router } from 'zs3-ui-components'
1234
- import type { Route, RouteGuard } from 'zs3-ui-components'
1235
-
1236
- // Guard d'autenticacio
1237
- const authGuard: RouteGuard = ({ path }) => {
1238
- return isLoggedIn() ? true : '/login'
1239
- }
1496
+ ### zs3-recover-password-form
1240
1497
 
1241
- const routes: Route[] = [
1242
- {
1243
- path: '/',
1244
- component: () => import('./pages/HomePage'),
1245
- title: 'Inici',
1246
- },
1247
- {
1248
- path: '/users/:id',
1249
- component: () => import('./pages/UserPage'),
1250
- title: 'Usuari',
1251
- guard: authGuard,
1252
- },
1253
- {
1254
- path: '/login',
1255
- component: () => import('./pages/LoginPage'),
1256
- title: 'Iniciar sessio',
1257
- },
1258
- {
1259
- path: '*',
1260
- component: () => import('./pages/NotFoundPage'),
1261
- title: 'Pàgina no trobada',
1262
- },
1263
- ]
1498
+ Formulari de recuperació de contrasenya amb camp d'email.
1264
1499
 
1265
- const router = new Router(routes, document.getElementById('app')!)
1266
- router.start()
1267
- ```
1500
+ **Atributs:** `show-cancel`, `i18n-submit-text`, `i18n-description`
1268
1501
 
1269
- ### Navegació en plantilles HTML
1502
+ **Esdeveniments:** `zs3-form-submit` (amb `detail.values.email`), `zs3-form-cancel`
1270
1503
 
1271
- Afegeix l'atribut `data-link` a qualsevol `<a>` perquè el router l'intercepti sense recarregar la pàgina:
1504
+ #### HTML inline
1272
1505
 
1273
1506
  ```html
1274
- <nav>
1275
- <a href="/" data-link>Inici</a>
1276
- <a href="/users/42" data-link>Perfil</a>
1277
- </nav>
1278
- ```
1279
-
1280
- ### Navegació programàtica
1281
-
1282
- ```typescript
1283
- // Navegar a una pàgina
1284
- await router.navigate('/users/42')
1285
-
1286
- // Tornar enrere
1287
- router.back()
1507
+ <zs3-modal-dialog id="dlg-recover" i18n-title="zs3.recover.title" size="small" backdrop close-on-escape>
1508
+ <zs3-recover-password-form
1509
+ i18n-description="zs3.recover.description"
1510
+ show-cancel
1511
+ zs3-form-submit="document.getElementById('dlg-recover').$.hide()"
1512
+ zs3-form-cancel="document.getElementById('dlg-recover').$.hide()">
1513
+ </zs3-recover-password-form>
1514
+ </zs3-modal-dialog>
1515
+ ```
1516
+
1517
+ #### TypeScript programàtic
1518
+
1519
+ ```ts
1520
+ import { $RecoverPasswordForm, $ModalDialog } from 'zs3-ui-components'
1521
+
1522
+ const recoverDialog = new $ModalDialog()
1523
+ recoverDialog.setAttribute('i18n-title', 'zs3.recover.title')
1524
+ recoverDialog.setAttribute('size', 'small')
1525
+ recoverDialog.setAttribute('backdrop', '')
1526
+ recoverDialog.setAttribute('close-on-escape', '')
1527
+ document.body.append(recoverDialog)
1528
+
1529
+ const recoverForm = new $RecoverPasswordForm()
1530
+ recoverForm.setAttribute('i18n-description', 'zs3.recover.description')
1531
+ recoverForm.showCancel = true
1532
+
1533
+ recoverForm.addEventListener('zs3-form-submit', (e: Event) => {
1534
+ const { values } = (e as CustomEvent).detail
1535
+ recoverDialog.hide()
1536
+ console.log('Email per recuperar:', values.email)
1537
+ })
1538
+ recoverForm.addEventListener('zs3-form-cancel', () => recoverDialog.hide())
1539
+ recoverDialog.append(recoverForm)
1288
1540
  ```
1289
1541
 
1290
1542
  ---
1291
1543
 
1292
- ## Components
1293
-
1294
- ### zs3-button
1295
-
1296
- Boto personalitzat amb suport per icones, variants, mides i efecte float.
1297
-
1298
- #### Atributs
1299
-
1300
- | Atribut | Tipus | Per defecte | Descripcio |
1301
- |---------|-------|-------------|------------|
1302
- | `icon` | `string` | - | Nom de la icona a mostrar. |
1303
- | `icon-size` | `string` | - | Mida de la icona. |
1304
- | `position` | `'left' \| 'right'` | `'right'` | Posicio de la icona. |
1305
- | `variant` | `'primary' \| 'secondary' \| 'success' \| 'danger' \| 'warning' \| 'info'` | - | Variant d'estil del boto. |
1306
- | `size` | `'sm' \| 'md' \| 'lg'` | - | Mida del boto. |
1307
- | `rounded` | `boolean` | `false` | Boto completament arrodonit. |
1308
- | `disabled` | `boolean` | `false` | Desactiva el boto. |
1309
- | `loading` | `boolean` | `false` | Mostra estat de carrega (spinner). |
1310
- | `float` | `'top' \| 'bottom' \| 'left' \| 'right'` | - | Posicio flotant. |
1311
- | `move` | `'x' \| 'y' \| 'xy'` | - | Direccio de moviment (drag). |
1312
- | `i18n-text` | `string` | - | Clau i18n per al text del boto. |
1313
- | `i18n-aria-label` | `string` | - | Clau i18n per a l'aria-label. |
1314
-
1315
- #### Events
1316
-
1317
- | Event | Detail | Descripcio |
1318
- |-------|--------|------------|
1319
- | `zs3-click` | `{ button, originalEvent }` | El boto ha estat clicat. |
1320
- | `zs3-icon-click` | `{ button, originalEvent }` | La icona del boto ha estat clicada. |
1321
-
1322
- #### Metodes publics
1544
+ ### zs3-window
1323
1545
 
1324
- | Metode | Signatura | Descripcio |
1325
- |--------|-----------|------------|
1326
- | `setText()` | `(text: string) => void` | Estableix el text del boto. |
1327
- | `getText()` | `() => string` | Obte el text del boto. |
1328
- | `click()` | `() => void` | Simula un clic programatic. |
1329
- | `setLoading()` | `(loading: boolean) => void` | Estableix l'estat de carrega. |
1330
- | `setDisabled()` | `(disabled: boolean) => void` | Estableix l'estat desactivat. |
1546
+ Finestra draggable amb barra de títol, botons de minimitzar/maximitzar/tancar i contingut personalitzable.
1331
1547
 
1332
- #### CSS Variables del boto
1548
+ **Atributs:**
1333
1549
 
1334
- ```css
1335
- :host {
1336
- --btn-padding-sm: 0.5rem 0.75rem;
1337
- --btn-padding-md: 0.625rem 1rem;
1338
- --btn-padding-lg: 0.75rem 1.5rem;
1339
- --btn-font-size-sm: var(--zs3-font-size-sm);
1340
- --btn-font-size-md: var(--zs3-font-size-base);
1341
- --btn-font-size-lg: var(--zs3-font-size-lg);
1342
- --btn-gap: 0.5rem;
1343
- --btn-radius: var(--zs3-border-radius);
1344
- --btn-transition: all var(--zs3-transition-fast);
1345
- }
1346
- ```
1550
+ | Atribut | Descripció |
1551
+ |---------|------------|
1552
+ | `title` / `i18n-title` | Títol de la finestra |
1553
+ | `width` | Amplada (px, %, etc.) |
1554
+ | `move` | `x` `y` `xy` — permet arrossegar |
1555
+ | `closable` | Mostra el botó de tancar |
1556
+ | `minimizable` | Mostra el botó de minimitzar |
1557
+ | `maximizable` | Mostra el botó de maximitzar |
1347
1558
 
1348
- #### Exemples
1559
+ #### HTML inline (index2.html)
1349
1560
 
1350
1561
  ```html
1351
- <!-- Boto basic -->
1352
- <zs3-button>Clic</zs3-button>
1353
-
1354
- <!-- Boto amb icona a l'esquerra -->
1355
- <zs3-button icon="save" position="left" variant="primary">Desar</zs3-button>
1356
-
1357
- <!-- Boto de perill arrodonit -->
1358
- <zs3-button variant="danger" rounded icon="trash">Eliminar</zs3-button>
1359
-
1360
- <!-- Boto en estat de carrega -->
1361
- <zs3-button loading variant="success">Processant...</zs3-button>
1562
+ <!-- Contenidor relatiu per delimitar el moviment -->
1563
+ <div style="position:relative;height:260px;border:1px dashed #e5e7eb;border-radius:.5rem;overflow:hidden">
1564
+ <zs3-window
1565
+ title="Finestra Draggable"
1566
+ width="280px"
1567
+ move="xy"
1568
+ closable
1569
+ minimizable
1570
+ maximizable
1571
+ style="position:absolute;top:16px;left:16px">
1572
+ <div style="padding:.75rem">
1573
+ <p>Contingut HTML de la finestra.</p>
1574
+ <zs3-button variant="success" icon="save" size="sm">Guardar</zs3-button>
1575
+ </div>
1576
+ </zs3-window>
1577
+
1578
+ <zs3-window
1579
+ i18n-title="form.title"
1580
+ width="240px"
1581
+ move="xy"
1582
+ closable
1583
+ minimizable
1584
+ style="position:absolute;top:16px;left:320px">
1585
+ <div style="padding:.75rem">
1586
+ <p>Títol via i18n-title.</p>
1587
+ </div>
1588
+ </zs3-window>
1589
+ </div>
1590
+ ```
1591
+
1592
+ #### TypeScript programàtic (index3.ts)
1593
+
1594
+ ```ts
1595
+ import { $Window, $Toolbar, $Button, $Notification } from 'zs3-ui-components'
1596
+
1597
+ const container = document.createElement('div')
1598
+ container.style.cssText = 'position:relative;height:260px;border:1px dashed #e5e7eb;border-radius:.5rem;overflow:hidden'
1599
+
1600
+ const win1 = new $Window()
1601
+ win1.setAttribute('title', 'Finestra 1 — Draggable')
1602
+ win1.setAttribute('width', '280px')
1603
+ win1.setAttribute('move', 'xy')
1604
+ win1.setAttribute('closable', '')
1605
+ win1.setAttribute('minimizable', '')
1606
+ win1.setAttribute('maximizable', '')
1607
+ win1.style.cssText = 'position:absolute;top:16px;left:16px'
1608
+
1609
+ const toolbar = new $Toolbar()
1610
+ toolbar.setAttribute('direction', 'row')
1611
+ const bSave = new $Button()
1612
+ bSave.textContent = 'Guardar'
1613
+ bSave.variant = 'success'
1614
+ bSave.icon = 'save'
1615
+ bSave.size = 'sm'
1616
+ bSave.addEventListener('zs3-click', () =>
1617
+ $Notification.success('Guardat!', { position: 'top-right', duration: 2000 })
1618
+ )
1619
+ toolbar.append(bSave)
1620
+ win1.append(toolbar)
1362
1621
 
1363
- <!-- Boto amb i18n -->
1364
- <zs3-button i18n-text="btn.save" variant="primary"></zs3-button>
1622
+ // Finestra amb i18n al títol
1623
+ const win2 = new $Window()
1624
+ win2.setAttribute('i18n-title', 'form.title') // traducció automàtica
1625
+ win2.setAttribute('width', '240px')
1626
+ win2.setAttribute('move', 'xy')
1627
+ win2.setAttribute('closable', '')
1628
+ win2.style.cssText = 'position:absolute;top:16px;left:320px'
1365
1629
 
1366
- <!-- Boto flotant -->
1367
- <zs3-button float="right" icon="menu">Menu</zs3-button>
1630
+ container.append(win1, win2)
1631
+ document.getElementById('app')!.append(container)
1368
1632
  ```
1369
1633
 
1370
1634
  ---
1371
1635
 
1372
- ### zs3-icon
1373
-
1374
- Component d'icona SVG amb suport per transformacions, colors i interactivitat.
1375
-
1376
- #### Atributs
1636
+ ## Utilities
1377
1637
 
1378
- | Atribut | Tipus | Per defecte | Descripcio |
1379
- |---------|-------|-------------|------------|
1380
- | `name` | `string` | - | Nom de la icona (obligatori). |
1381
- | `size` | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | Mida: small=12px, medium=24px, large=36px, o valor numeric. |
1382
- | `color` | `'primary' \| 'secondary' \| 'success' \| 'danger' \| 'warning' \| 'info' \| 'inherit'` | - | Variant de color. |
1383
- | `stroke-width` | `number` | `2` | Amplada del trac SVG. |
1384
- | `rotate` | `number` | `0` | Rotacio en graus. |
1385
- | `flip` | `'horizontal' \| 'vertical' \| 'both'` | - | Direccio de reflexio. |
1386
- | `clickable` | `boolean` | `false` | Fa la icona interactiva amb efecte hover. |
1387
- | `i18n-aria-label` | `string` | - | Clau i18n per a l'aria-label. |
1638
+ ### $Store Reactive State
1388
1639
 
1389
- #### Events
1640
+ Gestió d'estat reactiva amb reducers i subscripcions tipades. Inspirat en Redux.
1390
1641
 
1391
- | Event | Detail | Descripcio |
1392
- |-------|--------|------------|
1393
- | `zs3-icon-click` | `{ icon, name, originalEvent }` | Icona clicada (si `clickable`). |
1642
+ ```ts
1643
+ import { $Store } from 'zs3-ui-components'
1394
1644
 
1395
- #### Metodes estatics
1645
+ interface CounterState { count: number }
1396
1646
 
1397
- | Metode | Signatura | Descripcio |
1398
- |--------|-----------|------------|
1399
- | `$Icon.addIcon()` | `(name: string, path: string) => void` | Afegeix una icona personalitzada (SVG path). |
1400
- | `$Icon.addIcons()` | `(icons: Record<string,string>) => void` | Afegeix multiples icones. |
1401
- | `$Icon.getAvailableIcons()` | `() => string[]` | Llista totes les icones disponibles. |
1647
+ const counterStore = new $Store<CounterState>({ count: 0 })
1402
1648
 
1403
- #### Icones disponibles
1649
+ // Registra reducers
1650
+ counterStore.registerReducer('increment', (state) => ({ count: state.count + 1 }))
1651
+ counterStore.registerReducer('decrement', (state) => ({ count: Math.max(0, state.count - 1) }))
1652
+ counterStore.registerReducer('reset', () => ({ count: 0 }))
1653
+ counterStore.registerReducer('add', (state, payload) => ({ count: state.count + (payload as number) }))
1404
1654
 
1405
- `increase`, `decrease`, `error`, `warning`, `information`, `close`, `left-arrow`, `right-arrow`, `up-arrow`, `down-arrow`, `menu`, `accept`, `cancel`, `search`, `delete`, `dark`, `sun`, `users`, `copy`, `trash`, `upload`, `logout`, `login`, `moon`, `modal`, `save`, `edit`, `download`, `settings`, `home`, `star`, `heart`, `filter`, `refresh`, `lock`, `unlock`, `bell`, `mail`, `phone`, `calendar`, `clock`, `eye`, `eye-off`, `plus`, `minus`, `check`, `x`
1655
+ // Subscriu-te als canvis
1656
+ counterStore.subscribe('*', (state) => {
1657
+ document.getElementById('counter')!.textContent = String(state.count)
1658
+ })
1406
1659
 
1407
- #### Exemples
1660
+ // Subscripció per acció específica
1661
+ counterStore.subscribe('increment', (state) => {
1662
+ console.log('Increment! Nou valor:', state.count)
1663
+ })
1408
1664
 
1409
- ```html
1410
- <zs3-icon name="warning"></zs3-icon>
1411
- <zs3-icon name="users" size="large" color="primary"></zs3-icon>
1412
- <zs3-icon name="right-arrow" rotate="90"></zs3-icon>
1413
- <zs3-icon name="menu" flip="horizontal" clickable></zs3-icon>
1414
- <zs3-icon name="heart" size="48" color="danger" stroke-width="1.5"></zs3-icon>
1415
- ```
1665
+ // Dispatch
1666
+ counterStore.dispatch('increment')
1667
+ counterStore.dispatch('add', 10)
1668
+ counterStore.dispatch('reset')
1416
1669
 
1417
- ```typescript
1418
- // Afegir icona personalitzada
1419
- $Icon.addIcon('custom', '<path d="M12 2L2 22h20z"/>')
1670
+ // Llegir l'estat actual
1671
+ const current = counterStore.getState()
1420
1672
  ```
1421
1673
 
1422
- ---
1423
-
1424
- ### zs3-toolbar
1425
-
1426
- Contenidor per organitzar botons i elements interactius.
1427
-
1428
- #### Atributs
1429
-
1430
- | Atribut | Tipus | Per defecte | Descripcio |
1431
- |---------|-------|-------------|------------|
1432
- | `direction` | `'row' \| 'column'` | `'row'` | Direccio del layout. |
1433
- | `align` | `'start' \| 'center' \| 'end' \| 'space-between' \| 'space-around'` | - | Alineacio dels elements. |
1434
- | `gap` | `string` | `'0.625rem'` | Espai entre elements (valor CSS). |
1435
- | `variant` | `'default' \| 'compact' \| 'spacious'` | - | Variant d'estil. |
1436
- | `rounded` | `boolean` | `false` | Toolbar arrodonit. |
1437
- | `float` | `'top' \| 'bottom' \| 'left' \| 'right'` | - | Posicio flotant. |
1438
- | `move` | `'x' \| 'y' \| 'xy'` | - | Direccio de moviment (drag). |
1439
- | `i18n-aria-label` | `string` | - | Clau i18n per a l'aria-label. |
1440
-
1441
- #### Events
1442
-
1443
- | Event | Detail | Descripcio |
1444
- |-------|--------|------------|
1445
- | `zs3-toolbar-click` | `{ toolbar, button, index, originalEvent }` | Un boto del toolbar ha estat clicat. |
1446
- | `zs3-button-added` | `{ toolbar, button, count }` | S'ha afegit un boto programaticament. |
1447
- | `zs3-button-removed` | `{ toolbar, button, index, count }` | S'ha eliminat un boto. |
1448
- | `zs3-buttons-cleared` | `{ toolbar, removedCount }` | S'han eliminat tots els botons. |
1449
-
1450
- #### Metodes publics
1451
-
1452
- | Metode | Signatura | Descripcio |
1453
- |--------|-----------|------------|
1454
- | `addButton()` | `(button: $Button) => void` | Afegeix un boto al toolbar. |
1455
- | `removeButton()` | `(index: number) => void` | Elimina un boto per index. |
1456
- | `removeButtonElement()` | `(button: $Button) => void` | Elimina un boto per referencia. |
1457
- | `getButton()` | `(index: number) => $Button \| null` | Obte un boto per index. |
1458
- | `getButtons()` | `() => $Button[]` | Obte tots els botons. |
1459
- | `getButtonCount()` | `() => number` | Obte el nombre de botons. |
1460
- | `clearButtons()` | `() => void` | Elimina tots els botons. |
1461
- | `enableAllButtons()` | `() => void` | Activa tots els botons. |
1462
- | `disableAllButtons()` | `() => void` | Desactiva tots els botons. |
1463
- | `showAllButtons()` | `() => void` | Mostra tots els botons. |
1464
- | `hideAllButtons()` | `() => void` | Amaga tots els botons. |
1465
-
1466
- #### Exemples
1674
+ #### HTML inline (index2.html) + index2.ts
1467
1675
 
1468
1676
  ```html
1469
- <!-- Toolbar horitzontal -->
1470
- <zs3-toolbar>
1471
- <zs3-button icon="save">Desar</zs3-button>
1472
- <zs3-button icon="edit">Editar</zs3-button>
1473
- <zs3-button icon="trash" variant="danger">Eliminar</zs3-button>
1474
- </zs3-toolbar>
1475
-
1476
- <!-- Toolbar vertical flotant -->
1477
- <zs3-toolbar direction="column" float="left" gap="0.5rem">
1478
- <zs3-button icon="home"></zs3-button>
1479
- <zs3-button icon="settings"></zs3-button>
1480
- </zs3-toolbar>
1481
-
1482
- <!-- Toolbar arrossegable -->
1483
- <zs3-toolbar move="xy" rounded variant="compact">
1484
- <zs3-button icon="menu">Menu</zs3-button>
1485
- </zs3-toolbar>
1677
+ <div id="store-counter" style="font-size:3.5rem;font-weight:700;text-align:center">0</div>
1678
+ <div>
1679
+ <zs3-button variant="danger" zs3-click="storeDecrement()">-1</zs3-button>
1680
+ <zs3-button variant="success" zs3-click="storeIncrement()">+1</zs3-button>
1681
+ <zs3-button zs3-click="storeReset()">Reset</zs3-button>
1682
+ <zs3-button variant="info" zs3-click="storeAdd(10)">+10</zs3-button>
1683
+ </div>
1684
+ <div id="store-log"></div>
1685
+ ```
1686
+
1687
+ ```ts
1688
+ // index2.ts
1689
+ window.storeIncrement = () => counterStore.dispatch('increment')
1690
+ window.storeDecrement = () => counterStore.dispatch('decrement')
1691
+ window.storeReset = () => counterStore.dispatch('reset')
1692
+ window.storeAdd = (n: number) => counterStore.dispatch('add', n)
1693
+
1694
+ counterStore.subscribe('*', (state) => {
1695
+ const el = document.getElementById('store-counter')
1696
+ if (el) el.textContent = String(state.count)
1697
+
1698
+ const logEl = document.getElementById('store-log')
1699
+ if (logEl) {
1700
+ const entry = document.createElement('div')
1701
+ entry.textContent = `[${new Date().toLocaleTimeString()}] count = ${state.count}`
1702
+ logEl.prepend(entry)
1703
+ }
1704
+ })
1486
1705
  ```
1487
1706
 
1488
- ---
1489
-
1490
- ### zs3-notification
1491
-
1492
- Component de notificacions toast amb diversos tipus i posicions.
1493
-
1494
- #### Atributs
1495
-
1496
- | Atribut | Tipus | Per defecte | Descripcio |
1497
- |---------|-------|-------------|------------|
1498
- | `type` | `'error' \| 'warning' \| 'info' \| 'success'` | `'info'` | Tipus de notificacio. |
1499
- | `title` | `string` | `''` | Titol de la notificacio. |
1500
- | `message` | `string` | `''` | Missatge de la notificacio. |
1501
- | `duration` | `number` | `5000` | Durada en mil-lisegons. `0` per no amagar automaticament. |
1502
- | `position` | `NotificationPosition` | `'top-center'` | Posicio a la pantalla. |
1503
- | `dismissible` | `boolean` | `true` | Mostra boto de tancar. |
1504
- | `show-icon` | `boolean` | `true` | Mostra icona del tipus. |
1505
- | `show-progress` | `boolean` | `false` | Mostra barra de progres. |
1506
- | `auto-show` | `boolean` | `false` | Mostra automaticament al muntar-se. |
1507
- | `i18n-title` | `string` | - | Clau i18n per al titol. |
1508
- | `i18n-message` | `string` | - | Clau i18n per al missatge. |
1509
- | `i18n-close-label` | `string` | - | Clau i18n per al boto de tancar. |
1510
-
1511
- **Posicions disponibles:** `top-center`, `top-left`, `top-right`, `bottom-center`, `bottom-left`, `bottom-right`
1512
-
1513
- #### Events
1514
-
1515
- | Event | Detail | Descripcio |
1516
- |-------|--------|------------|
1517
- | `zs3-notification-show` | `{ notification, type, message }` | La notificacio s'ha mostrat. |
1518
- | `zs3-notification-hide` | `{ notification, type, message }` | La notificacio s'ha amagat. |
1519
- | `zs3-notification-dismiss` | `{ notification, type }` | L'usuari ha tancat la notificacio. |
1520
- | `zs3-notification-click` | `{ notification, type }` | S'ha clicat la notificacio. |
1521
-
1522
- #### Metodes publics
1523
-
1524
- | Metode | Signatura | Descripcio |
1525
- |--------|-----------|------------|
1526
- | `show()` | `() => void` | Mostra la notificacio. |
1527
- | `hide()` | `() => void` | Amaga la notificacio. |
1528
- | `toggle()` | `() => void` | Alterna la visibilitat. |
1529
- | `update()` | `(config: Partial<NotificationConfig>) => void` | Actualitza el contingut. |
1530
- | `isVisible()` | `() => boolean` | Retorna si es visible. |
1531
-
1532
- #### Factory estatics
1533
-
1534
- | Metode | Signatura | Descripcio |
1535
- |--------|-----------|------------|
1536
- | `$Notification.success()` | `(message: string, options?) => $Notification` | Crea i mostra una notificacio d'exit. |
1537
- | `$Notification.error()` | `(message: string, options?) => $Notification` | Crea i mostra una notificacio d'error. |
1538
- | `$Notification.warning()` | `(message: string, options?) => $Notification` | Crea i mostra una notificacio d'advertencia. |
1539
- | `$Notification.info()` | `(message: string, options?) => $Notification` | Crea i mostra una notificacio informativa. |
1540
- | `$Notification.create()` | `(config: NotificationConfig) => $Notification` | Crea una notificacio amb configuracio completa. |
1541
-
1542
- #### Exemples
1543
-
1544
- ```html
1545
- <zs3-notification
1546
- type="success"
1547
- title="Desat!"
1548
- message="Els canvis s'han guardat correctament"
1549
- auto-show
1550
- show-progress
1551
- ></zs3-notification>
1552
- ```
1707
+ #### TypeScript programàtic (index3.ts)
1553
1708
 
1554
- ```typescript
1555
- // Crear programaticament
1556
- $Notification.success('Operacio completada!', { duration: 3000 })
1557
- $Notification.error('Error de connexio', { position: 'bottom-right' })
1709
+ ```ts
1710
+ // Subscripció que actualitza elements creats per TypeScript
1711
+ counterStore.subscribe('*', (state) => {
1712
+ counterEl.textContent = String(state.count)
1713
+ const entry = el('div', {}, `[${new Date().toLocaleTimeString()}] count = ${state.count}`)
1714
+ storeLog.prepend(entry)
1715
+ })
1558
1716
 
1559
- // Amb i18n
1560
- $Notification.create({
1561
- type: 'info',
1562
- i18nTitle: 'notif.info.title',
1563
- i18nMessage: 'notif.info.msg',
1564
- showProgress: true,
1717
+ // Botons d'acció creats programàticament
1718
+ const storeActions: Array<[string, string, () => void]> = [
1719
+ ['-1', 'danger', () => counterStore.dispatch('decrement')],
1720
+ ['+1', 'success', () => counterStore.dispatch('increment')],
1721
+ ['Reset', '', () => counterStore.dispatch('reset')],
1722
+ ['+10', 'info', () => counterStore.dispatch('add', 10)],
1723
+ ]
1724
+ storeActions.forEach(([label, variant, fn]) => {
1725
+ const b = btn(label, variant || undefined)
1726
+ b.addEventListener('zs3-click', fn)
1727
+ storeRow.append(b)
1565
1728
  })
1566
1729
  ```
1567
1730
 
1568
1731
  ---
1569
1732
 
1570
- ### zs3-modal
1571
-
1572
- Dialog modal basic amb backdrop, suport per teclat i focus trap.
1573
-
1574
- #### Atributs
1575
-
1576
- | Atribut | Tipus | Per defecte | Descripcio |
1577
- |---------|-------|-------------|------------|
1578
- | `size` | `'small' \| 'medium' \| 'large' \| 'fullscreen'` | `'medium'` | Mida del modal. |
1579
- | `backdrop` | `boolean` | `true` | Mostra backdrop. |
1580
- | `close-on-backdrop` | `boolean` | `true` | Tanca al clicar el backdrop. |
1581
- | `close-on-escape` | `boolean` | `true` | Tanca amb la tecla Escape. |
1582
- | `centered` | `boolean` | `true` | Centra verticalment. |
1583
- | `scrollable` | `boolean` | `false` | Permet scroll del contingut. |
1733
+ ### EventBus
1584
1734
 
1585
- **Mides:**
1586
- - `small`: max-width 400px
1587
- - `medium`: max-width 600px
1588
- - `large`: max-width 900px
1589
- - `fullscreen`: 100% pantalla
1735
+ Comunicació desacoblada entre components via events amb nom.
1590
1736
 
1591
- #### Events
1592
-
1593
- | Event | Detail | Descripcio |
1594
- |-------|--------|------------|
1595
- | `zs3-modal-show` | `{ modal }` | El modal s'ha obert. |
1596
- | `zs3-modal-hide` | `{ modal }` | El modal s'ha tancat. |
1597
- | `zs3-modal-backdrop-click` | `{ modal }` | S'ha clicat el backdrop. |
1598
-
1599
- #### Metodes publics
1600
-
1601
- | Metode | Signatura | Descripcio |
1602
- |--------|-----------|------------|
1603
- | `show()` | `() => void` | Mostra el modal. Bloqueja scroll del body i trap focus. |
1604
- | `hide()` | `() => void` | Amaga el modal. Restaura scroll i focus. |
1605
- | `toggle()` | `() => void` | Alterna la visibilitat. |
1606
- | `isVisible()` | `() => boolean` | Retorna si es visible. |
1607
- | `update()` | `(config: Partial<ModalConfig>) => void` | Actualitza la configuracio. |
1737
+ ```ts
1738
+ import { eventBus } from 'zs3-ui-components'
1608
1739
 
1609
- #### Exemples
1740
+ // Subscripció
1741
+ eventBus.on('user:login', (data) => console.log('Usuari ha entrat:', data))
1742
+ eventBus.on('app:error', (data) => console.error('Error:', data))
1743
+ eventBus.on('data:update', (data) => console.log('Dades actualitzades:', data))
1610
1744
 
1611
- ```html
1612
- <zs3-modal id="myModal" size="large" centered>
1613
- <h2>Titol del Modal</h2>
1614
- <p>Contingut del modal</p>
1615
- </zs3-modal>
1745
+ // Subscripció a TOTS els events
1746
+ eventBus.on('*', (payload) => console.log('Event rebut:', payload))
1616
1747
 
1617
- <script>
1618
- document.getElementById('myModal').show()
1619
- </script>
1748
+ // Emissió
1749
+ eventBus.emit('user:login', { name: 'Joan', role: 'admin' })
1750
+ eventBus.emit('app:error', { code: 404, msg: 'Not found' })
1751
+ eventBus.emit('data:update', { items: 42 })
1620
1752
  ```
1621
1753
 
1622
- ---
1623
-
1624
- ### zs3-modal-dialog
1625
-
1626
- Dialog modal avancat amb titol, missatge, icona i botons d'accio. Hereta de `zs3-modal`.
1627
-
1628
- #### Atributs (propis)
1629
-
1630
- | Atribut | Tipus | Per defecte | Descripcio |
1631
- |---------|-------|-------------|------------|
1632
- | `title` | `string` | `''` | Titol del dialog. |
1633
- | `message` | `string` | `''` | Missatge del dialog. |
1634
- | `show-accept` | `boolean` | `false` | Mostra boto d'acceptar. |
1635
- | `show-cancel` | `boolean` | `false` | Mostra boto de cancellar. |
1636
- | `show-close` | `boolean` | `false` | Mostra boto de tancar. |
1637
- | `accept-text` | `string` | `'Accept'` | Text del boto acceptar. |
1638
- | `cancel-text` | `string` | `'Cancel'` | Text del boto cancellar. |
1639
- | `close-text` | `string` | `'Close'` | Text del boto tancar. |
1640
- | `accept-variant` | `Variant` | `'primary'` | Variant del boto acceptar. |
1641
- | `cancel-variant` | `Variant` | `'secondary'` | Variant del boto cancellar. |
1642
- | `icon` | `string` | - | Nom de la icona a mostrar a la capcalera. |
1643
- | `i18n-title` | `string` | - | Clau i18n per al titol. |
1644
- | `i18n-message` | `string` | - | Clau i18n per al missatge. |
1645
- | `i18n-accept-text` | `string` | - | Clau i18n per al text d'acceptar. |
1646
- | `i18n-cancel-text` | `string` | - | Clau i18n per al text de cancellar. |
1647
- | `i18n-close-text` | `string` | - | Clau i18n per al text de tancar. |
1648
-
1649
- Tambe hereta tots els atributs de [`zs3-modal`](#zs3-modal): `size`, `backdrop`, `close-on-backdrop`, `close-on-escape`, `centered`, `scrollable`.
1650
-
1651
- #### Events
1652
-
1653
- | Event | Detail | Descripcio |
1654
- |-------|--------|------------|
1655
- | `zs3-dialog-accept` | `{ dialog, action: 'accept' }` | Boto acceptar clicat. |
1656
- | `zs3-dialog-cancel` | `{ dialog, action: 'cancel' }` | Boto cancellar clicat. |
1657
- | `zs3-dialog-close` | `{ dialog, action: 'close' }` | Boto tancar clicat. |
1658
- | `zs3-modal-show` | `{ modal }` | Heretat: dialog obert. |
1659
- | `zs3-modal-hide` | `{ modal }` | Heretat: dialog tancat. |
1660
-
1661
- #### Metodes publics
1662
-
1663
- | Metode | Signatura | Descripcio |
1664
- |--------|-----------|------------|
1665
- | `showDialog()` | `() => Promise<DialogAction>` | Mostra el dialog i retorna una Promise que es resol amb l'accio (`'accept'`, `'cancel'` o `'close'`). |
1666
- | `updateDialog()` | `(config: Partial<ModalDialogConfig>) => void` | Actualitza el contingut del dialog. |
1667
-
1668
- #### Factory estatics
1669
-
1670
- | Metode | Signatura | Descripcio |
1671
- |--------|-----------|------------|
1672
- | `$ModalDialog.confirm()` | `(message: string, title?: string) => Promise<boolean>` | Dialog de confirmacio. Retorna `true` si accepta. |
1673
- | `$ModalDialog.confirmI18n()` | `(config) => Promise<boolean>` | Dialog de confirmacio amb i18n. |
1674
- | `$ModalDialog.alert()` | `(message: string, title?: string) => Promise<void>` | Dialog d'alerta. |
1675
- | `$ModalDialog.alertI18n()` | `(config) => Promise<void>` | Dialog d'alerta amb i18n. |
1676
- | `$ModalDialog.prompt()` | `(title: string, content: HTMLElement) => Promise<DialogAction>` | Dialog amb contingut personalitzat via slot. |
1677
- | `$ModalDialog.confirmDelete()` | `(itemName: string) => Promise<boolean>` | Dialog de confirmacio d'eliminacio. |
1678
- | `$ModalDialog.confirmDeleteI18n()` | `(config) => Promise<boolean>` | Dialog d'eliminacio amb i18n. |
1679
- | `$ModalDialog.create()` | `(config: ModalDialogConfig) => $ModalDialog` | Crea un dialog des de configuracio completa. |
1680
-
1681
- #### Exemples
1754
+ #### HTML inline (index2.html) + index2.ts
1682
1755
 
1683
1756
  ```html
1684
- <!-- Dialog de confirmacio declaratiu -->
1685
- <zs3-modal-dialog
1686
- id="dlg"
1687
- title="Confirmar"
1688
- message="Estas segur?"
1689
- show-accept
1690
- show-cancel
1691
- accept-variant="primary"
1692
- size="small"
1693
- ></zs3-modal-dialog>
1757
+ <div>
1758
+ <zs3-button variant="primary" zs3-click="emitBusEvent('user:login', { name: 'Joan', role: 'admin' })">user:login</zs3-button>
1759
+ <zs3-button variant="warning" zs3-click="emitBusEvent('app:error', { code: 404 })">app:error</zs3-button>
1760
+ <zs3-button variant="info" zs3-click="emitBusEvent('data:update', { items: 42 })">data:update</zs3-button>
1761
+ </div>
1762
+ <div id="eventbus-output"></div>
1763
+ ```
1764
+
1765
+ ```ts
1766
+ // index2.ts
1767
+ window.emitBusEvent = (name: string, data: unknown) => eventBus.emit(name, data)
1768
+
1769
+ function setupEventBusListener(): void {
1770
+ const output = document.getElementById('eventbus-output')
1771
+ ;['user:login', 'app:error', 'data:update'].forEach((evt) => {
1772
+ eventBus.on(evt, (data) => {
1773
+ if (output) {
1774
+ const entry = document.createElement('div')
1775
+ entry.textContent = `[${new Date().toLocaleTimeString()}] ${evt}: ${JSON.stringify(data)}`
1776
+ output.prepend(entry)
1777
+ if (output.children.length > 10) output.lastChild?.remove()
1778
+ }
1779
+ })
1780
+ })
1781
+ }
1694
1782
  ```
1695
1783
 
1696
- ```typescript
1697
- // Factory estatic
1698
- const result = await $ModalDialog.confirm('Estas segur?', 'Confirmar')
1699
- if (result) { /* acceptat */ }
1784
+ #### TypeScript programàtic (index3.ts)
1700
1785
 
1701
- // Factory amb i18n
1702
- const result = await $ModalDialog.confirmI18n({
1703
- i18nTitle: 'dlg.confirm.title',
1704
- i18nMessage: 'dlg.confirm.msg',
1705
- i18nAcceptText: 'btn.accept',
1706
- i18nCancelText: 'btn.cancel',
1707
- })
1708
-
1709
- // Confirmacio d'eliminacio
1710
- const deleted = await $ModalDialog.confirmDelete('fitxer.txt')
1711
-
1712
- // Dialog programatic complet
1713
- const dialog = $ModalDialog.create({
1714
- title: 'Configuracio',
1715
- message: 'Selecciona les opcions',
1716
- showAccept: true,
1717
- showCancel: true,
1718
- acceptVariant: 'success',
1719
- icon: 'settings',
1786
+ ```ts
1787
+ const busEvents: Array<[string, string, object]> = [
1788
+ ['user:login', 'primary', { name: 'Joan', role: 'admin' }],
1789
+ ['app:error', 'warning', { code: 404, msg: 'Not found' }],
1790
+ ['data:update','info', { items: 42 }],
1791
+ ]
1792
+ busEvents.forEach(([evtName, variant, payload]) => {
1793
+ eventBus.on(evtName, (data) => {
1794
+ const entry = el('div', {}, `[${new Date().toLocaleTimeString()}] ${evtName}: ${JSON.stringify(data)}`)
1795
+ busLog.prepend(entry)
1796
+ })
1797
+ const b = btn(`Emit ${evtName}`, variant)
1798
+ b.addEventListener('zs3-click', () => eventBus.emit(evtName, payload))
1799
+ busRow.append(b)
1720
1800
  })
1721
- document.body.appendChild(dialog)
1722
- const action = await dialog.showDialog()
1723
- dialog.remove()
1724
1801
  ```
1725
1802
 
1726
1803
  ---
1727
1804
 
1728
- ### zs3-window
1729
-
1730
- Component de finestra arrossegable amb barra de titol i controls.
1731
-
1732
- #### Atributs
1805
+ ### $Storage — LocalStorage
1733
1806
 
1734
- | Atribut | Tipus | Per defecte | Descripcio |
1735
- |---------|-------|-------------|------------|
1736
- | `title` | `string` | `'Window'` | Titol de la finestra. |
1737
- | `width` | `string` | `'300px'` | Amplada (valor CSS). |
1738
- | `height` | `string` | `'auto'` | Altura (valor CSS). |
1739
- | `move` | `'x' \| 'y' \| 'xy'` | - | Direccio de moviment. S'arrossega per la barra de titol. |
1740
- | `closable` | `boolean` | `false` | Mostra boto de tancar. |
1741
- | `minimizable` | `boolean` | `false` | Mostra boto de minimitzar. |
1742
- | `maximizable` | `boolean` | `false` | Mostra boto de maximitzar. |
1743
- | `i18n-title` | `string` | - | Clau i18n per al titol. |
1807
+ Wrapper de `localStorage` amb serialització JSON automàtica.
1744
1808
 
1745
- #### Events
1746
-
1747
- | Event | Detail | Descripcio |
1748
- |-------|--------|------------|
1749
- | `zs3-window-close` | `{ window }` | Boto tancar clicat. |
1750
- | `zs3-window-minimize` | `{ window }` | Boto minimitzar clicat. |
1751
- | `zs3-window-maximize` | `{ window }` | Boto maximitzar clicat. |
1752
- | `zs3-move-start` | `MoveEventDetail` | Inici d'arrossegament. |
1753
- | `zs3-move` | `MoveEventDetail` | Durant l'arrossegament. |
1754
- | `zs3-move-end` | `MoveEventDetail` | Fi d'arrossegament. |
1755
- | `zs3-move-cancel` | `MoveEventDetail` | Arrossegament cancel-lat (Escape). |
1756
- | `zs3-move-boundary` | `MoveEventDetail` | S'ha tocat un limit. |
1757
-
1758
- #### Metodes publics
1809
+ ```ts
1810
+ import { storage } from 'zs3-ui-components'
1759
1811
 
1760
- | Metode | Signatura | Descripcio |
1761
- |--------|-----------|------------|
1762
- | `setTitle()` | `(title: string) => void` | Estableix el titol de la finestra. |
1763
- | `close()` | `() => void` | Emet l'event `zs3-window-close` (no elimina del DOM). |
1812
+ storage.set('user.name', 'Joan')
1813
+ storage.set('user.prefs', { theme: 'dark', locale: 'ca' })
1764
1814
 
1765
- #### Exemple
1815
+ const name = storage.get('user.name') // → 'Joan'
1816
+ const prefs = storage.get('user.prefs') // → { theme: 'dark', locale: 'ca' }
1817
+ const miss = storage.get('noexist') // → null
1766
1818
 
1767
- ```html
1768
- <zs3-window title="La meva finestra" move="xy" closable minimizable width="400px" height="300px">
1769
- <p>Contingut de la finestra. Arrossega la barra de titol!</p>
1770
- </zs3-window>
1819
+ storage.remove('user.name')
1820
+ storage.clear()
1771
1821
  ```
1772
1822
 
1773
- ---
1823
+ #### HTML inline (index2.html) + index2.ts
1774
1824
 
1775
- ### zs3-form
1776
-
1777
- Component de formulari dinamic amb validacio integrada. Permet crear formularis des de configuracio JSON o programaticament.
1778
-
1779
- #### Atributs
1780
-
1781
- | Atribut | Tipus | Per defecte | Descripcio |
1782
- |---------|-------|-------------|------------|
1783
- | `title` | `string` | `''` | Titol del formulari. |
1784
- | `description` | `string` | `''` | Descripcio del formulari. |
1785
- | `accept-text` | `string` | `'Acceptar'` | Text del boto d'acceptar. |
1786
- | `cancel-text` | `string` | `'Cancel·lar'` | Text del boto de cancellar. |
1787
- | `show-cancel` | `boolean` | `false` | Mostra el boto de cancellar. |
1788
- | `accept-variant` | `Variant` | `'primary'` | Variant del boto d'acceptar. |
1789
- | `cancel-variant` | `Variant` | `'secondary'` | Variant del boto de cancellar. |
1790
- | `disabled` | `boolean` | `false` | Desactiva tot el formulari. |
1791
- | `fields` | `string` | - | Camps del formulari en format JSON. |
1792
- | `i18n-title` | `string` | - | Clau i18n per al titol. |
1793
- | `i18n-description` | `string` | - | Clau i18n per a la descripcio. |
1794
- | `i18n-accept-text` | `string` | - | Clau i18n per al text d'acceptar. |
1795
- | `i18n-cancel-text` | `string` | - | Clau i18n per al text de cancellar. |
1796
-
1797
- #### Events
1798
-
1799
- | Event | Detail | Descripcio |
1800
- |-------|--------|------------|
1801
- | `zs3-form-submit` | `{ values, isValid }` | El formulari s'ha enviat amb dades valides. |
1802
- | `zs3-form-cancel` | `{ values }` | S'ha clicat el boto de cancellar. |
1803
- | `zs3-form-change` | `{ name, value, isValid }` | Un camp ha canviat de valor. |
1804
- | `zs3-form-validation` | `ValidationResult` | L'estat de validacio ha canviat. |
1805
-
1806
- #### Metodes publics
1807
-
1808
- | Metode | Signatura | Descripcio |
1809
- |--------|-----------|------------|
1810
- | `setFields()` | `(fields: FormFieldConfig[]) => void` | Estableix els camps del formulari. |
1811
- | `getValues()` | `() => Record<string, unknown>` | Obte tots els valors actuals. |
1812
- | `getValue()` | `(name: string) => unknown` | Obte el valor d'un camp. |
1813
- | `setValue()` | `(name: string, value: unknown) => void` | Estableix el valor d'un camp. |
1814
- | `setValues()` | `(values: Record<string, unknown>) => void` | Estableix multiples valors. |
1815
- | `reset()` | `() => void` | Reinicia el formulari als valors per defecte. |
1816
- | `validate()` | `() => ValidationResult` | Valida tots els camps i retorna el resultat. |
1817
- | `isValid()` | `() => boolean` | Retorna si el formulari es valid. |
1818
- | `getErrors()` | `() => Record<string, string>` | Obte els errors de validacio actuals. |
1819
- | `appendTo()` | `(parent: string \| HTMLElement) => void` | Afegeix el formulari a un element. |
1820
-
1821
- #### Tipus de camp (FormFieldType)
1822
-
1823
- `text`, `email`, `password`, `number`, `tel`, `url`, `textarea`, `select`, `checkbox`, `date`, `time`, `datetime-local`
1824
-
1825
- #### Configuracio de camp (FormFieldConfig)
1826
-
1827
- ```typescript
1828
- interface FormFieldConfig {
1829
- name: string // Nom unic del camp
1830
- label: string // Etiqueta visible
1831
- i18nLabel?: string // Clau i18n per l'etiqueta
1832
- type: FormFieldType // Tipus de camp
1833
- required?: boolean // Camp obligatori
1834
- placeholder?: string // Text placeholder
1835
- i18nPlaceholder?: string // Clau i18n pel placeholder
1836
- defaultValue?: string | number | boolean
1837
- options?: FormFieldOption[] // Per a selects
1838
- validation?: FormFieldValidation
1839
- errorMessage?: string // Missatge d'error personalitzat
1840
- i18nErrorMessage?: string // Clau i18n pel missatge d'error
1841
- disabled?: boolean
1842
- readonly?: boolean
1843
- rows?: number // Per textareas
1825
+ ```html
1826
+ <div>
1827
+ <input id="storage-key" type="text" placeholder="clau" value="demo.key" />
1828
+ <input id="storage-value" type="text" placeholder="valor" value="valor de prova" />
1829
+ <zs3-button variant="primary" zs3-click="storageSet()" size="sm">set()</zs3-button>
1830
+ <zs3-button zs3-click="storageGet()" size="sm">get()</zs3-button>
1831
+ <zs3-button variant="danger" zs3-click="storageRemove()" size="sm">remove()</zs3-button>
1832
+ </div>
1833
+ <div id="storage-output"></div>
1834
+ ```
1835
+
1836
+ ```ts
1837
+ // index2.ts
1838
+ window.storageSet = () => {
1839
+ const key = (document.getElementById('storage-key') as HTMLInputElement).value
1840
+ const val = (document.getElementById('storage-value') as HTMLInputElement).value
1841
+ storage.set(key, val)
1842
+ setOutput('storage-output', `Guardat: "${key}" = "${val}"`)
1844
1843
  }
1845
-
1846
- interface FormFieldValidation {
1847
- pattern?: string | RegExp // Patro regex
1848
- min?: number // Valor minim (numbers)
1849
- max?: number // Valor maxim (numbers)
1850
- minLength?: number // Longitud minima
1851
- maxLength?: number // Longitud maxima
1852
- custom?: (value: unknown) => boolean | string // Validacio personalitzada
1844
+ window.storageGet = () => {
1845
+ const key = (document.getElementById('storage-key') as HTMLInputElement).value
1846
+ const val = storage.get(key)
1847
+ setOutput('storage-output', val !== null
1848
+ ? `Llegit: "${key}" = "${val}"`
1849
+ : `Clau "${key}" no trobada`
1850
+ )
1853
1851
  }
1854
-
1855
- interface FormFieldOption {
1856
- value: string
1857
- label: string
1858
- i18nLabel?: string
1852
+ window.storageRemove = () => {
1853
+ const key = (document.getElementById('storage-key') as HTMLInputElement).value
1854
+ storage.remove(key)
1855
+ setOutput('storage-output', `Eliminat: "${key}"`)
1859
1856
  }
1860
1857
  ```
1861
1858
 
1862
- #### Factory estatics
1859
+ #### TypeScript programàtic (index3.ts)
1863
1860
 
1864
- | Metode | Signatura | Descripcio |
1865
- |--------|-----------|------------|
1866
- | `$Form.create()` | `(config: FormConfig) => $Form` | Crea un formulari des de configuracio. |
1867
- | `$Form.prompt()` | `(config: FormConfig) => Promise<Record<string,unknown> \| null>` | Crea i mostra un formulari. Retorna els valors o `null` si es cancella. |
1861
+ ```ts
1862
+ const bSet = btn('set()', 'primary'); bSet.size = 'sm'
1863
+ bSet.addEventListener('zs3-click', () => {
1864
+ storage.set(keyInput.value, valInput.value)
1865
+ setOutput('storage-out-ts', `Guardat: "${keyInput.value}" = "${valInput.value}"`)
1866
+ })
1868
1867
 
1869
- #### Exemples
1868
+ const bGet = btn('get()'); bGet.size = 'sm'
1869
+ bGet.addEventListener('zs3-click', () => {
1870
+ const v = storage.get(keyInput.value)
1871
+ setOutput('storage-out-ts', v !== null
1872
+ ? `Llegit: "${keyInput.value}" = "${v}"`
1873
+ : `Clau "${keyInput.value}" no trobada`
1874
+ )
1875
+ })
1870
1876
 
1871
- ```html
1872
- <!-- Formulari declaratiu -->
1873
- <zs3-form
1874
- id="contactForm"
1875
- title="Contacte"
1876
- i18n-title="form.contact.title"
1877
- show-cancel
1878
- ></zs3-form>
1879
-
1880
- <script>
1881
- const form = document.getElementById('contactForm');
1882
- form.setFields([
1883
- { name: 'name', label: 'Nom', type: 'text', required: true },
1884
- { name: 'email', label: 'Email', type: 'email', required: true },
1885
- { name: 'message', label: 'Missatge', type: 'textarea', rows: 5 }
1886
- ]);
1887
-
1888
- form.addEventListener('zs3-form-submit', (e) => {
1889
- console.log('Valors:', e.detail.values);
1890
- });
1891
- </script>
1892
- ```
1893
-
1894
- ```typescript
1895
- // Crear programaticament
1896
- const form = $Form.create({
1897
- title: 'Nou usuari',
1898
- showCancel: true,
1899
- fields: [
1900
- { name: 'username', label: 'Usuari', type: 'text', required: true },
1901
- { name: 'role', label: 'Rol', type: 'select', options: [
1902
- { value: 'admin', label: 'Administrador' },
1903
- { value: 'user', label: 'Usuari' }
1904
- ]}
1905
- ]
1906
- });
1907
- document.body.appendChild(form);
1908
-
1909
- // Amb Promise
1910
- const values = await $Form.prompt({
1911
- title: 'Introdueix dades',
1912
- fields: [
1913
- { name: 'code', label: 'Codi', type: 'text', required: true }
1914
- ]
1915
- });
1916
- if (values) {
1917
- console.log('Codi:', values.code);
1918
- }
1877
+ const bRm = btn('remove()', 'danger'); bRm.size = 'sm'
1878
+ bRm.addEventListener('zs3-click', () => {
1879
+ storage.remove(keyInput.value)
1880
+ setOutput('storage-out-ts', `Eliminat: "${keyInput.value}"`)
1881
+ })
1919
1882
  ```
1920
1883
 
1921
1884
  ---
1922
1885
 
1923
- ### zs3-login-form
1924
-
1925
- Formulari de login predefinit amb camps d'email i contrasenya. Hereta de `zs3-form`.
1926
-
1927
- #### Atributs (propis)
1928
-
1929
- | Atribut | Tipus | Per defecte | Descripcio |
1930
- |---------|-------|-------------|------------|
1931
- | `show-register` | `boolean` | `false` | Mostra un boto per anar al registre. |
1932
- | `login-text` | `string` | `'Iniciar sessio'` | Text del boto de login. |
1933
- | `register-text` | `string` | `'Registrar-se'` | Text del boto de registre. |
1934
- | `i18n-login-text` | `string` | - | Clau i18n per al text de login. |
1935
- | `i18n-register-text` | `string` | - | Clau i18n per al text de registre. |
1886
+ ### HttpClient
1936
1887
 
1937
- Tambe hereta tots els atributs de [`zs3-form`](#zs3-form).
1888
+ Client HTTP amb base URL, timeout i tipatge genèric de respostes.
1938
1889
 
1939
- #### Events
1890
+ ```ts
1891
+ import { HttpClient } from 'zs3-ui-components'
1940
1892
 
1941
- | Event | Detail | Descripcio |
1942
- |-------|--------|------------|
1943
- | `zs3-form-submit` | `{ values: { email, password }, isValid }` | Formulari enviat. |
1944
- | `zs3-login-register` | `{ form }` | Boto de registre clicat. |
1945
-
1946
- #### Camps predefinits
1893
+ const http = new HttpClient({
1894
+ baseUrl: 'https://api.example.com',
1895
+ timeout: 8000,
1896
+ })
1947
1897
 
1948
- - **email**: Camp d'email obligatori
1949
- - **password**: Camp de contrasenya obligatori (minim 6 caracters)
1898
+ // GET tipat
1899
+ const res = await http.get<User[]>('/users?_limit=3')
1900
+ console.log(res.status) // → 200
1901
+ console.log(res.data) // → User[]
1950
1902
 
1951
- #### Exemple
1903
+ // POST / PUT / PATCH / DELETE
1904
+ await http.post('/users', { name: 'Joan', email: 'joan@example.com' })
1905
+ await http.put('/users/1', { name: 'Joan Updated' })
1906
+ await http.patch('/users/1', { name: 'Joan' })
1907
+ await http.delete('/users/1')
1952
1908
 
1953
- ```html
1954
- <zs3-login-form
1955
- i18n-title="app.login.title"
1956
- show-register
1957
- zs3-form-submit="log.info('Login:', event.detail.values)"
1958
- zs3-login-register="window.location = '/register'"
1959
- ></zs3-login-form>
1909
+ // Gestió d'errors
1910
+ try {
1911
+ await http.get('/nonexistent')
1912
+ } catch (e: unknown) {
1913
+ const err = e as { status?: number; message?: string }
1914
+ console.error(`HTTP [${err.status ?? 'network'}]: ${err.message}`)
1915
+ }
1960
1916
  ```
1961
1917
 
1962
- ---
1963
-
1964
- ### zs3-register-form
1965
-
1966
- Formulari de registre predefinit amb camps complets i validacio de contrasenya. Hereta de `zs3-form`.
1967
-
1968
- #### Atributs (propis)
1969
-
1970
- | Atribut | Tipus | Per defecte | Descripcio |
1971
- |---------|-------|-------------|------------|
1972
- | `register-text` | `string` | `'Registrar-se'` | Text del boto de registre. |
1973
- | `i18n-register-text` | `string` | - | Clau i18n per al text de registre. |
1974
-
1975
- Tambe hereta tots els atributs de [`zs3-form`](#zs3-form).
1976
-
1977
- #### Camps predefinits
1978
-
1979
- - **email**: Email obligatori
1980
- - **username**: Nom d'usuari (3-20 caracters, alfanumeric + guio baix)
1981
- - **firstName**: Nom obligatori
1982
- - **lastName**: Cognom obligatori
1983
- - **password**: Contrasenya (minim 8 caracters)
1984
- - **confirmPassword**: Confirmacio de contrasenya (ha de coincidir)
1985
-
1986
- #### Validacio especial
1987
-
1988
- El formulari valida automaticament que `password` i `confirmPassword` coincideixin.
1989
-
1990
- #### Exemple
1918
+ #### HTML inline (index2.html) + index2.ts
1991
1919
 
1992
1920
  ```html
1993
- <zs3-register-form
1994
- title="Crear compte"
1995
- i18n-title="app.register.title"
1996
- show-cancel
1997
- zs3-form-submit="handleRegistration(event.detail.values)"
1998
- zs3-form-cancel="window.location = '/login'"
1999
- ></zs3-register-form>
1921
+ <div>
1922
+ <zs3-button variant="primary" zs3-click="httpGetUsers()">GET /users</zs3-button>
1923
+ <zs3-button variant="success" zs3-click="httpGetPost()">GET /posts/1</zs3-button>
1924
+ <zs3-button variant="danger" zs3-click="httpError()">Error 404</zs3-button>
1925
+ </div>
1926
+ <div id="http-output">Fes una petició...</div>
2000
1927
  ```
2001
1928
 
2002
- ---
2003
-
2004
- ### zs3-recover-password-form
2005
-
2006
- Formulari de recuperacio de contrasenya amb un unic camp d'email. Hereta de `zs3-form`.
2007
-
2008
- #### Atributs (propis)
2009
-
2010
- | Atribut | Tipus | Per defecte | Descripcio |
2011
- |---------|-------|-------------|------------|
2012
- | `submit-text` | `string` | `'Recuperar contrasenya'` | Text del boto d'enviament. |
2013
- | `i18n-submit-text` | `string` | - | Clau i18n per al text d'enviament. |
2014
-
2015
- Tambe hereta tots els atributs de [`zs3-form`](#zs3-form).
2016
-
2017
- #### Camps predefinits
2018
-
2019
- - **email**: Email obligatori
1929
+ ```ts
1930
+ // index2.ts
1931
+ const http = new HttpClient({ baseUrl: 'https://jsonplaceholder.typicode.com', timeout: 8000 })
2020
1932
 
2021
- #### Exemple
1933
+ window.httpGetUsers = async () => {
1934
+ setOutput('http-output', 'Carregant...')
1935
+ try {
1936
+ const res = await http.get<{ id: number; name: string }[]>('/users?_limit=3')
1937
+ setOutput('http-output',
1938
+ `GET /users [${res.status}]:\n${res.data.map((u) => ` - ${u.name}`).join('\n')}`,
1939
+ )
1940
+ } catch (e) { setOutput('http-output', `Error: ${e}`) }
1941
+ }
2022
1942
 
2023
- ```html
2024
- <zs3-recover-password-form
2025
- title="Has oblidat la contrasenya?"
2026
- i18n-title="app.recover.title"
2027
- show-cancel
2028
- zs3-form-submit="sendRecoveryEmail(event.detail.values.email)"
2029
- ></zs3-recover-password-form>
1943
+ window.httpError = async () => {
1944
+ setOutput('http-output', 'Carregant...')
1945
+ try {
1946
+ await http.get('/nonexistent-endpoint-12345')
1947
+ } catch (e: unknown) {
1948
+ const err = e as { status?: number; message?: string }
1949
+ setOutput('http-output', `Error HTTP [${err.status ?? 'network'}]: ${err.message ?? String(e)}`)
1950
+ }
1951
+ }
2030
1952
  ```
2031
1953
 
2032
- ---
2033
-
2034
- ### zs3-select-theme
2035
-
2036
- Selector desplegable per canviar de tema. Es posiciona de forma fixa (`position: fixed`) per defecte.
2037
-
2038
- #### Atributs
1954
+ #### TypeScript programàtic (index3.ts)
2039
1955
 
2040
- | Atribut | Tipus | Per defecte | Descripcio |
2041
- |---------|-------|-------------|------------|
2042
- | `zs3-position` | `string` | `'position: fixed; top: 1rem; right: 1rem'` | CSS de posicionament personalitzat. |
2043
-
2044
- #### Exemple
2045
-
2046
- ```html
2047
- <!-- Posicio per defecte (fixed top-right) -->
2048
- <zs3-select-theme></zs3-select-theme>
1956
+ ```ts
1957
+ const bUsers = btn('GET /users', 'primary')
1958
+ bUsers.addEventListener('zs3-click', async () => {
1959
+ httpOutPre.textContent = 'Carregant...'
1960
+ try {
1961
+ const res = await http.get<{ id: number; name: string }[]>('/users?_limit=3')
1962
+ httpOutPre.textContent =
1963
+ `GET /users [${res.status}]:\n${res.data.map((u) => ` - ${u.name}`).join('\n')}`
1964
+ } catch (e) { httpOutPre.textContent = `Error: ${e}` }
1965
+ })
2049
1966
 
2050
- <!-- Posicio personalitzada -->
2051
- <zs3-select-theme zs3-position="position: fixed; bottom: 1rem; left: 1rem"></zs3-select-theme>
1967
+ const bErr = btn('Error 404', 'danger')
1968
+ bErr.addEventListener('zs3-click', async () => {
1969
+ try {
1970
+ await http.get('/nonexistent')
1971
+ } catch (e: unknown) {
1972
+ const err = e as { status?: number; message?: string }
1973
+ httpOutPre.textContent = `Error HTTP [${err.status ?? 'network'}]: ${err.message}`
1974
+ }
1975
+ })
2052
1976
  ```
2053
1977
 
2054
1978
  ---
2055
1979
 
2056
- ### zs3-select-locale
1980
+ ### $Log — Logging
2057
1981
 
2058
- Selector desplegable per canviar d'idioma. Mostra els idiomes carregats al sistema i18n.
1982
+ Logger avançat amb nivells i formatació a la consola del navegador.
2059
1983
 
2060
- #### Atributs
1984
+ ```ts
1985
+ import { log } from 'zs3-ui-components'
2061
1986
 
2062
- | Atribut | Tipus | Per defecte | Descripcio |
2063
- |---------|-------|-------------|------------|
2064
- | `zs3-position` | `string` | `'position: fixed; top: 1rem; right: 1rem'` | CSS de posicionament personalitzat. |
1987
+ log.info('Aplicació iniciada', { version: '1.2.0' })
1988
+ log.warn('Configuració incompleta', { missing: 'api-key' })
1989
+ log.error('Error en la petició', { code: 500, url: '/api/users' })
1990
+ log.success('Operació completada!')
1991
+ log.debug('Dades de debug', { state: counterStore.getState() })
1992
+ ```
2065
1993
 
2066
- #### Exemple
1994
+ ```ts
1995
+ // index2.ts — exposa log globalment per a handlers inline
1996
+ window.log = log
2067
1997
 
2068
- ```html
2069
- <zs3-select-locale></zs3-select-locale>
1998
+ // index3.ts — logging d'events globals
1999
+ window.addEventListener('zs3-locale-change', () => log.info(`Idioma: ${i18n.getLocale()}`))
2000
+ window.addEventListener('zs3-theme-change', () => log.info(`Tema: ${themeManager.getTheme()}`))
2070
2001
  ```
2071
2002
 
2072
2003
  ---
2073
2004
 
2074
- ## Inline Event Handlers
2005
+ ### DIContainer Dependency Injection
2075
2006
 
2076
- ZS3 suporta handlers d'events inline directament als atributs HTML dels components. Dins del codi de l'atribut, hi ha disponibles les seguents variables injectades:
2007
+ Contenidor d'injecció de dependències amb singletons i fàbriques.
2077
2008
 
2078
- | Variable | Tipus | Descripcio |
2079
- |----------|-------|------------|
2080
- | `$` | `(selector: string, root?: Document \| HTMLElement \| ShadowRoot) => ZS3 \| null` | Funcio de seleccio d'elements. El segon parametre opcional limita la cerca a un element arrel. |
2081
- | `log` | `$Log` | Sistema de logging. |
2082
- | `debug` | `$Debug` | Utilitat de debugging. |
2083
- | `params` | `$Parameters` | Gestio de parametres d'URL. |
2084
- | `storage` | `$Storage` | Gestio de localStorage. |
2085
- | `$env` | `() => Record<string,string>` | Informacio d'entorn. |
2086
- | `this` | `HTMLElement` | L'element que emet l'event. |
2087
- | `event` | `CustomEvent` | L'event emes. |
2009
+ ```ts
2010
+ import { diContainer } from 'zs3-ui-components'
2088
2011
 
2089
- ### Exemple d'us
2012
+ // Registra
2013
+ diContainer.registerSingleton('httpClient', () => http)
2014
+ diContainer.registerSingleton('userService', () => ({
2015
+ label: 'UserService',
2016
+ getUser: (id: number) => http.get(`/users/${id}`),
2017
+ getUsers: () => http.get('/users'),
2018
+ }))
2090
2019
 
2091
- ```html
2092
- <!-- Boto que mostra una notificacio -->
2093
- <zs3-button
2094
- variant="success"
2095
- zs3-click="
2096
- const n = $('#notif').$;
2097
- n.setAttribute('type', 'success');
2098
- n.setAttribute('title', 'Exit!');
2099
- n.setAttribute('message', 'Operacio completada');
2100
- n.forceUpdate();
2101
- n.show();
2102
- log.info('Notificacio mostrada')
2103
- "
2104
- >Mostrar notificacio</zs3-button>
2105
-
2106
- <!-- Boto que obre un dialog i processa el resultat -->
2107
- <zs3-button
2108
- variant="primary"
2109
- zs3-click="
2110
- $('#myDialog').$.showDialog().then(function(action) {
2111
- $('#output').$.textContent = 'Resultat: ' + action;
2112
- log.info('Dialog: ' + action)
2113
- })
2114
- "
2115
- >Obrir dialog</zs3-button>
2020
+ // Resol
2021
+ const svc = diContainer.resolve<{ label: string; getUsers: () => Promise<unknown> }>('userService')
2022
+ const users = await svc.getUsers()
2116
2023
 
2117
- <!-- Boto que desa a storage -->
2118
- <zs3-button
2119
- zs3-click="
2120
- storage.set('lastClick', new Date().toISOString());
2121
- log.info('Desat a storage: ' + storage.get('lastClick'))
2122
- "
2123
- >Desar timestamp</zs3-button>
2024
+ // Comprova
2025
+ diContainer.has('httpClient') // → true
2026
+ diContainer.has('unknownService') // → false
2124
2027
  ```
2125
2028
 
2126
- Els inline handlers tambe funcionen amb events de float i move:
2029
+ #### HTML inline (index2.html) + index2.ts
2127
2030
 
2128
2031
  ```html
2129
- <zs3-toolbar
2130
- float="top"
2131
- zs3-float-show-start="log.info('Toolbar apareixent')"
2132
- zs3-float-hide-end="log.info('Toolbar amagat')"
2133
- >
2134
- <zs3-button>Boto</zs3-button>
2135
- </zs3-toolbar>
2136
- ```
2137
-
2138
- ---
2139
-
2140
- ## Guia de desenvolupament de nous components
2141
-
2142
- ### Estructura de fitxers
2143
-
2144
- ```
2145
- src/
2146
- components/
2147
- zs3-BaseComponent.ts # Classe base abstracta
2148
- zs3-Button.ts # Component boto
2149
- zs3-Icon.ts # Component icona
2150
- zs3-Toolbar.ts # Component toolbar
2151
- zs3-Modal.ts # Component modal
2152
- zs3-ModalDialog.ts # Component dialog (hereta Modal)
2153
- zs3-Notification.ts # Component notificacio
2154
- zs3-Window.ts # Component finestra
2155
- zs3-Form.ts # Component formulari dinamic
2156
- zs3-LoginForm.ts # Formulari de login (hereta Form)
2157
- zs3-RegisterForm.ts # Formulari de registre (hereta Form)
2158
- zs3-RecoverPasswordForm.ts # Formulari recuperacio (hereta Form)
2159
- zs3-SelectTheme.ts # Selector de tema
2160
- zs3-SelectLocale.ts # Selector d'idioma
2161
- zs3-common.ts # Float, Move, generateEvent
2162
- types.ts # Tipus compartits (Variant)
2163
- decorators/
2164
- component.ts # @Component
2165
- observedAttributes.ts # @ObservedAttributes (amb opcio inherit)
2166
- float.ts # @Float
2167
- move.ts # @Move
2168
- attr.ts # @Attr
2169
- index.ts # Re-exporta tot
2170
- css/
2171
- zs3.css # Definicions de temes i estils base
2172
- i18n/
2173
- defaults.ts # Traduccions per defecte dels components
2174
- zs3.ts # Classe ZS3, $, params, log, debug, storage
2175
- theme.ts # ThemeManager
2176
- i18n.ts # Sistema i18n
2177
- types.ts # EventDescription
2178
- index.ts # Entry point (exporta tot, registra components)
2179
- ```
2180
-
2181
- ### $BaseComponent
2182
-
2183
- Tots els components ZS3 hereten de `$BaseComponent`, que es una classe abstracta que extén `HTMLElement`.
2184
-
2185
- #### Opcions del constructor
2186
-
2187
- ```typescript
2188
- interface BaseComponentOptions {
2189
- useShadowDOM?: boolean // true per defecte
2190
- shadowMode?: 'open' | 'closed' // 'open' per defecte
2191
- delegatesFocus?: boolean // false per defecte
2032
+ <div>
2033
+ <zs3-button variant="primary" zs3-click="diResolveDemo()">resolve('userService')</zs3-button>
2034
+ <zs3-button variant="info" zs3-click="diHasDemo()">has('httpClient')?</zs3-button>
2035
+ </div>
2036
+ <div id="di-output">Resultat DI...</div>
2037
+ ```
2038
+
2039
+ ```ts
2040
+ // index2.ts
2041
+ window.diResolveDemo = () => {
2042
+ const svc = diContainer.resolve<{ label: string }>('userService')
2043
+ setOutput('di-output', `Resolt 'userService': ${svc.label}`)
2044
+ }
2045
+ window.diHasDemo = () => {
2046
+ const h = diContainer.has('httpClient')
2047
+ const u = diContainer.has('unknownService')
2048
+ setOutput('di-output', `has('httpClient') = ${h} | has('unknownService') = ${u}`)
2192
2049
  }
2193
2050
  ```
2194
2051
 
2195
- ### Cicle de vida
2196
-
2197
- ```
2198
- constructor
2199
- -> initialize() // Setup logic, configuracio de _floatTargetSelector, etc.
2200
- -> emitLifecycleEvent('zs3-built')
2201
-
2202
- connectedCallback (element inserit al DOM)
2203
- -> beforeMount() // Primera vegada nomes
2204
- -> _mounted = true
2205
- -> emitLifecycleEvent('zs3-created', { phase: 'start' })
2206
- -> Registra listeners: zs3-theme-change, zs3-locale-change
2207
- -> render() // Renderitza el component
2208
- -> afterMount() // Setup event listeners, etc.
2209
- -> applyFloatEffect()
2210
- -> applyMoveEffect()
2211
- -> emitLifecycleEvent('zs3-created', { phase: 'end' })
2212
-
2213
- attributeChangedCallback (canvi d'atribut)
2214
- -> emitLifecycleEvent('zs3-modified', { phase: 'start' })
2215
- -> Handler registrat amb onAttributeChange() (si existeix)
2216
- -> onAttributeChanged() // Hook per subclasses
2217
- -> render() // Re-renderitza
2218
- -> applyFloatEffect()
2219
- -> applyMoveEffect()
2220
- -> emitLifecycleEvent('zs3-modified', { phase: 'end' })
2221
-
2222
- [Canvi de tema]
2223
- -> onThemeChange(theme) // Hook per subclasses
2224
- -> render()
2225
-
2226
- [Canvi d'idioma]
2227
- -> onLocaleChange(locale) // Hook per subclasses
2228
- -> render()
2229
-
2230
- disconnectedCallback (element eliminat del DOM)
2231
- -> _mounted = false
2232
- -> emitLifecycleEvent('zs3-destroyed', { phase: 'start' })
2233
- -> Elimina listeners de tema/idioma
2234
- -> beforeUnmount() // Cleanup
2235
- -> Cleanup de managed event listeners
2236
- -> emitLifecycleEvent('zs3-destroyed', { phase: 'end' })
2237
- ```
2238
-
2239
- ### Metodes disponibles
2240
-
2241
- #### Queries al DOM intern
2242
-
2243
- | Metode | Signatura | Descripcio |
2244
- |--------|-----------|------------|
2245
- | `qs()` | `<T extends HTMLElement>(selector: string) => T \| null` | querySelector dins del root (shadow o element). |
2246
- | `qsAll()` | `<T extends HTMLElement>(selector: string) => NodeListOf<T>` | querySelectorAll dins del root. |
2247
-
2248
- #### Gestio d'atributs
2249
-
2250
- | Metode | Signatura | Descripcio |
2251
- |--------|-----------|------------|
2252
- | `onAttributeChange()` | `(name: string, handler: AttributeChangeCallback) => void` | Registra un handler per un atribut especific. |
2253
- | `getBooleanAttribute()` | `(name: string, defaultValue?: boolean) => boolean` | Obte atribut com a boolean. |
2254
- | `getNumberAttribute()` | `(name: string, defaultValue?: number) => number` | Obte atribut com a number. |
2255
- | `getArrayAttribute()` | `(name: string, defaultValue?: string[]) => string[]` | Obte atribut com a array (coma-separat). |
2256
- | `setBooleanAttribute()` | `(name: string, value: boolean) => void` | Estableix atribut boolean. |
2257
-
2258
- #### Events
2259
-
2260
- | Metode | Signatura | Descripcio |
2261
- |--------|-----------|------------|
2262
- | `addManagedEventListener()` | `(element, event, handler, options?) => void` | Afegeix listener amb cleanup automatic. |
2263
- | `emit()` | `<T>(eventName: string, detail?: T) => boolean` | Emet un CustomEvent. Executa inline handlers si existeixen. Bloquejat si disabled/loading. |
2264
- | `emitLifecycleEvent()` | `(eventName: string, detail?: Record<string,unknown>) => void` | Emet event de cicle de vida. |
2265
-
2266
- #### Renderitzat
2267
-
2268
- | Metode | Signatura | Descripcio |
2269
- |--------|-----------|------------|
2270
- | `render()` | `() => void` | **Abstracte.** Renderitza el component. |
2271
- | `setHTML()` | `(html: string, target?) => void` | Estableix innerHTML al root o target. |
2272
- | `requestUpdate()` | `() => Promise<void>` | Sol-licita re-render en el proxim animation frame. |
2273
- | `forceUpdate()` | `() => void` | Forca re-render immediat + float + move. |
2274
- | `debounce()` | `<T>(fn: T, delay: number) => (...args) => void` | Crea una funcio debounced. |
2275
-
2276
- #### i18n
2277
-
2278
- | Metode | Signatura | Descripcio |
2279
- |--------|-----------|------------|
2280
- | `t()` | `(key: string, params?) => string` | Tradueix una clau. |
2281
- | `hasTranslation()` | `(key: string) => boolean` | Verifica si existeix traduccio. |
2282
- | `getTranslatedAttribute()` | `(attrName: string, defaultValue?) => string` | Obte el valor d'un atribut traduit (`i18n-{attrName}` o `attrName`). |
2283
-
2284
- #### Float/Move
2285
-
2286
- | Propietat | Tipus | Descripcio |
2287
- |-----------|-------|------------|
2288
- | `_floatTargetSelector` | `string \| null` | Selector de l'element float target. |
2289
- | `_moveContainerSelector` | `string \| null` | Selector del container move. `':host'` per al component sencer. |
2290
- | `_moveDragSelector` | `string \| null` | Selector del drag handle. Si no es defineix, s'usa el container. |
2291
-
2292
2052
  ---
2293
2053
 
2294
- ## Decoradors
2295
-
2296
- ### @Component(tagName)
2054
+ ### $ — DOM Helper
2297
2055
 
2298
- Registra el custom element amb `customElements.define()`.
2056
+ Helper per seleccionar i manipular elements del DOM.
2299
2057
 
2300
- ```typescript
2301
- @Component('zs3-my-component')
2302
- export class $MyComponent extends $BaseComponent { ... }
2303
- ```
2304
-
2305
- ### @ObservedAttributes(attributes, options?)
2306
-
2307
- Declara els atributs observats per `attributeChangedCallback`.
2308
-
2309
- ```typescript
2310
- @ObservedAttributes(['size', 'variant', 'disabled'])
2311
- export class $MyComponent extends $BaseComponent { ... }
2312
- ```
2313
-
2314
- #### Opcions
2315
-
2316
- | Opcio | Tipus | Per defecte | Descripcio |
2317
- |-------|-------|-------------|------------|
2318
- | `inherit` | `boolean` | `false` | Hereta els `observedAttributes` de la classe pare. |
2319
-
2320
- #### Herencia d'atributs
2321
-
2322
- Quan un component extén un altre component ZS3 i necessita observar tant els seus propis atributs com els de la classe pare, utilitza l'opcio `inherit: true`:
2323
-
2324
- ```typescript
2325
- // Component base amb els seus atributs
2326
- @ObservedAttributes(['title', 'description', 'show-cancel'])
2327
- export class $Form extends $BaseComponent { ... }
2328
-
2329
- // Component derivat que hereta els atributs del pare
2330
- @ObservedAttributes(['show-register', 'login-text'], { inherit: true })
2331
- export class $LoginForm extends $Form { ... }
2332
- // Observa: 'title', 'description', 'show-cancel', 'show-register', 'login-text'
2333
- ```
2334
-
2335
- Sense `inherit: true`, el component derivat nomes observaria els seus propis atributs i perdria la reactivitat als atributs del pare.
2336
-
2337
- ### @Float(selector)
2338
-
2339
- Configura el selector de l'element target per l'efecte float.
2340
-
2341
- ```typescript
2342
- @Float('button') // Selector dins del shadow DOM
2343
- @Float('.toolbar') // Classe CSS interna
2344
- ```
2345
-
2346
- ### @Move(containerSelector, dragSelector?)
2347
-
2348
- Configura el moviment drag & drop.
2058
+ ```ts
2059
+ import { $ } from 'zs3-ui-components'
2349
2060
 
2350
- ```typescript
2351
- @Move(':host') // El component sencer es mou i es drag handle
2352
- @Move(':host', '.title-bar') // El component es mou, drag per .title-bar
2353
- @Move('.window', '.title-bar') // .window es mou, drag per .title-bar
2061
+ $('#my-element')?.addClass('highlighted')
2062
+ $('#my-element')?.removeClass('highlighted')
2063
+ $('#my-element')?.toggleClass('highlighted')
2064
+ $('#my-element')?.hide()
2065
+ $('#my-element')?.show()
2066
+ $('#my-element')?.text('Nou contingut')
2067
+ $('#my-element')?.attr('aria-label', 'Descripció')
2354
2068
  ```
2355
2069
 
2356
- ### @Attr(name, options)
2357
-
2358
- Genera automaticament getters/setters per atributs HTML.
2070
+ #### HTML inline (index2.html)
2359
2071
 
2360
- ```typescript
2361
- interface AttrOptions {
2362
- type: 'string' | 'boolean' | 'number'
2363
- default?: string | boolean | number
2364
- }
2072
+ ```html
2073
+ <div id="dollar-box" style="padding:.875rem;border:2px solid #d1d5db;border-radius:.375rem">
2074
+ Element manipulat pel $ DOM helper
2075
+ </div>
2076
+ <div>
2077
+ <zs3-button variant="primary" zs3-click="$('#dollar-box').addClass('highlighted')" size="sm">addClass</zs3-button>
2078
+ <zs3-button zs3-click="$('#dollar-box').removeClass('highlighted')" size="sm">removeClass</zs3-button>
2079
+ <zs3-button variant="info" zs3-click="$('#dollar-box').toggleClass('highlighted')" size="sm">toggleClass</zs3-button>
2080
+ <zs3-button variant="warning" zs3-click="$('#dollar-box').hide()" size="sm">hide()</zs3-button>
2081
+ <zs3-button variant="success" zs3-click="$('#dollar-box').show()" size="sm">show()</zs3-button>
2082
+ <zs3-button zs3-click="$('#dollar-box').text('Text: ' + Date.now())" size="sm">text()</zs3-button>
2083
+ </div>
2084
+ ```
2085
+
2086
+ #### TypeScript programàtic (index3.ts)
2087
+
2088
+ ```ts
2089
+ const domActions: Array<[string, string, () => void]> = [
2090
+ ['addClass', 'primary', () => $(`#dollar-box-ts`)?.addClass('highlighted')],
2091
+ ['removeClass', '', () => $(`#dollar-box-ts`)?.removeClass('highlighted')],
2092
+ ['toggleClass', 'info', () => $(`#dollar-box-ts`)?.toggleClass('highlighted')],
2093
+ ['hide()', 'warning', () => $(`#dollar-box-ts`)?.hide()],
2094
+ ['show()', 'success', () => $(`#dollar-box-ts`)?.show()],
2095
+ ['text()', '', () => $(`#dollar-box-ts`)?.text(`Text: ${Date.now()}`)],
2096
+ ]
2097
+ domActions.forEach(([label, variant, fn]) => {
2098
+ const b = btn(label, variant || undefined); b.size = 'sm'
2099
+ b.addEventListener('zs3-click', fn)
2100
+ dollarRow.append(b)
2101
+ })
2365
2102
  ```
2366
2103
 
2367
- **Exemples:**
2368
-
2369
- ```typescript
2370
- // string sense default -> string | null
2371
- @Attr('icon', { type: 'string' })
2372
- icon!: string | null
2373
-
2374
- // string amb default -> string
2375
- @Attr('position', { type: 'string', default: 'right' })
2376
- position!: string
2104
+ ---
2377
2105
 
2378
- // boolean (default false)
2379
- @Attr('disabled', { type: 'boolean' })
2380
- disabled!: boolean
2106
+ ## Decorators
2381
2107
 
2382
- // boolean amb default true
2383
- @Attr('dismissible', { type: 'boolean', default: true })
2384
- dismissible!: boolean
2108
+ Per crear components personalitzats que extenguin `$BaseComponent`:
2385
2109
 
2386
- // number amb default
2387
- @Attr('duration', { type: 'number', default: 5000 })
2388
- duration!: number
2389
- ```
2110
+ ```ts
2111
+ import { Component, ObservedAttributes, Attr, $BaseComponent, i18n } from 'zs3-ui-components'
2390
2112
 
2391
- ### Exemple complet: Creacio d'un component nou
2113
+ @Component('my-component')
2114
+ @ObservedAttributes(['title', 'count'])
2115
+ class MyComponent extends $BaseComponent {
2392
2116
 
2393
- ```typescript
2394
- import { $BaseComponent } from 'zs3-ui-components'
2395
- import { Component, ObservedAttributes, Float, Move, Attr } from 'zs3-ui-components'
2117
+ @Attr('title', { type: 'string' })
2118
+ title!: string | null
2396
2119
 
2397
- @Component('zs3-my-card')
2398
- @ObservedAttributes(['title', 'variant', 'collapsible', 'float', 'move'])
2399
- @Float('.card')
2400
- @Move(':host', '.card-header')
2401
- export class $MyCard extends $BaseComponent {
2402
- constructor() {
2403
- super({ useShadowDOM: true, shadowMode: 'open' })
2404
- }
2120
+ @Attr('count', { type: 'number', default: 0 })
2121
+ count!: number
2405
2122
 
2406
2123
  protected initialize(): void {
2407
- // Setup logic
2124
+ // Cridat un cop, abans del primer render
2408
2125
  }
2409
2126
 
2410
- protected afterMount(): void {
2411
- const header = this.qs('.card-header')
2412
- if (header && this.collapsible) {
2413
- this.addManagedEventListener(header, 'click', () => {
2414
- this.emit('zs3-card-toggle', { collapsed: this.collapsed })
2415
- })
2416
- }
2417
- }
2418
-
2419
- render(): void {
2420
- const title = this.getTranslatedAttribute('title', 'Card')
2127
+ protected render(): void {
2128
+ // Accés a traduccions via this.t()
2129
+ const label = this.t('my.label.key')
2421
2130
 
2422
2131
  this.setHTML(`
2423
2132
  <style>
2424
- :host { display: block; }
2425
- .card {
2426
- border: 1px solid var(--zs3-border-color);
2427
- border-radius: var(--zs3-border-radius);
2428
- background: var(--zs3-bg-color);
2429
- }
2430
- .card-header {
2431
- padding: var(--zs3-spacing-md);
2432
- font-weight: 600;
2433
- border-bottom: 1px solid var(--zs3-border-color);
2434
- cursor: ${this.collapsible ? 'pointer' : 'default'};
2435
- }
2436
- .card-body { padding: var(--zs3-spacing-md); }
2133
+ :host { display: block; padding: 1rem; }
2437
2134
  </style>
2438
- <div class="card">
2439
- <div class="card-header">${title}</div>
2440
- <div class="card-body"><slot></slot></div>
2135
+ <div>
2136
+ <h2 data-i18n="my.title.key">${this.title ?? label}</h2>
2137
+ <span>Count: ${this.count}</span>
2441
2138
  </div>
2442
2139
  `)
2443
2140
  }
2444
2141
 
2445
- @Attr('title', { type: 'string', default: 'Card' })
2446
- cardTitle!: string
2447
-
2448
- @Attr('variant', { type: 'string' })
2449
- variant!: string | null
2450
-
2451
- @Attr('collapsible', { type: 'boolean' })
2452
- collapsible!: boolean
2453
-
2454
- @Attr('collapsed', { type: 'boolean' })
2455
- collapsed!: boolean
2456
- }
2457
- ```
2458
-
2459
- ```html
2460
- <zs3-my-card title="La meva targeta" collapsible>
2461
- <p>Contingut de la targeta</p>
2462
- </zs3-my-card>
2463
- ```
2464
-
2465
- ---
2466
-
2467
- ## Efecte Float
2468
-
2469
- L'efecte float posiciona un element parcialment amagat fora del seu contenidor, i el mostra completament quan el ratoli passa per sobre.
2470
-
2471
- ### API
2472
-
2473
- ```typescript
2474
- import { float } from 'zs3-ui-components'
2475
-
2476
- // Us basic (nomes necessita l'atribut `float` al component)
2477
- float(component)
2478
-
2479
- // Amb configuracio personalitzada
2480
- float(component, {
2481
- visibleRatio: 0.2, // 20% visible (defecte: 0.15)
2482
- offset: 10, // Offset en pixels (defecte: 0)
2483
- transitionDuration: 0.5, // Duracio en segons (defecte: 0.4)
2484
- horizontalPosition: '10px', // Per float top/bottom
2485
- verticalPosition: '50%', // Per float left/right
2486
- })
2487
- ```
2488
-
2489
- ### Posicions
2142
+ protected afterMount(): void {
2143
+ // Actualitza traduccions al shadow root:
2144
+ if (this._shadowRoot) i18n.updateRoot(this._shadowRoot)
2145
+ }
2490
2146
 
2491
- | Posicio | Descripcio |
2492
- |---------|------------|
2493
- | `top` | Amagat a la part superior, es mostra cap avall. |
2494
- | `bottom` | Amagat a la part inferior, es mostra cap amunt. |
2495
- | `left` | Amagat a l'esquerra, es mostra cap a la dreta. |
2496
- | `right` | Amagat a la dreta, es mostra cap a l'esquerra. |
2497
-
2498
- ### FloatConfig
2499
-
2500
- ```typescript
2501
- interface FloatConfig {
2502
- visibleRatio?: number // 0-1, percentatge visible (defecte: 0.15)
2503
- offset?: number // Pixels d'offset (defecte: 0)
2504
- transitionDuration?: number // Segons (defecte: 0.4)
2505
- horizontalPosition?: string // Posicio X per top/bottom (defecte: '50%')
2506
- verticalPosition?: string // Posicio Y per left/right (defecte: '50%')
2147
+ // Es crida automàticament quan canvia l'idioma
2148
+ // render() s'invoca automàticament després
2149
+ protected onLocaleChange(): void {
2150
+ // lògica addicional si cal
2151
+ }
2507
2152
  }
2508
2153
  ```
2509
2154
 
2510
- ### Events Float
2511
-
2512
- | Event | Detail (`FloatEventDetail`) | Descripcio |
2513
- |-------|----------------------------|------------|
2514
- | `zs3-float-show-start` | `{ position, element }` | L'element comenca a apareixer. |
2515
- | `zs3-float-show-end` | `{ position, element }` | L'element ha acabat d'apareixer. |
2516
- | `zs3-float-hide-start` | `{ position, element }` | L'element comenca a amagar-se. |
2517
- | `zs3-float-hide-end` | `{ position, element }` | L'element s'ha amagat completament. |
2518
-
2519
2155
  ---
2520
2156
 
2521
- ## Efecte Move
2522
-
2523
- L'efecte move permet arrossegar elements dins del seu contenidor pare amb el ratoli.
2157
+ ## CSS Variables
2524
2158
 
2525
- ### API
2526
-
2527
- ```typescript
2528
- import { move, disableMove, isMoveEnabled, getMoveDirection } from 'zs3-ui-components'
2529
-
2530
- // Moviment lliure
2531
- move({ container: element, direction: 'xy' })
2532
-
2533
- // Nomes horitzontal
2534
- move({ container: element, direction: 'x' })
2535
-
2536
- // Amb drag handle separat
2537
- move({ container: windowEl, dragHandle: titleBarEl, direction: 'xy' })
2538
-
2539
- // Desactivar
2540
- disableMove(dragHandle)
2541
-
2542
- // Comprovar estat
2543
- isMoveEnabled(dragHandle) // boolean
2544
- getMoveDirection(dragHandle) // 'x' | 'y' | 'xy' | undefined
2545
- ```
2546
-
2547
- ### MoveConfig
2548
-
2549
- ```typescript
2550
- interface MoveConfig {
2551
- container: HTMLElement // Element que es moura
2552
- dragHandle?: HTMLElement // Element per arrossegar (defecte: container)
2553
- direction?: MoveDirection // 'x' | 'y' | 'xy' (defecte: 'xy')
2554
- }
2555
- ```
2159
+ Tots els components utilitzen variables CSS que pots sobreescriure:
2556
2160
 
2557
- ### MoveDirection
2558
-
2559
- | Valor | Descripcio |
2560
- |-------|------------|
2561
- | `'x'` | Nomes moviment horitzontal. |
2562
- | `'y'` | Nomes moviment vertical. |
2563
- | `'xy'` | Moviment lliure en ambdues direccions. |
2564
-
2565
- ### MoveBoundary
2566
-
2567
- | Valor | Descripcio |
2568
- |-------|------------|
2569
- | `'top'` | Limit superior del contenidor. |
2570
- | `'bottom'` | Limit inferior del contenidor. |
2571
- | `'left'` | Limit esquerre del contenidor. |
2572
- | `'right'` | Limit dret del contenidor. |
2573
-
2574
- ### Events Move
2575
-
2576
- | Event | Detail (`MoveEventDetail`) | Descripcio |
2577
- |-------|----------------------------|------------|
2578
- | `zs3-move-start` | `{ direction, element, startX, startY }` | Inici d'arrossegament. |
2579
- | `zs3-move` | `{ direction, element, x, y, deltaX, deltaY }` | Durant l'arrossegament. |
2580
- | `zs3-move-end` | `{ direction, element, endX, endY }` | Fi d'arrossegament. |
2581
- | `zs3-move-cancel` | `{ direction, element }` | Arrossegament cancel-lat amb Escape. |
2582
- | `zs3-move-boundary` | `{ direction, element, x, y, boundary }` | S'ha tocat un limit del contenidor. |
2583
-
2584
- ### MoveEventDetail
2585
-
2586
- ```typescript
2587
- interface MoveEventDetail {
2588
- direction: MoveDirection
2589
- element: HTMLElement
2590
- x?: number
2591
- y?: number
2592
- startX?: number
2593
- startY?: number
2594
- endX?: number
2595
- endY?: number
2596
- deltaX?: number
2597
- deltaY?: number
2598
- boundary?: MoveBoundary
2161
+ ```css
2162
+ :root {
2163
+ /* Colors principals */
2164
+ --zs3-primary-color: #3b82f6;
2165
+ --zs3-secondary-color: #6b7280;
2166
+ --zs3-success-color: #10b981;
2167
+ --zs3-danger-color: #ef4444;
2168
+ --zs3-warning-color: #f59e0b;
2169
+ --zs3-info-color: #06b6d4;
2170
+
2171
+ /* Fons i text */
2172
+ --zs3-bg-color: #f9fafb;
2173
+ --zs3-fg-color: #111827;
2174
+ --zs3-surface-color: #ffffff;
2175
+ --zs3-surface-alt-color: #f3f4f6;
2176
+ --zs3-border-color: #e5e7eb;
2177
+
2178
+ /* Tipografia */
2179
+ --zs3-font-family: system-ui, sans-serif;
2180
+ --zs3-font-size-base: 1rem;
2181
+
2182
+ /* Espaciat */
2183
+ --zs3-spacing-sm: 0.5rem;
2184
+ --zs3-spacing-md: 1rem;
2185
+ --zs3-spacing-lg: 1.5rem;
2186
+
2187
+ /* Forma i efectes */
2188
+ --zs3-border-radius: 0.375rem;
2189
+ --zs3-shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
2190
+ --zs3-shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1);
2191
+ --zs3-transition-base: 250ms ease-in-out;
2192
+ --zs3-transition-fast: 150ms ease-in-out;
2193
+
2194
+ /* Z-index */
2195
+ --zs3-z-index-modal: 1000;
2196
+ --zs3-z-index-tooltip: 2000;
2599
2197
  }
2600
2198
  ```
2601
2199
 
2602
- ---
2603
-
2604
- ## Exemples complets
2605
-
2606
- ### Exemple HTML amb inline handlers
2607
-
2608
- ```html
2609
- <!doctype html>
2610
- <html lang="ca">
2611
- <head>
2612
- <meta charset="UTF-8" />
2613
- <title>ZS3 Demo</title>
2614
- </head>
2615
- <body>
2616
- <!-- Notificacio reusable -->
2617
- <zs3-notification id="notif" dismissible show-icon show-progress duration="4000">
2618
- </zs3-notification>
2619
-
2620
- <!-- Toolbar flotant superior -->
2621
- <zs3-toolbar float="top" direction="row" align="center" gap="0.5rem">
2622
- <zs3-button icon="error" variant="danger" size="sm"
2623
- zs3-click="
2624
- const n = $('#notif').$;
2625
- n.setAttribute('type', 'error');
2626
- n.setAttribute('title', 'Error');
2627
- n.setAttribute('message', 'Alguna cosa ha anat malament');
2628
- n.forceUpdate(); n.show();
2629
- log.info('Notificacio error mostrada')
2630
- "
2631
- >Error</zs3-button>
2632
-
2633
- <zs3-button icon="accept" variant="success" size="sm"
2634
- zs3-click="
2635
- const n = $('#notif').$;
2636
- n.setAttribute('type', 'success');
2637
- n.setAttribute('title', 'Exit!');
2638
- n.setAttribute('message', 'Operacio completada');
2639
- n.forceUpdate(); n.show();
2640
- "
2641
- >Exit</zs3-button>
2642
- </zs3-toolbar>
2643
-
2644
- <!-- Dialog predefinit -->
2645
- <zs3-modal-dialog
2646
- id="dlg-confirm"
2647
- title="Confirmar"
2648
- message="Estas segur?"
2649
- show-accept show-cancel
2650
- accept-variant="primary"
2651
- size="small"
2652
- ></zs3-modal-dialog>
2653
-
2654
- <!-- Boto que obre el dialog -->
2655
- <zs3-button variant="primary"
2656
- zs3-click="
2657
- $('#dlg-confirm').$.showDialog().then(function(a) {
2658
- log.info('Resultat: ' + a)
2659
- })
2660
- "
2661
- >Obrir dialog</zs3-button>
2662
-
2663
- <script type="module" src="./app.ts"></script>
2664
- </body>
2665
- </html>
2666
- ```
2667
-
2668
- ### Exemple TypeScript
2669
-
2670
- ```typescript
2671
- import { $, log, i18n, themeManager } from 'zs3-ui-components'
2672
- import { $Notification } from 'zs3-ui-components'
2673
- import { $ModalDialog } from 'zs3-ui-components'
2674
- import type { ThemeType } from 'zs3-ui-components'
2675
-
2676
- // Carregar traduccions
2677
- i18n.loadMultiple({
2678
- ca: { 'hello': 'Hola!', 'btn.save': 'Desar' },
2679
- en: { 'hello': 'Hello!', 'btn.save': 'Save' },
2680
- })
2681
- i18n.init({ locale: 'ca', fallbackLocale: 'en' })
2682
-
2683
- // Setup botons de notificacio
2684
- $('#btn-success')?.addEvent('zs3-click', () => {
2685
- $Notification.success('Operacio completada!', { duration: 3000 })
2686
- })
2687
-
2688
- $('#btn-error')?.addEvent('zs3-click', () => {
2689
- $Notification.error('Error de connexio', { position: 'bottom-right' })
2690
- })
2691
-
2692
- // Setup dialogs
2693
- $('#btn-confirm')?.addEvent('zs3-click', async () => {
2694
- const result = await $ModalDialog.confirm('Estas segur?', 'Confirmar')
2695
- log.info(`Resultat: ${result ? 'acceptat' : 'cancel-lat'}`)
2696
- })
2697
-
2698
- $('#btn-delete')?.addEvent('zs3-click', async () => {
2699
- const deleted = await $ModalDialog.confirmDelete('fitxer.txt')
2700
- if (deleted) log.info('Fitxer eliminat')
2701
- })
2702
-
2703
- // Canviar tema
2704
- $('#btn-dark')?.addEvent('zs3-click', () => {
2705
- themeManager.setTheme('dark')
2706
- })
2707
-
2708
- // Canviar idioma
2709
- $('#btn-lang-en')?.addEvent('zs3-click', () => {
2710
- i18n.setLocale('en')
2711
- })
2712
-
2713
- // Events globals
2714
- window.addEventListener('zs3-theme-change', (e: Event) => {
2715
- const detail = (e as CustomEvent).detail
2716
- log.info(`Tema canviat a: ${detail.theme}`)
2717
- })
2718
-
2719
- window.addEventListener('zs3-locale-change', (e: Event) => {
2720
- const detail = (e as CustomEvent).detail
2721
- log.info(`Idioma canviat a: ${detail.locale}`)
2722
- })
2723
- ```
2724
-
2725
- ---
2726
-
2727
- ## Estructura del projecte
2728
-
2729
- ```
2730
- zs3-ui-components/
2731
- ├── src/ # Codi font
2732
- │ ├── components/ # Web Components
2733
- │ │ ├── zs3-BaseComponent.ts
2734
- │ │ ├── zs3-Button.ts
2735
- │ │ ├── zs3-Icon.ts
2736
- │ │ ├── zs3-Toolbar.ts
2737
- │ │ ├── zs3-Modal.ts
2738
- │ │ ├── zs3-ModalDialog.ts
2739
- │ │ ├── zs3-Notification.ts
2740
- │ │ ├── zs3-Window.ts
2741
- │ │ ├── zs3-Form.ts # Formulari dinamic
2742
- │ │ ├── zs3-LoginForm.ts # Login (hereta Form)
2743
- │ │ ├── zs3-RegisterForm.ts # Registre (hereta Form)
2744
- │ │ ├── zs3-RecoverPasswordForm.ts # Recuperacio (hereta Form)
2745
- │ │ ├── zs3-SelectTheme.ts
2746
- │ │ ├── zs3-SelectLocale.ts
2747
- │ │ ├── zs3-common.ts # Float & Move
2748
- │ │ └── types.ts # Variant type
2749
- │ ├── decorators/ # TypeScript decoradors
2750
- │ │ ├── component.ts
2751
- │ │ ├── observedAttributes.ts # Suporta inherit
2752
- │ │ ├── float.ts
2753
- │ │ ├── move.ts
2754
- │ │ ├── attr.ts
2755
- │ │ └── index.ts
2756
- │ ├── css/
2757
- │ │ └── zs3.css # Temes i estils base
2758
- │ ├── i18n/
2759
- │ │ └── defaults.ts # Traduccions per defecte
2760
- │ ├── zs3.ts # ZS3, $, params, log, debug, storage
2761
- │ ├── theme.ts # ThemeManager
2762
- │ ├── i18n.ts # Sistema i18n
2763
- │ ├── eventBus.ts # Bus d'events (pub/sub)
2764
- │ ├── diContainer.ts # Contenidor d'injeccio de dependencies
2765
- │ ├── types.ts # EventDescription
2766
- │ └── index.ts # Entry point
2767
- ├── dist/ # Build output
2768
- │ ├── index.js # ES Module compilat
2769
- │ ├── index.css # CSS compilat
2770
- │ └── index.d.ts # Declaracions TypeScript
2771
- ├── dev/ # Demos de desenvolupament
2772
- ├── package.json
2773
- ├── tsconfig.json
2774
- ├── vite.config.ts
2775
- └── README.md
2776
- ```
2200
+ Els temes sobreescriuen automàticament les variables corresponents quan es crida `themeManager.setTheme('dark')` o l'usuari utilitza `<zs3-select-theme>`.
2777
2201
 
2778
2202
  ---
2779
2203
 
2780
- ## Suport i llicencia
2204
+ ## License
2781
2205
 
2782
- - **Llicencia:** ISC
2783
- - **Autor:** Manuel Candeal
2784
- - **Repositori:** [github.com/manuelcandeal/zs3-ui-components](https://github.com/manuelcandeal/zs3-ui-components)
2785
- - **Issues:** [github.com/manuelcandeal/zs3-ui-components/issues](https://github.com/manuelcandeal/zs3-ui-components/issues)
2206
+ Apache License 2.0 — vegeu el fitxer [LICENSE](./LICENSE).