rook-cli 1.3.2 → 1.3.5
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.
- package/package.json +3 -2
- package/rook-framework/PRD-INSTALL-COMMAND.md +379 -0
- package/rook-framework/PRD.md +1214 -0
- package/rook-framework/README.md +143 -0
- package/rook-framework/assets/rk-accordion.js +99 -0
- package/rook-framework/assets/rk-alert-dialog.js +132 -0
- package/rook-framework/assets/rk-bottom-app-bar.js +88 -0
- package/rook-framework/assets/rk-carousel.js +145 -0
- package/rook-framework/assets/rk-collapsible.js +151 -0
- package/rook-framework/assets/rk-dialog.js +161 -0
- package/rook-framework/assets/rk-drawer.js +214 -0
- package/rook-framework/assets/rk-framework-core.css +2554 -0
- package/rook-framework/assets/rk-framework-tokens.css +101 -0
- package/rook-framework/assets/rk-modal.js +91 -0
- package/rook-framework/assets/rk-popover.js +264 -0
- package/rook-framework/assets/rk-progress.js +81 -0
- package/rook-framework/assets/rk-quantity.js +91 -0
- package/rook-framework/assets/rk-scroll-area.js +286 -0
- package/rook-framework/assets/rk-sheet.js +157 -0
- package/rook-framework/assets/rk-tabs.js +179 -0
- package/rook-framework/assets/rk-toggle.js +153 -0
- package/rook-framework/blocks/rk-accordion.liquid +97 -0
- package/rook-framework/blocks/rk-badge.liquid +103 -0
- package/rook-framework/blocks/rk-button.liquid +166 -0
- package/rook-framework/blocks/rk-divider.liquid +100 -0
- package/rook-framework/blocks/rk-form-field.liquid +120 -0
- package/rook-framework/blocks/rk-icon.liquid +134 -0
- package/rook-framework/blocks/rk-image.liquid +198 -0
- package/rook-framework/blocks/rk-installments.liquid +99 -0
- package/rook-framework/blocks/rk-pix-discount.liquid +99 -0
- package/rook-framework/blocks/rk-price.liquid +128 -0
- package/rook-framework/blocks/rk-quantity.liquid +108 -0
- package/rook-framework/blocks/rk-quick-add.liquid +137 -0
- package/rook-framework/blocks/rk-skeleton.liquid +104 -0
- package/rook-framework/blocks/rk-typography.liquid +183 -0
- package/rook-framework/config/rk-color-scheme-group.json +138 -0
- package/rook-framework/config/rk-settings_schema.json +259 -0
- package/rook-framework/snippets/rk-accordion.liquid +31 -0
- package/rook-framework/snippets/rk-alert-dialog.liquid +83 -0
- package/rook-framework/snippets/rk-aspect-ratio.liquid +23 -0
- package/rook-framework/snippets/rk-badge.liquid +17 -0
- package/rook-framework/snippets/rk-bottom-app-bar.liquid +51 -0
- package/rook-framework/snippets/rk-button.liquid +49 -0
- package/rook-framework/snippets/rk-card.liquid +64 -0
- package/rook-framework/snippets/rk-carousel.liquid +74 -0
- package/rook-framework/snippets/rk-checkbox.liquid +34 -0
- package/rook-framework/snippets/rk-collapsible.liquid +52 -0
- package/rook-framework/snippets/rk-color-schemes-standalone.liquid +61 -0
- package/rook-framework/snippets/rk-color-schemes.liquid +43 -0
- package/rook-framework/snippets/rk-dialog.liquid +85 -0
- package/rook-framework/snippets/rk-divider.liquid +25 -0
- package/rook-framework/snippets/rk-drawer.liquid +81 -0
- package/rook-framework/snippets/rk-external-assets copy.liquid +33 -0
- package/rook-framework/snippets/rk-external-assets.liquid +68 -0
- package/rook-framework/snippets/rk-form-field.liquid +83 -0
- package/rook-framework/snippets/rk-gap-style.liquid +32 -0
- package/rook-framework/snippets/rk-icon.liquid +28 -0
- package/rook-framework/snippets/rk-image.liquid +60 -0
- package/rook-framework/snippets/rk-input.liquid +35 -0
- package/rook-framework/snippets/rk-installments.liquid +54 -0
- package/rook-framework/snippets/rk-item.liquid +69 -0
- package/rook-framework/snippets/rk-layout-style.liquid +37 -0
- package/rook-framework/snippets/rk-modal.liquid +31 -0
- package/rook-framework/snippets/rk-pix-discount.liquid +34 -0
- package/rook-framework/snippets/rk-popover.liquid +77 -0
- package/rook-framework/snippets/rk-price.liquid +48 -0
- package/rook-framework/snippets/rk-progress.liquid +38 -0
- package/rook-framework/snippets/rk-quantity.liquid +56 -0
- package/rook-framework/snippets/rk-quick-add.liquid +67 -0
- package/rook-framework/snippets/rk-scripts.liquid +17 -0
- package/rook-framework/snippets/rk-scroll-area.liquid +60 -0
- package/rook-framework/snippets/rk-sheet.liquid +86 -0
- package/rook-framework/snippets/rk-size-style.liquid +48 -0
- package/rook-framework/snippets/rk-skeleton.liquid +25 -0
- package/rook-framework/snippets/rk-spacing-padding.liquid +18 -0
- package/rook-framework/snippets/rk-spacing-style.liquid +54 -0
- package/rook-framework/snippets/rk-spinner.liquid +43 -0
- package/rook-framework/snippets/rk-swatch.liquid +33 -0
- package/rook-framework/snippets/rk-table.liquid +44 -0
- package/rook-framework/snippets/rk-tabs.liquid +52 -0
- package/rook-framework/snippets/rk-textarea.liquid +42 -0
- package/rook-framework/snippets/rk-toggle-group.liquid +27 -0
- package/rook-framework/snippets/rk-toggle.liquid +58 -0
- package/rook-framework/snippets/rk-typography.liquid +27 -0
- package/rook-framework/snippets/rk-variables.liquid +76 -0
- package/src/app.js +24 -0
- package/src/commands/InstallCommand.js +133 -0
- package/src/mcp/server.js +111 -1
- package/src/services/FrameworkInstaller.js +485 -0
- package/src/templates/block.liquid.txt +0 -15
- package/src/ui/PromptUI.js +15 -1
- package/src/utils/logger.js +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Rook UI Core Framework
|
|
2
|
+
|
|
3
|
+
> Biblioteca de componentes UI agnóstica para Shopify — portável entre temas.
|
|
4
|
+
|
|
5
|
+
## Instalação Rápida
|
|
6
|
+
|
|
7
|
+
### Automática (recomendado)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
rook install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Manual
|
|
14
|
+
|
|
15
|
+
#### 1. Copiar arquivos para o tema
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Assets (CSS + JS)
|
|
19
|
+
cp rook-framework/assets/* assets/
|
|
20
|
+
|
|
21
|
+
# Snippets (Liquid)
|
|
22
|
+
cp rook-framework/snippets/* snippets/
|
|
23
|
+
|
|
24
|
+
# Blocks (Liquid)
|
|
25
|
+
cp rook-framework/blocks/* blocks/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
#### 2. Injetar configurações
|
|
29
|
+
|
|
30
|
+
Abra `config/settings_schema.json` e adicione cada objeto do array de `rook-framework/config/rk-settings_schema.json` como novos itens no final do array principal (são 4 seções: Cores, Forma e Transição, Parcelamento, Pix).
|
|
31
|
+
|
|
32
|
+
#### 3. Carregar no layout
|
|
33
|
+
|
|
34
|
+
Adicione ao `layout/theme.liquid` (dentro do `<head>`, antes de `{{ content_for_header }}`):
|
|
35
|
+
|
|
36
|
+
```liquid
|
|
37
|
+
{%- comment -%} Rook UI Core Framework {%- endcomment -%}
|
|
38
|
+
{%- render 'rk-variables' -%}
|
|
39
|
+
{{ 'rk-framework-tokens.css' | asset_url | stylesheet_tag }}
|
|
40
|
+
{{ 'rk-framework-core.css' | asset_url | stylesheet_tag }}
|
|
41
|
+
{%- render 'rk-external-assets', location: 'head' -%}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
E antes do `</body>`:
|
|
45
|
+
|
|
46
|
+
```liquid
|
|
47
|
+
{%- comment -%} Rook UI Core Framework — Scripts {%- endcomment -%}
|
|
48
|
+
{%- render 'rk-external-assets', location: 'body' -%}
|
|
49
|
+
{%- render 'rk-scripts' -%}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Ordem de Carregamento
|
|
53
|
+
|
|
54
|
+
1. **`rk-variables`** — CSS vars dinâmicas geradas via Liquid (`{% style %}`)
|
|
55
|
+
2. **`rk-framework-tokens.css`** — Tokens estáticos (spacing, typography, z-index)
|
|
56
|
+
3. **`rk-framework-core.css`** — Estilos BEM de todos os componentes
|
|
57
|
+
4. **`rk-external-assets`** (head) — Preconnects e CDNs externas
|
|
58
|
+
5. **`rk-external-assets`** (body) — Scripts externos (Swiper, etc)
|
|
59
|
+
6. **`rk-scripts`** — Web Components JS do framework
|
|
60
|
+
|
|
61
|
+
## Uso
|
|
62
|
+
|
|
63
|
+
```liquid
|
|
64
|
+
{% render 'rk-button', text: 'Comprar', style: 'primary', size: 'lg' %}
|
|
65
|
+
|
|
66
|
+
{% render 'rk-price', product: product, from: true %}
|
|
67
|
+
|
|
68
|
+
{% render 'rk-installments', price: variant.price %}
|
|
69
|
+
|
|
70
|
+
{% render 'rk-pix-discount', price: variant.price %}
|
|
71
|
+
|
|
72
|
+
{% render 'rk-quantity', value: 1, min: 1, max: 10 %}
|
|
73
|
+
|
|
74
|
+
{% render 'rk-quick-add', product: product, section_id: section.id, show_quantity: true %}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Componentes
|
|
78
|
+
|
|
79
|
+
### Átomos (14)
|
|
80
|
+
| Componente | Arquivo |
|
|
81
|
+
|---|---|
|
|
82
|
+
| Button | `rk-button.liquid` |
|
|
83
|
+
| Image | `rk-image.liquid` |
|
|
84
|
+
| Icon | `rk-icon.liquid` |
|
|
85
|
+
| Input | `rk-input.liquid` |
|
|
86
|
+
| Textarea | `rk-textarea.liquid` |
|
|
87
|
+
| Checkbox | `rk-checkbox.liquid` |
|
|
88
|
+
| Badge | `rk-badge.liquid` |
|
|
89
|
+
| Typography | `rk-typography.liquid` |
|
|
90
|
+
| Divider | `rk-divider.liquid` |
|
|
91
|
+
| Skeleton | `rk-skeleton.liquid` |
|
|
92
|
+
| Swatch | `rk-swatch.liquid` |
|
|
93
|
+
| Aspect Ratio | `rk-aspect-ratio.liquid` |
|
|
94
|
+
| Item | `rk-item.liquid` |
|
|
95
|
+
| Spinner | `rk-spinner.liquid` |
|
|
96
|
+
|
|
97
|
+
### Moléculas (21)
|
|
98
|
+
| Componente | Arquivo | Controller |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| Card | `rk-card.liquid` | — |
|
|
101
|
+
| Price | `rk-price.liquid` | — |
|
|
102
|
+
| Installments | `rk-installments.liquid` | — |
|
|
103
|
+
| Pix Discount | `rk-pix-discount.liquid` | — |
|
|
104
|
+
| Quantity | `rk-quantity.liquid` | `rk-quantity.js` |
|
|
105
|
+
| Accordion | `rk-accordion.liquid` | `rk-accordion.js` |
|
|
106
|
+
| Form Field | `rk-form-field.liquid` | — |
|
|
107
|
+
| Modal | `rk-modal.liquid` | `rk-modal.js` |
|
|
108
|
+
| Popover | `rk-popover.liquid` | `rk-popover.js` |
|
|
109
|
+
| Scroll Area | `rk-scroll-area.liquid` | `rk-scroll-area.js` |
|
|
110
|
+
| Drawer | `rk-drawer.liquid` | `rk-drawer.js` |
|
|
111
|
+
| Dialog | `rk-dialog.liquid` | `rk-dialog.js` |
|
|
112
|
+
| Sheet | `rk-sheet.liquid` | `rk-sheet.js` |
|
|
113
|
+
| Table | `rk-table.liquid` | — |
|
|
114
|
+
| Tabs | `rk-tabs.liquid` | `rk-tabs.js` |
|
|
115
|
+
| Toggle | `rk-toggle.liquid` | `rk-toggle.js` |
|
|
116
|
+
| Toggle Group | `rk-toggle-group.liquid` | `rk-toggle.js` |
|
|
117
|
+
| Progress | `rk-progress.liquid` | `rk-progress.js` |
|
|
118
|
+
| Collapsible | `rk-collapsible.liquid` | `rk-collapsible.js` |
|
|
119
|
+
| Carousel | `rk-carousel.liquid` | `rk-carousel.js` |
|
|
120
|
+
| Bottom App Bar | `rk-bottom-app-bar.liquid` | `rk-bottom-app-bar.js` |
|
|
121
|
+
| Quick Add | `rk-quick-add.liquid` | Horizon `quick-add.js` |
|
|
122
|
+
|
|
123
|
+
### Serviços (2)
|
|
124
|
+
| Componente | Arquivo |
|
|
125
|
+
|---|---|
|
|
126
|
+
| External Assets | `rk-external-assets.liquid` |
|
|
127
|
+
| Scripts Loader | `rk-scripts.liquid` |
|
|
128
|
+
|
|
129
|
+
### Settings (4 seções no painel Shopify)
|
|
130
|
+
| Seção | Settings |
|
|
131
|
+
|---|---|
|
|
132
|
+
| Rook UI — Cores | 13 cores (primárias, secundárias, fundos, semânticas, texto, bordas, foco) |
|
|
133
|
+
| Rook UI — Forma e Transição | 4 border-radius + velocidade transição |
|
|
134
|
+
| Rook UI — Parcelamento | 5 configs de parcelamento |
|
|
135
|
+
| Rook UI — Pix | 3 configs de desconto Pix |
|
|
136
|
+
|
|
137
|
+
## Documentação Completa
|
|
138
|
+
|
|
139
|
+
Consulte o [PRD.md](PRD.md) para detalhes de arquitetura, props, events e guias.
|
|
140
|
+
|
|
141
|
+
## Licença
|
|
142
|
+
|
|
143
|
+
Proprietário — Chesslab
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Rook UI Core — Accordion Controller
|
|
3
|
+
Web Component: <rk-accordion-element>
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
if (!customElements.get('rk-accordion-element')) {
|
|
7
|
+
class RkAccordionElement extends HTMLElement {
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
this.details = null;
|
|
11
|
+
this.content = null;
|
|
12
|
+
this.animation = null;
|
|
13
|
+
this.isClosing = false;
|
|
14
|
+
this.isExpanding = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
connectedCallback() {
|
|
18
|
+
this.details = this.querySelector('details');
|
|
19
|
+
this.content = this.querySelector('.rk-accordion__content');
|
|
20
|
+
|
|
21
|
+
if (!this.details || !this.content) return;
|
|
22
|
+
|
|
23
|
+
const summary = this.details.querySelector('summary');
|
|
24
|
+
if (summary) {
|
|
25
|
+
summary.addEventListener('click', (e) => this.onClick(e));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onClick(e) {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
|
|
32
|
+
this.details.style.overflow = 'hidden';
|
|
33
|
+
|
|
34
|
+
if (this.isClosing || !this.details.open) {
|
|
35
|
+
this.open();
|
|
36
|
+
} else if (this.isExpanding || this.details.open) {
|
|
37
|
+
this.shrink();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
shrink() {
|
|
42
|
+
this.isClosing = true;
|
|
43
|
+
|
|
44
|
+
const startHeight = `${this.details.offsetHeight}px`;
|
|
45
|
+
const summary = this.details.querySelector('summary');
|
|
46
|
+
const endHeight = `${summary.offsetHeight}px`;
|
|
47
|
+
|
|
48
|
+
if (this.animation) {
|
|
49
|
+
this.animation.cancel();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.animation = this.details.animate(
|
|
53
|
+
{ height: [startHeight, endHeight] },
|
|
54
|
+
{ duration: 200, easing: 'ease-out' }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
this.animation.onfinish = () => this.onAnimationFinish(false);
|
|
58
|
+
this.animation.oncancel = () => (this.isClosing = false);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
open() {
|
|
62
|
+
this.details.style.height = `${this.details.offsetHeight}px`;
|
|
63
|
+
this.details.open = true;
|
|
64
|
+
|
|
65
|
+
window.requestAnimationFrame(() => this.expand());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
expand() {
|
|
69
|
+
this.isExpanding = true;
|
|
70
|
+
|
|
71
|
+
const startHeight = `${this.details.offsetHeight}px`;
|
|
72
|
+
const summary = this.details.querySelector('summary');
|
|
73
|
+
const endHeight = `${summary.offsetHeight + this.content.offsetHeight}px`;
|
|
74
|
+
|
|
75
|
+
if (this.animation) {
|
|
76
|
+
this.animation.cancel();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.animation = this.details.animate(
|
|
80
|
+
{ height: [startHeight, endHeight] },
|
|
81
|
+
{ duration: 200, easing: 'ease-out' }
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
this.animation.onfinish = () => this.onAnimationFinish(true);
|
|
85
|
+
this.animation.oncancel = () => (this.isExpanding = false);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onAnimationFinish(open) {
|
|
89
|
+
this.details.open = open;
|
|
90
|
+
this.animation = null;
|
|
91
|
+
this.isClosing = false;
|
|
92
|
+
this.isExpanding = false;
|
|
93
|
+
this.details.style.height = '';
|
|
94
|
+
this.details.style.overflow = '';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
customElements.define('rk-accordion-element', RkAccordionElement);
|
|
99
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Rook UI Core — Alert Dialog Controller
|
|
3
|
+
Web Component: <rk-alert-dialog-element>
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
if (!customElements.get('rk-alert-dialog-element')) {
|
|
7
|
+
class RkAlertDialogElement extends HTMLElement {
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
this.dialog = null;
|
|
11
|
+
this.previousFocus = null;
|
|
12
|
+
this.focusableElements = [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
connectedCallback() {
|
|
16
|
+
this.dialog = this.querySelector('.rk-alert-dialog');
|
|
17
|
+
if (!this.dialog) return;
|
|
18
|
+
|
|
19
|
+
this._bindEvents();
|
|
20
|
+
this._setupOpenTriggers();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ------------------------------------------------------------------ */
|
|
24
|
+
/* Events */
|
|
25
|
+
/* ------------------------------------------------------------------ */
|
|
26
|
+
|
|
27
|
+
_bindEvents() {
|
|
28
|
+
// Cancel buttons close the dialog
|
|
29
|
+
this.querySelectorAll('[data-action="cancel"]').forEach((el) => {
|
|
30
|
+
el.addEventListener('click', () => this.close('cancel'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Action buttons fire confirm then close
|
|
34
|
+
this.querySelectorAll('[data-action="confirm"]').forEach((el) => {
|
|
35
|
+
el.addEventListener('click', () => {
|
|
36
|
+
this.dispatchEvent(
|
|
37
|
+
new CustomEvent('rk:alert-dialog:confirm', {
|
|
38
|
+
bubbles: true,
|
|
39
|
+
detail: { id: this.dialog.id },
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
// Don't auto-close: let consumer decide (e.g. loading state)
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ESC key — configurable via data-esc-close
|
|
47
|
+
this._onKeyDown = (e) => {
|
|
48
|
+
if (e.key === 'Escape' && this.isOpen()) {
|
|
49
|
+
const escClose = this.dataset.escClose !== 'false';
|
|
50
|
+
if (escClose) {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
this.close('cancel');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// NOTE: overlay click does NOT close (per PRD requirement)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_setupOpenTriggers() {
|
|
61
|
+
const dialogId = this.dataset.alertDialogId || this.dialog?.id;
|
|
62
|
+
if (!dialogId) return;
|
|
63
|
+
|
|
64
|
+
document.querySelectorAll(`[data-alert-dialog-open="${dialogId}"]`).forEach((trigger) => {
|
|
65
|
+
trigger.addEventListener('click', (e) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
this.open();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ------------------------------------------------------------------ */
|
|
73
|
+
/* Public API */
|
|
74
|
+
/* ------------------------------------------------------------------ */
|
|
75
|
+
|
|
76
|
+
isOpen() {
|
|
77
|
+
return this.dialog?.classList.contains('rk-alert-dialog--active');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
open() {
|
|
81
|
+
if (!this.dialog || this.isOpen()) return;
|
|
82
|
+
|
|
83
|
+
this.previousFocus = document.activeElement;
|
|
84
|
+
this.dialog.classList.add('rk-alert-dialog--active');
|
|
85
|
+
document.body.style.overflow = 'hidden';
|
|
86
|
+
document.addEventListener('keydown', this._onKeyDown);
|
|
87
|
+
|
|
88
|
+
// Focus the cancel button by default (safety: prevent accidental confirm)
|
|
89
|
+
requestAnimationFrame(() => {
|
|
90
|
+
const cancelBtn = this.dialog.querySelector('[data-action="cancel"]');
|
|
91
|
+
if (cancelBtn) {
|
|
92
|
+
cancelBtn.focus();
|
|
93
|
+
} else {
|
|
94
|
+
// Fallback: first focusable element
|
|
95
|
+
const first = this.dialog.querySelector(
|
|
96
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
97
|
+
);
|
|
98
|
+
if (first) first.focus();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.dispatchEvent(
|
|
103
|
+
new CustomEvent('rk:alert-dialog:open', {
|
|
104
|
+
bubbles: true,
|
|
105
|
+
detail: { id: this.dialog.id },
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
close(reason) {
|
|
111
|
+
if (!this.dialog || !this.isOpen()) return;
|
|
112
|
+
|
|
113
|
+
this.dialog.classList.remove('rk-alert-dialog--active');
|
|
114
|
+
document.body.style.overflow = '';
|
|
115
|
+
document.removeEventListener('keydown', this._onKeyDown);
|
|
116
|
+
|
|
117
|
+
if (this.previousFocus) {
|
|
118
|
+
this.previousFocus.focus();
|
|
119
|
+
this.previousFocus = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.dispatchEvent(
|
|
123
|
+
new CustomEvent('rk:alert-dialog:close', {
|
|
124
|
+
bubbles: true,
|
|
125
|
+
detail: { id: this.dialog.id, reason: reason || 'cancel' },
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
customElements.define('rk-alert-dialog-element', RkAlertDialogElement);
|
|
132
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Rook UI Core — Bottom App Bar Controller
|
|
3
|
+
Web Component: <rk-bottom-app-bar-element>
|
|
4
|
+
========================================================================== */
|
|
5
|
+
|
|
6
|
+
if (!customElements.get('rk-bottom-app-bar-element')) {
|
|
7
|
+
class RkBottomAppBarElement extends HTMLElement {
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
this.items = [];
|
|
11
|
+
this._onKeyDown = this._onKeyDown.bind(this);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
connectedCallback() {
|
|
15
|
+
// Setup Menubar Role if missing
|
|
16
|
+
if (!this.hasAttribute('role')) {
|
|
17
|
+
this.setAttribute('role', 'menubar');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.items = Array.from(this.querySelectorAll('[role="menuitem"]'));
|
|
21
|
+
|
|
22
|
+
// Allow focus only on the first item initially
|
|
23
|
+
this.items.forEach((item, index) => {
|
|
24
|
+
if (index === 0) {
|
|
25
|
+
item.setAttribute('tabindex', '0');
|
|
26
|
+
} else {
|
|
27
|
+
item.setAttribute('tabindex', '-1');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.addEventListener('keydown', this._onKeyDown);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
disconnectedCallback() {
|
|
35
|
+
this.removeEventListener('keydown', this._onKeyDown);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ------------------------------------------------------------------ */
|
|
39
|
+
/* Roving Tabindex & ARIA Keyboard Navigation */
|
|
40
|
+
/* ------------------------------------------------------------------ */
|
|
41
|
+
|
|
42
|
+
_onKeyDown(e) {
|
|
43
|
+
const currentItem = document.activeElement;
|
|
44
|
+
const currentIndex = this.items.indexOf(currentItem);
|
|
45
|
+
|
|
46
|
+
if (currentIndex === -1) return; // Focus isn't on an item
|
|
47
|
+
|
|
48
|
+
let nextIndex = currentIndex;
|
|
49
|
+
|
|
50
|
+
switch (e.key) {
|
|
51
|
+
case 'ArrowRight':
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
nextIndex = (currentIndex + 1) % this.items.length;
|
|
54
|
+
break;
|
|
55
|
+
case 'ArrowLeft':
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
nextIndex = (currentIndex - 1 + this.items.length) % this.items.length;
|
|
58
|
+
break;
|
|
59
|
+
case 'Home':
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
nextIndex = 0;
|
|
62
|
+
break;
|
|
63
|
+
case 'End':
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
nextIndex = this.items.length - 1;
|
|
66
|
+
break;
|
|
67
|
+
case 'ArrowUp':
|
|
68
|
+
// If the item controls a popover or dropdown, we might proxy it
|
|
69
|
+
// But usually, standard Enter/Space handles trigger. The PRD mentions ArrowUp logic for menus.
|
|
70
|
+
// If there's an upward popover attached, we simulate a click or open it.
|
|
71
|
+
if (currentItem.hasAttribute('aria-haspopup')) {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
currentItem.click();
|
|
74
|
+
}
|
|
75
|
+
return; // Early return, don't move focus right/left
|
|
76
|
+
default:
|
|
77
|
+
return; // Ignore other keys
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Move focus
|
|
81
|
+
this.items[currentIndex].setAttribute('tabindex', '-1');
|
|
82
|
+
this.items[nextIndex].setAttribute('tabindex', '0');
|
|
83
|
+
this.items[nextIndex].focus();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
customElements.define('rk-bottom-app-bar-element', RkBottomAppBarElement);
|
|
88
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Rook UI Core — Carousel Controller
|
|
3
|
+
Web Component: <rk-carousel-element>
|
|
4
|
+
Powered by Swiper.js (Lazy Loaded via CDN)
|
|
5
|
+
========================================================================== */
|
|
6
|
+
|
|
7
|
+
if (!customElements.get('rk-carousel-element')) {
|
|
8
|
+
class RkCarouselElement extends HTMLElement {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.swiper = null;
|
|
12
|
+
this.options = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async connectedCallback() {
|
|
16
|
+
await this._loadSwiper();
|
|
17
|
+
this._initSwiper();
|
|
18
|
+
|
|
19
|
+
// Resize observer for breakpoints updates/recalculation
|
|
20
|
+
if (window.ResizeObserver) {
|
|
21
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
22
|
+
if (this.swiper && typeof this.swiper.update === 'function') {
|
|
23
|
+
this.swiper.update();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
this.resizeObserver.observe(this);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
disconnectedCallback() {
|
|
31
|
+
if (this.swiper && typeof this.swiper.destroy === 'function') {
|
|
32
|
+
this.swiper.destroy(true, true);
|
|
33
|
+
this.swiper = null;
|
|
34
|
+
}
|
|
35
|
+
if (this.resizeObserver) {
|
|
36
|
+
this.resizeObserver.disconnect();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async _loadSwiper() {
|
|
41
|
+
// Check if Swiper is already on window
|
|
42
|
+
if (window.Swiper) return;
|
|
43
|
+
|
|
44
|
+
// Wait for concurrent loads if another carousel is fetching it
|
|
45
|
+
if (window.RkSwiperLoading) {
|
|
46
|
+
return window.RkSwiperLoading;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
window.RkSwiperLoading = new Promise((resolve, reject) => {
|
|
50
|
+
const script = document.createElement('script');
|
|
51
|
+
script.src = 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js';
|
|
52
|
+
|
|
53
|
+
const style = document.createElement('link');
|
|
54
|
+
style.rel = 'stylesheet';
|
|
55
|
+
style.href = 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css';
|
|
56
|
+
|
|
57
|
+
script.onload = () => resolve();
|
|
58
|
+
script.onerror = reject;
|
|
59
|
+
|
|
60
|
+
document.head.appendChild(style);
|
|
61
|
+
document.head.appendChild(script);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return window.RkSwiperLoading;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_initSwiper() {
|
|
68
|
+
const container = this.querySelector('.swiper');
|
|
69
|
+
if (!container) return;
|
|
70
|
+
|
|
71
|
+
// Read dataset options for customized parameters
|
|
72
|
+
const rawOptions = this.getAttribute('data-swiper-options');
|
|
73
|
+
if (rawOptions) {
|
|
74
|
+
try {
|
|
75
|
+
this.options = JSON.parse(rawOptions);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error('Invalid Swiper options JSON:', e);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Fallback attribute reading for clean API
|
|
82
|
+
const gap = parseInt(this.getAttribute('data-gap')) || 0;
|
|
83
|
+
const deskItems = parseFloat(this.getAttribute('data-desktop-items')) || 3;
|
|
84
|
+
const mobileItems = parseFloat(this.getAttribute('data-mobile-items')) || 1;
|
|
85
|
+
|
|
86
|
+
const defaultOptions = {
|
|
87
|
+
slidesPerView: mobileItems,
|
|
88
|
+
spaceBetween: gap,
|
|
89
|
+
navigation: {
|
|
90
|
+
nextEl: this.querySelector('.swiper-button-next'),
|
|
91
|
+
prevEl: this.querySelector('.swiper-button-prev'),
|
|
92
|
+
},
|
|
93
|
+
pagination: {
|
|
94
|
+
el: this.querySelector('.swiper-pagination'),
|
|
95
|
+
clickable: true,
|
|
96
|
+
},
|
|
97
|
+
loop: this.hasAttribute('data-loop') && this.getAttribute('data-loop') !== 'false',
|
|
98
|
+
autoplay: this.hasAttribute('data-autoplay') ? {
|
|
99
|
+
delay: parseInt(this.getAttribute('data-autoplay-delay')) || 3000,
|
|
100
|
+
disableOnInteraction: false,
|
|
101
|
+
} : false,
|
|
102
|
+
freeMode: this.hasAttribute('data-free-mode'),
|
|
103
|
+
breakpoints: this.options.breakpoints || {
|
|
104
|
+
768: {
|
|
105
|
+
slidesPerView: deskItems
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
// Accessibility features native to Swiper
|
|
109
|
+
a11y: {
|
|
110
|
+
enabled: true,
|
|
111
|
+
prevSlideMessage: 'Slide Anterior',
|
|
112
|
+
nextSlideMessage: 'Próximo Slide',
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Merge passed options directly, allowing deep overrides
|
|
117
|
+
const finalOptions = { ...defaultOptions, ...this.options };
|
|
118
|
+
|
|
119
|
+
// Initialize virtual component integration
|
|
120
|
+
this.swiper = new window.Swiper(container, finalOptions);
|
|
121
|
+
|
|
122
|
+
this.dispatchEvent(new CustomEvent('rk:carousel:init', {
|
|
123
|
+
bubbles: true,
|
|
124
|
+
detail: { swiper: this.swiper }
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* ------------------------------------------------------------------ */
|
|
129
|
+
/* Public API methods for external buttons / programmatic flow */
|
|
130
|
+
/* ------------------------------------------------------------------ */
|
|
131
|
+
slideNext() {
|
|
132
|
+
if (this.swiper) this.swiper.slideNext();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
slidePrev() {
|
|
136
|
+
if (this.swiper) this.swiper.slidePrev();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
slideTo(index) {
|
|
140
|
+
if (this.swiper) this.swiper.slideTo(index);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
customElements.define('rk-carousel-element', RkCarouselElement);
|
|
145
|
+
}
|