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 +1768 -2347
- package/dist/components/zs3-Button.d.ts +0 -10
- package/dist/components/zs3-Form.d.ts +5 -4
- package/dist/components/zs3-Icon.d.ts +1 -5
- package/dist/components/zs3-LoginForm.d.ts +3 -18
- package/dist/components/zs3-ModalDialog.d.ts +0 -24
- package/dist/components/zs3-Notification.d.ts +0 -12
- package/dist/components/zs3-RecoverPasswordForm.d.ts +3 -14
- package/dist/components/zs3-RegisterForm.d.ts +3 -14
- package/dist/components/zs3-Window.d.ts +0 -4
- package/dist/decorators/component.d.ts +1 -0
- package/dist/decorators/index.d.ts +2 -2
- package/dist/globals.d.ts +2 -0
- package/dist/i18n.d.ts +19 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +814 -960
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
# ZS3 UI Components
|
|
2
2
|
|
|
3
|
-
**
|
|
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
|
-
##
|
|
9
|
+
## Table of Contents
|
|
17
10
|
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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-
|
|
28
|
+
- [zs3-window](#zs3-window)
|
|
46
29
|
- [zs3-select-locale](#zs3-select-locale)
|
|
47
|
-
- [
|
|
48
|
-
- [
|
|
49
|
-
- [$
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
52
|
-
- [
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
55
|
-
- [
|
|
56
|
-
- [
|
|
57
|
-
- [
|
|
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
|
-
##
|
|
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
|
-
|
|
60
|
+
## Installation
|
|
64
61
|
|
|
65
62
|
```bash
|
|
66
63
|
npm install zs3-ui-components
|
|
67
64
|
```
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Importa tot el paquet (registra automaticament tots els custom elements):
|
|
66
|
+
Importa els components i el CSS:
|
|
72
67
|
|
|
73
|
-
```
|
|
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
|
-
|
|
73
|
+
O importa selectivament:
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
```
|
|
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
|
-
|
|
86
|
-
import { themeManager, getCSSVariable, setCSSVariable, initThemeSystem } from 'zs3-ui-components'
|
|
79
|
+
---
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
import { i18n } from 'zs3-ui-components'
|
|
81
|
+
## Dev Setup
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
+
---
|
|
166
101
|
|
|
167
|
-
|
|
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
|
-
|
|
175
|
-
const zs3 = ZS3.fromElement(htmlElement)
|
|
176
|
-
```
|
|
104
|
+
ZS3 UI suporta dos estils d'ús que es poden combinar lliurement.
|
|
177
105
|
|
|
178
|
-
|
|
106
|
+
### Enfocament 1 — HTML Inline (`index2.html` / `index2.ts`)
|
|
179
107
|
|
|
180
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
| `$$` | `HTMLElement[]` | Tots els elements seleccionats |
|
|
160
|
+
// Inicialitza i18n
|
|
161
|
+
i18n.loadMultiple(translations)
|
|
162
|
+
i18n.init({ locale: 'ca', fallbackLocale: 'en' })
|
|
195
163
|
|
|
196
|
-
|
|
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
|
-
|
|
172
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
173
|
+
log.info('Demo 2 iniciada')
|
|
174
|
+
setupIconsGrid()
|
|
175
|
+
})
|
|
199
176
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
192
|
+
### Enfocament 2 — TypeScript Programàtic (`index3.html` / `index3.ts`)
|
|
207
193
|
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
221
|
+
i18n.loadMultiple(translations)
|
|
222
|
+
i18n.init({ locale: 'ca', fallbackLocale: 'en' })
|
|
227
223
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
268
|
+
---
|
|
250
269
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
317
|
+
### Inicialització
|
|
262
318
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
323
|
+
// Carrega totes les traduccions d'una vegada
|
|
324
|
+
i18n.loadMultiple(translations)
|
|
271
325
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
326
|
+
// Inicialitza amb locale per defecte i fallback
|
|
327
|
+
i18n.init({
|
|
328
|
+
locale: 'ca',
|
|
329
|
+
fallbackLocale: 'en',
|
|
330
|
+
})
|
|
331
|
+
```
|
|
275
332
|
|
|
276
|
-
|
|
333
|
+
`init()` també accepta `translations` directament:
|
|
277
334
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
346
|
+
### API de l'objecte `i18n`
|
|
285
347
|
|
|
286
|
-
|
|
|
287
|
-
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
365
|
+
// Obtenir l'idioma actiu
|
|
366
|
+
console.log(i18n.getLocale()) // → "en"
|
|
367
|
+
```
|
|
313
368
|
|
|
314
|
-
###
|
|
369
|
+
### Escolta el canvi d'idioma
|
|
315
370
|
|
|
316
|
-
|
|
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
|
-
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
386
|
+
Afegeix `data-i18n="clau"` a qualsevol element i el seu `textContent` s'actualitzarà automàticament en canviar l'idioma:
|
|
331
387
|
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
394
|
+
Per traduir **atributs** (aria-label, placeholder, title…):
|
|
341
395
|
|
|
342
|
-
|
|
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
|
-
|
|
345
|
-
|
|
400
|
+
<!-- Tradueix el placeholder -->
|
|
401
|
+
<input data-i18n-attr='{"placeholder":"form.field.name.placeholder"}' />
|
|
346
402
|
```
|
|
347
403
|
|
|
348
|
-
|
|
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
|
-
|
|
406
|
+
### i18n en components TypeScript
|
|
359
407
|
|
|
360
|
-
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
###
|
|
420
|
+
### i18n en atributs de components
|
|
372
421
|
|
|
373
|
-
|
|
422
|
+
Tots els components accepten atributs `i18n-*` per les seves propietats de text:
|
|
374
423
|
|
|
375
|
-
```
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
457
|
+
const win = new $Window()
|
|
458
|
+
win.setAttribute('i18n-title', 'form.title')
|
|
459
|
+
```
|
|
389
460
|
|
|
390
|
-
|
|
391
|
-
const containerA = document.getElementById('sectionA')!
|
|
392
|
-
const containerB = document.getElementById('sectionB')!
|
|
461
|
+
### Traduccions internes del framework
|
|
393
462
|
|
|
394
|
-
|
|
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
|
-
|
|
465
|
+
```ts
|
|
466
|
+
import { defaultTranslations } from 'zs3-ui-components'
|
|
399
467
|
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
476
|
+
Pots sobreescriure qualsevol clau interna carregant les teves pròpies traduccions amb `i18n.loadMultiple()`.
|
|
405
477
|
|
|
406
|
-
|
|
478
|
+
### Detecció automàtica del navegador
|
|
407
479
|
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
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
|
-
|
|
489
|
+
El component `<zs3-select-locale>` mostra un selector d'idioma que crida `i18n.setLocale()` automàticament:
|
|
422
490
|
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
491
|
+
```html
|
|
492
|
+
<!-- HTML -->
|
|
493
|
+
<zs3-select-locale zs3-position="position:relative;"></zs3-select-locale>
|
|
494
|
+
```
|
|
427
495
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
##
|
|
505
|
+
## Theme System
|
|
506
|
+
|
|
507
|
+
### Temes disponibles
|
|
437
508
|
|
|
438
|
-
|
|
509
|
+
| Tema | Valor |
|
|
510
|
+
|------|-------|
|
|
511
|
+
| Clar (default) | `light` |
|
|
512
|
+
| Fosc | `dark` |
|
|
513
|
+
| Alt contrast | `high-contrast` |
|
|
439
514
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
549
|
+
---
|
|
460
550
|
|
|
461
|
-
|
|
551
|
+
## Components
|
|
462
552
|
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
|
|
557
|
+
**Atributs:**
|
|
475
558
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
483
|
-
const store = diContainer.resolve<$Store<AppState>>('store')
|
|
484
|
-
```
|
|
569
|
+
**Esdeveniments:**
|
|
485
570
|
|
|
486
|
-
|
|
571
|
+
| Esdeveniment | Descripció |
|
|
572
|
+
|-------------|------------|
|
|
573
|
+
| `zs3-click` | Es dispara en fer clic (respecta `disabled` i `loading`) |
|
|
487
574
|
|
|
488
|
-
|
|
575
|
+
#### HTML inline (index2.html)
|
|
489
576
|
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
//
|
|
495
|
-
|
|
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
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
510
|
-
user: null,
|
|
511
|
-
}))
|
|
661
|
+
**API de `$Button`:**
|
|
512
662
|
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
518
|
-
loading: payload as boolean,
|
|
519
|
-
}))
|
|
520
|
-
}
|
|
521
|
-
```
|
|
675
|
+
---
|
|
522
676
|
|
|
523
|
-
###
|
|
677
|
+
### zs3-icon
|
|
524
678
|
|
|
525
|
-
|
|
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
|
-
|
|
681
|
+
**Atributs:**
|
|
534
682
|
|
|
535
|
-
|
|
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
|
-
|
|
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
|
-
|
|
697
|
+
#### HTML inline (index2.html)
|
|
543
698
|
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
758
|
+
#### TypeScript programàtic (index3.ts)
|
|
561
759
|
|
|
562
|
-
|
|
760
|
+
```ts
|
|
761
|
+
import { $Icon } from 'zs3-ui-components'
|
|
563
762
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
|
|
570
|
-
|
|
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
|
-
|
|
776
|
+
// Totes les icones disponibles
|
|
777
|
+
const names = $Icon.getAvailableIcons() // string[]
|
|
577
778
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
793
|
+
**Atributs:**
|
|
603
794
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
804
|
+
#### HTML inline (index2.html)
|
|
617
805
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
|
|
837
|
+
#### TypeScript programàtic (index3.ts)
|
|
642
838
|
|
|
643
|
-
|
|
839
|
+
```ts
|
|
840
|
+
import { $Toolbar, $Button } from 'zs3-ui-components'
|
|
644
841
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
//
|
|
668
|
-
|
|
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
|
-
|
|
884
|
+
---
|
|
885
|
+
|
|
886
|
+
### zs3-notification
|
|
672
887
|
|
|
673
|
-
|
|
888
|
+
Notificació emergent (toast) amb posicions, durades, barra de progrés i estat persistent.
|
|
674
889
|
|
|
675
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
906
|
+
```ts
|
|
907
|
+
import { $Notification } from 'zs3-ui-components'
|
|
696
908
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
909
|
+
$Notification.success('Operació completada!', {
|
|
910
|
+
position: 'top-right',
|
|
911
|
+
duration: 3000,
|
|
912
|
+
showProgress: true,
|
|
701
913
|
})
|
|
702
914
|
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
body: 'Contingut',
|
|
710
|
-
userId: 1,
|
|
922
|
+
$Notification.warning('Atenció: acció irreversible!', {
|
|
923
|
+
position: 'bottom-right',
|
|
924
|
+
duration: 4000,
|
|
711
925
|
})
|
|
712
926
|
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
|
|
717
|
-
await api.delete(`/posts/${nou.id}`)
|
|
935
|
+
#### HTML inline (index2.html)
|
|
718
936
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
|
|
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
|
-
|
|
988
|
+
const bShow = new $Button()
|
|
989
|
+
bShow.textContent = 'show()'
|
|
990
|
+
bShow.variant = 'primary'
|
|
991
|
+
bShow.addEventListener('zs3-click', () => notif.show())
|
|
736
992
|
|
|
737
|
-
|
|
738
|
-
|
|
993
|
+
const bHide = new $Button()
|
|
994
|
+
bHide.textContent = 'hide()'
|
|
995
|
+
bHide.addEventListener('zs3-click', () => notif.hide())
|
|
739
996
|
```
|
|
740
997
|
|
|
741
|
-
|
|
998
|
+
---
|
|
742
999
|
|
|
743
|
-
|
|
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
|
-
|
|
1002
|
+
Modal genèric contenidor de contingut amb mides, backdrop, tancament per ESC/backdrop i mode scrollable.
|
|
775
1003
|
|
|
776
|
-
|
|
777
|
-
import { getCSSVariable, setCSSVariable, initThemeSystem } from 'zs3-ui-components'
|
|
1004
|
+
**Atributs:**
|
|
778
1005
|
|
|
779
|
-
|
|
780
|
-
|
|
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
|
-
|
|
783
|
-
setCSSVariable('zs3-primary-color', '#ff0000')
|
|
1015
|
+
**Mètodes:** `show()`, `hide()`
|
|
784
1016
|
|
|
785
|
-
|
|
786
|
-
initThemeSystem({ defaultTheme: 'dark' })
|
|
787
|
-
```
|
|
1017
|
+
#### HTML inline (index2.html)
|
|
788
1018
|
|
|
789
|
-
|
|
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
|
-
|
|
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
|
-
|
|
794
|
-
|
|
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
|
-
|
|
797
|
-
|
|
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
|
-
|
|
1059
|
+
#### TypeScript programàtic (index3.ts)
|
|
802
1060
|
|
|
803
|
-
|
|
1061
|
+
```ts
|
|
1062
|
+
import { $Modal, $Button } from 'zs3-ui-components'
|
|
804
1063
|
|
|
805
|
-
|
|
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
|
-
|
|
808
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
879
|
-
<summary><strong>Light Blue</strong></summary>
|
|
1088
|
+
---
|
|
880
1089
|
|
|
881
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
911
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
-
|
|
931
|
-
|
|
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
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
950
|
-
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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
|
-
|
|
969
|
-
<summary><strong>System</strong></summary>
|
|
1160
|
+
#### HTML inline (index2.html)
|
|
970
1161
|
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
981
|
-
|
|
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
|
-
|
|
1235
|
+
#### TypeScript programàtic (index3.ts)
|
|
985
1236
|
|
|
986
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
-
|
|
1010
|
-
|
|
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
|
-
|
|
1267
|
+
---
|
|
1014
1268
|
|
|
1015
|
-
###
|
|
1269
|
+
### zs3-form
|
|
1016
1270
|
|
|
1017
|
-
|
|
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
|
-
|
|
1273
|
+
**Atributs:**
|
|
1025
1274
|
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1037
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1385
|
+
Formulari de login preconstruït amb camps d'email i contrasenya, opció de botó de registre i traduccions integrades.
|
|
1058
1386
|
|
|
1059
|
-
|
|
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
|
-
|
|
1389
|
+
**Esdeveniments:** `zs3-form-submit` (amb `detail.values.email` i `detail.values.password`), `zs3-login-register`
|
|
1068
1390
|
|
|
1069
|
-
|
|
1070
|
-
import { eventBus } from 'zs3-ui-components'
|
|
1391
|
+
#### HTML inline (index2.html) — en un modal
|
|
1071
1392
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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
|
-
|
|
1078
|
-
|
|
1430
|
+
loginForm.addEventListener('zs3-login-register', () => {
|
|
1431
|
+
loginDialog.hide()
|
|
1432
|
+
registerDialog.show() // mostra el diàleg de registre
|
|
1433
|
+
})
|
|
1079
1434
|
|
|
1080
|
-
|
|
1081
|
-
unsub()
|
|
1435
|
+
loginDialog.append(loginForm)
|
|
1082
1436
|
|
|
1083
|
-
//
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
-
|
|
1089
|
-
eventBus.off('user:login')
|
|
1448
|
+
---
|
|
1090
1449
|
|
|
1091
|
-
|
|
1092
|
-
eventBus.clear()
|
|
1093
|
-
```
|
|
1450
|
+
### zs3-register-form
|
|
1094
1451
|
|
|
1095
|
-
|
|
1452
|
+
Formulari de registre preconstruït amb camps nom, email, contrasenya i confirmació.
|
|
1096
1453
|
|
|
1097
|
-
|
|
1098
|
-
import { EventBus } from 'zs3-ui-components'
|
|
1454
|
+
**Atributs:** `show-cancel`, `i18n-register-text`
|
|
1099
1455
|
|
|
1100
|
-
|
|
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
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1266
|
-
router.start()
|
|
1267
|
-
```
|
|
1500
|
+
**Atributs:** `show-cancel`, `i18n-submit-text`, `i18n-description`
|
|
1268
1501
|
|
|
1269
|
-
|
|
1502
|
+
**Esdeveniments:** `zs3-form-submit` (amb `detail.values.email`), `zs3-form-cancel`
|
|
1270
1503
|
|
|
1271
|
-
|
|
1504
|
+
#### HTML inline
|
|
1272
1505
|
|
|
1273
1506
|
```html
|
|
1274
|
-
<
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
```
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1548
|
+
**Atributs:**
|
|
1333
1549
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
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
|
-
####
|
|
1559
|
+
#### HTML inline (index2.html)
|
|
1349
1560
|
|
|
1350
1561
|
```html
|
|
1351
|
-
<!--
|
|
1352
|
-
<
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
<
|
|
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
|
-
|
|
1364
|
-
|
|
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
|
-
|
|
1367
|
-
|
|
1630
|
+
container.append(win1, win2)
|
|
1631
|
+
document.getElementById('app')!.append(container)
|
|
1368
1632
|
```
|
|
1369
1633
|
|
|
1370
1634
|
---
|
|
1371
1635
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
Component d'icona SVG amb suport per transformacions, colors i interactivitat.
|
|
1375
|
-
|
|
1376
|
-
#### Atributs
|
|
1636
|
+
## Utilities
|
|
1377
1637
|
|
|
1378
|
-
|
|
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
|
-
|
|
1640
|
+
Gestió d'estat reactiva amb reducers i subscripcions tipades. Inspirat en Redux.
|
|
1390
1641
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
| `zs3-icon-click` | `{ icon, name, originalEvent }` | Icona clicada (si `clickable`). |
|
|
1642
|
+
```ts
|
|
1643
|
+
import { $Store } from 'zs3-ui-components'
|
|
1394
1644
|
|
|
1395
|
-
|
|
1645
|
+
interface CounterState { count: number }
|
|
1396
1646
|
|
|
1397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1655
|
+
// Subscriu-te als canvis
|
|
1656
|
+
counterStore.subscribe('*', (state) => {
|
|
1657
|
+
document.getElementById('counter')!.textContent = String(state.count)
|
|
1658
|
+
})
|
|
1406
1659
|
|
|
1407
|
-
|
|
1660
|
+
// Subscripció per acció específica
|
|
1661
|
+
counterStore.subscribe('increment', (state) => {
|
|
1662
|
+
console.log('Increment! Nou valor:', state.count)
|
|
1663
|
+
})
|
|
1408
1664
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
-
|
|
1418
|
-
|
|
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
|
-
|
|
1470
|
-
<
|
|
1471
|
-
<zs3-button
|
|
1472
|
-
<zs3-button
|
|
1473
|
-
<zs3-button
|
|
1474
|
-
</zs3-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
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
|
-
```
|
|
1555
|
-
//
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
-
//
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1612
|
-
|
|
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
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
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
|
-
|
|
1685
|
-
<zs3-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1702
|
-
const
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
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
|
-
###
|
|
1729
|
-
|
|
1730
|
-
Component de finestra arrossegable amb barra de titol i controls.
|
|
1731
|
-
|
|
1732
|
-
#### Atributs
|
|
1805
|
+
### $Storage — LocalStorage
|
|
1733
1806
|
|
|
1734
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1768
|
-
|
|
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
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
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
|
-
####
|
|
1859
|
+
#### TypeScript programàtic (index3.ts)
|
|
1863
1860
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
1888
|
+
Client HTTP amb base URL, timeout i tipatge genèric de respostes.
|
|
1938
1889
|
|
|
1939
|
-
|
|
1890
|
+
```ts
|
|
1891
|
+
import { HttpClient } from 'zs3-ui-components'
|
|
1940
1892
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
#### Camps predefinits
|
|
1893
|
+
const http = new HttpClient({
|
|
1894
|
+
baseUrl: 'https://api.example.com',
|
|
1895
|
+
timeout: 8000,
|
|
1896
|
+
})
|
|
1947
1897
|
|
|
1948
|
-
|
|
1949
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
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
|
-
<
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
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
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
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
|
-
|
|
2051
|
-
|
|
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
|
-
###
|
|
1980
|
+
### $Log — Logging
|
|
2057
1981
|
|
|
2058
|
-
|
|
1982
|
+
Logger avançat amb nivells i formatació a la consola del navegador.
|
|
2059
1983
|
|
|
2060
|
-
|
|
1984
|
+
```ts
|
|
1985
|
+
import { log } from 'zs3-ui-components'
|
|
2061
1986
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
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
|
-
|
|
1994
|
+
```ts
|
|
1995
|
+
// index2.ts — exposa log globalment per a handlers inline
|
|
1996
|
+
window.log = log
|
|
2067
1997
|
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
-
|
|
2005
|
+
### DIContainer — Dependency Injection
|
|
2075
2006
|
|
|
2076
|
-
|
|
2007
|
+
Contenidor d'injecció de dependències amb singletons i fàbriques.
|
|
2077
2008
|
|
|
2078
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
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
|
-
|
|
2029
|
+
#### HTML inline (index2.html) + index2.ts
|
|
2127
2030
|
|
|
2128
2031
|
```html
|
|
2129
|
-
<
|
|
2130
|
-
|
|
2131
|
-
zs3-
|
|
2132
|
-
|
|
2133
|
-
>
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
```
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
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
|
-
|
|
2295
|
-
|
|
2296
|
-
### @Component(tagName)
|
|
2054
|
+
### $ — DOM Helper
|
|
2297
2055
|
|
|
2298
|
-
|
|
2056
|
+
Helper per seleccionar i manipular elements del DOM.
|
|
2299
2057
|
|
|
2300
|
-
```
|
|
2301
|
-
|
|
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
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
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
|
-
|
|
2357
|
-
|
|
2358
|
-
Genera automaticament getters/setters per atributs HTML.
|
|
2070
|
+
#### HTML inline (index2.html)
|
|
2359
2071
|
|
|
2360
|
-
```
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2379
|
-
@Attr('disabled', { type: 'boolean' })
|
|
2380
|
-
disabled!: boolean
|
|
2106
|
+
## Decorators
|
|
2381
2107
|
|
|
2382
|
-
|
|
2383
|
-
@Attr('dismissible', { type: 'boolean', default: true })
|
|
2384
|
-
dismissible!: boolean
|
|
2108
|
+
Per crear components personalitzats que extenguin `$BaseComponent`:
|
|
2385
2109
|
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
duration!: number
|
|
2389
|
-
```
|
|
2110
|
+
```ts
|
|
2111
|
+
import { Component, ObservedAttributes, Attr, $BaseComponent, i18n } from 'zs3-ui-components'
|
|
2390
2112
|
|
|
2391
|
-
|
|
2113
|
+
@Component('my-component')
|
|
2114
|
+
@ObservedAttributes(['title', 'count'])
|
|
2115
|
+
class MyComponent extends $BaseComponent {
|
|
2392
2116
|
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
import { Component, ObservedAttributes, Float, Move, Attr } from 'zs3-ui-components'
|
|
2117
|
+
@Attr('title', { type: 'string' })
|
|
2118
|
+
title!: string | null
|
|
2396
2119
|
|
|
2397
|
-
@
|
|
2398
|
-
|
|
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
|
-
//
|
|
2124
|
+
// Cridat un cop, abans del primer render
|
|
2408
2125
|
}
|
|
2409
2126
|
|
|
2410
|
-
protected
|
|
2411
|
-
|
|
2412
|
-
|
|
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
|
|
2439
|
-
<
|
|
2440
|
-
<
|
|
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
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
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
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
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
|
-
##
|
|
2522
|
-
|
|
2523
|
-
L'efecte move permet arrossegar elements dins del seu contenidor pare amb el ratoli.
|
|
2157
|
+
## CSS Variables
|
|
2524
2158
|
|
|
2525
|
-
|
|
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
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
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
|
-
##
|
|
2204
|
+
## License
|
|
2781
2205
|
|
|
2782
|
-
|
|
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).
|