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.
Files changed (92) hide show
  1. package/package.json +3 -2
  2. package/rook-framework/PRD-INSTALL-COMMAND.md +379 -0
  3. package/rook-framework/PRD.md +1214 -0
  4. package/rook-framework/README.md +143 -0
  5. package/rook-framework/assets/rk-accordion.js +99 -0
  6. package/rook-framework/assets/rk-alert-dialog.js +132 -0
  7. package/rook-framework/assets/rk-bottom-app-bar.js +88 -0
  8. package/rook-framework/assets/rk-carousel.js +145 -0
  9. package/rook-framework/assets/rk-collapsible.js +151 -0
  10. package/rook-framework/assets/rk-dialog.js +161 -0
  11. package/rook-framework/assets/rk-drawer.js +214 -0
  12. package/rook-framework/assets/rk-framework-core.css +2554 -0
  13. package/rook-framework/assets/rk-framework-tokens.css +101 -0
  14. package/rook-framework/assets/rk-modal.js +91 -0
  15. package/rook-framework/assets/rk-popover.js +264 -0
  16. package/rook-framework/assets/rk-progress.js +81 -0
  17. package/rook-framework/assets/rk-quantity.js +91 -0
  18. package/rook-framework/assets/rk-scroll-area.js +286 -0
  19. package/rook-framework/assets/rk-sheet.js +157 -0
  20. package/rook-framework/assets/rk-tabs.js +179 -0
  21. package/rook-framework/assets/rk-toggle.js +153 -0
  22. package/rook-framework/blocks/rk-accordion.liquid +97 -0
  23. package/rook-framework/blocks/rk-badge.liquid +103 -0
  24. package/rook-framework/blocks/rk-button.liquid +166 -0
  25. package/rook-framework/blocks/rk-divider.liquid +100 -0
  26. package/rook-framework/blocks/rk-form-field.liquid +120 -0
  27. package/rook-framework/blocks/rk-icon.liquid +134 -0
  28. package/rook-framework/blocks/rk-image.liquid +198 -0
  29. package/rook-framework/blocks/rk-installments.liquid +99 -0
  30. package/rook-framework/blocks/rk-pix-discount.liquid +99 -0
  31. package/rook-framework/blocks/rk-price.liquid +128 -0
  32. package/rook-framework/blocks/rk-quantity.liquid +108 -0
  33. package/rook-framework/blocks/rk-quick-add.liquid +137 -0
  34. package/rook-framework/blocks/rk-skeleton.liquid +104 -0
  35. package/rook-framework/blocks/rk-typography.liquid +183 -0
  36. package/rook-framework/config/rk-color-scheme-group.json +138 -0
  37. package/rook-framework/config/rk-settings_schema.json +259 -0
  38. package/rook-framework/snippets/rk-accordion.liquid +31 -0
  39. package/rook-framework/snippets/rk-alert-dialog.liquid +83 -0
  40. package/rook-framework/snippets/rk-aspect-ratio.liquid +23 -0
  41. package/rook-framework/snippets/rk-badge.liquid +17 -0
  42. package/rook-framework/snippets/rk-bottom-app-bar.liquid +51 -0
  43. package/rook-framework/snippets/rk-button.liquid +49 -0
  44. package/rook-framework/snippets/rk-card.liquid +64 -0
  45. package/rook-framework/snippets/rk-carousel.liquid +74 -0
  46. package/rook-framework/snippets/rk-checkbox.liquid +34 -0
  47. package/rook-framework/snippets/rk-collapsible.liquid +52 -0
  48. package/rook-framework/snippets/rk-color-schemes-standalone.liquid +61 -0
  49. package/rook-framework/snippets/rk-color-schemes.liquid +43 -0
  50. package/rook-framework/snippets/rk-dialog.liquid +85 -0
  51. package/rook-framework/snippets/rk-divider.liquid +25 -0
  52. package/rook-framework/snippets/rk-drawer.liquid +81 -0
  53. package/rook-framework/snippets/rk-external-assets copy.liquid +33 -0
  54. package/rook-framework/snippets/rk-external-assets.liquid +68 -0
  55. package/rook-framework/snippets/rk-form-field.liquid +83 -0
  56. package/rook-framework/snippets/rk-gap-style.liquid +32 -0
  57. package/rook-framework/snippets/rk-icon.liquid +28 -0
  58. package/rook-framework/snippets/rk-image.liquid +60 -0
  59. package/rook-framework/snippets/rk-input.liquid +35 -0
  60. package/rook-framework/snippets/rk-installments.liquid +54 -0
  61. package/rook-framework/snippets/rk-item.liquid +69 -0
  62. package/rook-framework/snippets/rk-layout-style.liquid +37 -0
  63. package/rook-framework/snippets/rk-modal.liquid +31 -0
  64. package/rook-framework/snippets/rk-pix-discount.liquid +34 -0
  65. package/rook-framework/snippets/rk-popover.liquid +77 -0
  66. package/rook-framework/snippets/rk-price.liquid +48 -0
  67. package/rook-framework/snippets/rk-progress.liquid +38 -0
  68. package/rook-framework/snippets/rk-quantity.liquid +56 -0
  69. package/rook-framework/snippets/rk-quick-add.liquid +67 -0
  70. package/rook-framework/snippets/rk-scripts.liquid +17 -0
  71. package/rook-framework/snippets/rk-scroll-area.liquid +60 -0
  72. package/rook-framework/snippets/rk-sheet.liquid +86 -0
  73. package/rook-framework/snippets/rk-size-style.liquid +48 -0
  74. package/rook-framework/snippets/rk-skeleton.liquid +25 -0
  75. package/rook-framework/snippets/rk-spacing-padding.liquid +18 -0
  76. package/rook-framework/snippets/rk-spacing-style.liquid +54 -0
  77. package/rook-framework/snippets/rk-spinner.liquid +43 -0
  78. package/rook-framework/snippets/rk-swatch.liquid +33 -0
  79. package/rook-framework/snippets/rk-table.liquid +44 -0
  80. package/rook-framework/snippets/rk-tabs.liquid +52 -0
  81. package/rook-framework/snippets/rk-textarea.liquid +42 -0
  82. package/rook-framework/snippets/rk-toggle-group.liquid +27 -0
  83. package/rook-framework/snippets/rk-toggle.liquid +58 -0
  84. package/rook-framework/snippets/rk-typography.liquid +27 -0
  85. package/rook-framework/snippets/rk-variables.liquid +76 -0
  86. package/src/app.js +24 -0
  87. package/src/commands/InstallCommand.js +133 -0
  88. package/src/mcp/server.js +111 -1
  89. package/src/services/FrameworkInstaller.js +485 -0
  90. package/src/templates/block.liquid.txt +0 -15
  91. package/src/ui/PromptUI.js +15 -1
  92. 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
+ }