rook-cli 1.3.7 → 1.3.9

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.
@@ -1,6 +1,9 @@
1
1
  /* ==========================================================================
2
- Rook UI Core — Modal Controller
2
+ Rook UI Core — Modal Media Lightbox Controller
3
3
  Web Component: <rk-modal-element>
4
+
5
+ Handles image zoom, hosted video, YouTube/Vimeo embeds, and raw HTML
6
+ in a fullscreen lightbox overlay.
4
7
  ========================================================================== */
5
8
 
6
9
  if (!customElements.get('rk-modal-element')) {
@@ -8,33 +11,33 @@ if (!customElements.get('rk-modal-element')) {
8
11
  constructor() {
9
12
  super();
10
13
  this.modal = null;
11
- this.focusableElements = [];
14
+ this.content = null;
12
15
  this.previousFocus = null;
16
+
17
+ this._onKeyDown = this._onKeyDown.bind(this);
13
18
  }
14
19
 
15
20
  connectedCallback() {
16
21
  this.modal = this.querySelector('.rk-modal');
22
+ this.content = this.querySelector('.rk-modal__content');
23
+
17
24
  if (!this.modal) return;
18
25
 
19
- this.bindEvents();
20
- this.setupOpenTriggers();
26
+ this._bindEvents();
27
+ this._setupOpenTriggers();
21
28
  }
22
29
 
23
- bindEvents() {
24
- // Close buttons (overlay & X)
30
+ /* ------------------------------------------------------------------ */
31
+ /* Events */
32
+ /* ------------------------------------------------------------------ */
33
+
34
+ _bindEvents() {
25
35
  this.querySelectorAll('[data-action="close"]').forEach((el) => {
26
36
  el.addEventListener('click', () => this.close());
27
37
  });
28
-
29
- // ESC key
30
- document.addEventListener('keydown', (e) => {
31
- if (e.key === 'Escape' && this.isOpen()) {
32
- this.close();
33
- }
34
- });
35
38
  }
36
39
 
37
- setupOpenTriggers() {
40
+ _setupOpenTriggers() {
38
41
  const modalId = this.dataset.modalId || this.modal?.id;
39
42
  if (!modalId) return;
40
43
 
@@ -46,45 +49,122 @@ if (!customElements.get('rk-modal-element')) {
46
49
  });
47
50
  }
48
51
 
52
+ /* ------------------------------------------------------------------ */
53
+ /* Public API */
54
+ /* ------------------------------------------------------------------ */
55
+
49
56
  isOpen() {
50
57
  return this.modal?.classList.contains('rk-modal--active');
51
58
  }
52
59
 
53
60
  open() {
54
- if (!this.modal) return;
61
+ if (!this.modal || this.isOpen()) return;
55
62
 
56
63
  this.previousFocus = document.activeElement;
57
64
  this.modal.classList.add('rk-modal--active');
58
65
  document.body.style.overflow = 'hidden';
66
+ document.addEventListener('keydown', this._onKeyDown);
59
67
 
60
- // Trap focus
61
- this.focusableElements = this.modal.querySelectorAll(
62
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
63
- );
68
+ // Load lazy iframes (YouTube/Vimeo)
69
+ this._loadIframes();
64
70
 
65
- if (this.focusableElements.length > 0) {
66
- this.focusableElements[0].focus();
71
+ // Autoplay video if configured
72
+ if (this.dataset.autoplay === 'true') {
73
+ this._playMedia();
67
74
  }
68
75
 
76
+ // Focus close button
77
+ requestAnimationFrame(() => {
78
+ const closeBtn = this.querySelector('.rk-modal__close');
79
+ if (closeBtn) closeBtn.focus();
80
+ });
81
+
69
82
  this.dispatchEvent(
70
- new CustomEvent('rk:modal:open', { bubbles: true, detail: { id: this.modal.id } })
83
+ new CustomEvent('rk:modal:open', {
84
+ bubbles: true,
85
+ detail: { id: this.modal.id, type: this.dataset.type },
86
+ })
71
87
  );
72
88
  }
73
89
 
74
90
  close() {
75
- if (!this.modal) return;
91
+ if (!this.modal || !this.isOpen()) return;
76
92
 
77
93
  this.modal.classList.remove('rk-modal--active');
78
94
  document.body.style.overflow = '';
95
+ document.removeEventListener('keydown', this._onKeyDown);
96
+
97
+ // Stop all media
98
+ this._stopMedia();
79
99
 
80
100
  if (this.previousFocus) {
81
101
  this.previousFocus.focus();
102
+ this.previousFocus = null;
82
103
  }
83
104
 
84
105
  this.dispatchEvent(
85
- new CustomEvent('rk:modal:close', { bubbles: true, detail: { id: this.modal.id } })
106
+ new CustomEvent('rk:modal:close', {
107
+ bubbles: true,
108
+ detail: { id: this.modal.id, type: this.dataset.type },
109
+ })
86
110
  );
87
111
  }
112
+
113
+ /* ------------------------------------------------------------------ */
114
+ /* Media helpers */
115
+ /* ------------------------------------------------------------------ */
116
+
117
+ _loadIframes() {
118
+ this.querySelectorAll('iframe[data-src]').forEach((iframe) => {
119
+ if (!iframe.src || iframe.src === 'about:blank') {
120
+ const src = iframe.dataset.src;
121
+ if (this.dataset.autoplay === 'true' && src.includes('youtube.com')) {
122
+ iframe.src = src + (src.includes('?') ? '&' : '?') + 'autoplay=1';
123
+ } else if (this.dataset.autoplay === 'true' && src.includes('vimeo.com')) {
124
+ iframe.src = src + (src.includes('?') ? '&' : '?') + 'autoplay=1';
125
+ } else {
126
+ iframe.src = src;
127
+ }
128
+ }
129
+ });
130
+ }
131
+
132
+ _playMedia() {
133
+ const video = this.querySelector('video[data-autoplay]');
134
+ if (video) {
135
+ video.play().catch(() => {
136
+ /* autoplay blocked by browser — silent fail */
137
+ });
138
+ }
139
+ }
140
+
141
+ _stopMedia() {
142
+ // Pause hosted videos
143
+ this.querySelectorAll('video').forEach((v) => {
144
+ v.pause();
145
+ v.currentTime = 0;
146
+ });
147
+
148
+ // Clear iframe src to stop YouTube/Vimeo playback
149
+ this.querySelectorAll('iframe').forEach((iframe) => {
150
+ if (iframe.dataset.src) {
151
+ iframe.src = 'about:blank';
152
+ }
153
+ });
154
+ }
155
+
156
+ /* ------------------------------------------------------------------ */
157
+ /* Keyboard (ESC) */
158
+ /* ------------------------------------------------------------------ */
159
+
160
+ _onKeyDown(e) {
161
+ if (!this.isOpen()) return;
162
+
163
+ if (e.key === 'Escape') {
164
+ e.preventDefault();
165
+ this.close();
166
+ }
167
+ }
88
168
  }
89
169
 
90
170
  customElements.define('rk-modal-element', RkModalElement);
@@ -0,0 +1,212 @@
1
+ /* ==========================================================================
2
+ Rook UI Core — Reveal Controller
3
+ Web Component: <rk-reveal-element>
4
+
5
+ Revelacao progressiva por scroll usando IntersectionObserver.
6
+ Suporta animacoes: fade-up, fade-down, fade-left, fade-right, fade, zoom.
7
+ Suporta stagger (cascata) entre filhos diretos do content.
8
+ ========================================================================== */
9
+
10
+ if (!customElements.get('rk-reveal-element')) {
11
+ class RkRevealElement extends HTMLElement {
12
+ constructor() {
13
+ super();
14
+ this.observer = null;
15
+ this.hasRevealed = false;
16
+ }
17
+
18
+ connectedCallback() {
19
+ /* ---------------------------------------------------------- */
20
+ /* Leitura dos data-attributes */
21
+ /* ---------------------------------------------------------- */
22
+ this._animation = this.dataset.animation || 'fade-up';
23
+ this._duration = parseInt(this.dataset.duration) || 600;
24
+ this._delay = parseInt(this.dataset.delay) || 0;
25
+ this._stagger = parseInt(this.dataset.stagger) || 0;
26
+ this._threshold = parseFloat(this.dataset.threshold) || 0.1;
27
+ this._easing = this.dataset.easing || 'cubic-bezier(0.16, 1, 0.3, 1)';
28
+ this._once = this.dataset.once !== 'false';
29
+
30
+ /* ---------------------------------------------------------- */
31
+ /* Estado inicial: escondido */
32
+ /* ---------------------------------------------------------- */
33
+ this._applyInitialState();
34
+
35
+ /* ---------------------------------------------------------- */
36
+ /* Respeitar prefers-reduced-motion */
37
+ /* ---------------------------------------------------------- */
38
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
39
+ this._revealImmediate();
40
+ return;
41
+ }
42
+
43
+ /* ---------------------------------------------------------- */
44
+ /* IntersectionObserver */
45
+ /* ---------------------------------------------------------- */
46
+ this.observer = new IntersectionObserver(
47
+ (entries) => {
48
+ entries.forEach((entry) => {
49
+ if (entry.isIntersecting) {
50
+ this._reveal();
51
+ if (this._once) {
52
+ this.observer.unobserve(this);
53
+ }
54
+ } else if (!this._once && this.hasRevealed) {
55
+ this._hide();
56
+ }
57
+ });
58
+ },
59
+ { threshold: this._threshold }
60
+ );
61
+
62
+ this.observer.observe(this);
63
+ }
64
+
65
+ disconnectedCallback() {
66
+ if (this.observer) {
67
+ this.observer.disconnect();
68
+ this.observer = null;
69
+ }
70
+ }
71
+
72
+ /* ------------------------------------------------------------------ */
73
+ /* Estado inicial (pre-animacao) */
74
+ /* ------------------------------------------------------------------ */
75
+
76
+ _applyInitialState() {
77
+ const content = this.querySelector('.rk-reveal__content');
78
+ if (!content) return;
79
+
80
+ content.style.transition = 'none';
81
+ content.style.willChange = 'opacity, transform';
82
+
83
+ const transforms = {
84
+ 'fade-up': 'translateY(30px)',
85
+ 'fade-down': 'translateY(-30px)',
86
+ 'fade-left': 'translateX(30px)',
87
+ 'fade-right': 'translateX(-30px)',
88
+ 'fade': 'none',
89
+ 'zoom': 'scale(0.92)',
90
+ };
91
+
92
+ content.style.opacity = '0';
93
+ const transform = transforms[this._animation] || transforms['fade-up'];
94
+ if (transform !== 'none') {
95
+ content.style.transform = transform;
96
+ }
97
+
98
+ /* Stagger: esconde filhos diretos individualmente */
99
+ if (this._stagger > 0) {
100
+ const children = content.children;
101
+ for (let i = 0; i < children.length; i++) {
102
+ children[i].style.opacity = '0';
103
+ children[i].style.willChange = 'opacity, transform';
104
+ if (transform !== 'none') {
105
+ children[i].style.transform = transform;
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ /* ------------------------------------------------------------------ */
112
+ /* Revelacao com animacao */
113
+ /* ------------------------------------------------------------------ */
114
+
115
+ _reveal() {
116
+ this.hasRevealed = true;
117
+ const content = this.querySelector('.rk-reveal__content');
118
+ if (!content) return;
119
+
120
+ if (this._stagger > 0) {
121
+ this._revealWithStagger(content);
122
+ } else {
123
+ this._revealSingle(content);
124
+ }
125
+
126
+ this.classList.add('rk-reveal--visible');
127
+
128
+ this.dispatchEvent(
129
+ new CustomEvent('rk:reveal:visible', {
130
+ bubbles: true,
131
+ detail: { animation: this._animation },
132
+ })
133
+ );
134
+ }
135
+
136
+ _revealSingle(content) {
137
+ requestAnimationFrame(() => {
138
+ content.style.transition = `opacity ${this._duration}ms ${this._easing} ${this._delay}ms, transform ${this._duration}ms ${this._easing} ${this._delay}ms`;
139
+ content.style.opacity = '1';
140
+ content.style.transform = 'translate(0) scale(1)';
141
+ });
142
+ }
143
+
144
+ _revealWithStagger(content) {
145
+ /* O wrapper fica visivel imediatamente */
146
+ content.style.transition = `opacity ${this._duration}ms ${this._easing} ${this._delay}ms`;
147
+ content.style.opacity = '1';
148
+ content.style.transform = 'none';
149
+
150
+ /* Cada filho anima individualmente com delay incremental */
151
+ const children = content.children;
152
+ for (let i = 0; i < children.length; i++) {
153
+ const childDelay = this._delay + (this._stagger * i);
154
+ requestAnimationFrame(() => {
155
+ children[i].style.transition = `opacity ${this._duration}ms ${this._easing} ${childDelay}ms, transform ${this._duration}ms ${this._easing} ${childDelay}ms`;
156
+ children[i].style.opacity = '1';
157
+ children[i].style.transform = 'translate(0) scale(1)';
158
+ });
159
+ }
160
+ }
161
+
162
+ /* ------------------------------------------------------------------ */
163
+ /* Esconder (quando once: false) */
164
+ /* ------------------------------------------------------------------ */
165
+
166
+ _hide() {
167
+ this.hasRevealed = false;
168
+ const content = this.querySelector('.rk-reveal__content');
169
+ if (!content) return;
170
+
171
+ this.classList.remove('rk-reveal--visible');
172
+ this._applyInitialState();
173
+
174
+ /* Reabilitar transition apos resetar */
175
+ requestAnimationFrame(() => {
176
+ content.style.transition = `opacity ${this._duration}ms ${this._easing}, transform ${this._duration}ms ${this._easing}`;
177
+ if (this._stagger > 0) {
178
+ const children = content.children;
179
+ for (let i = 0; i < children.length; i++) {
180
+ children[i].style.transition = `opacity ${this._duration}ms ${this._easing}, transform ${this._duration}ms ${this._easing}`;
181
+ }
182
+ }
183
+ });
184
+ }
185
+
186
+ /* ------------------------------------------------------------------ */
187
+ /* Revelacao imediata (reduced motion / fallback) */
188
+ /* ------------------------------------------------------------------ */
189
+
190
+ _revealImmediate() {
191
+ const content = this.querySelector('.rk-reveal__content');
192
+ if (!content) return;
193
+
194
+ content.style.opacity = '1';
195
+ content.style.transform = 'none';
196
+ content.style.transition = 'none';
197
+
198
+ if (this._stagger > 0) {
199
+ const children = content.children;
200
+ for (let i = 0; i < children.length; i++) {
201
+ children[i].style.opacity = '1';
202
+ children[i].style.transform = 'none';
203
+ children[i].style.transition = 'none';
204
+ }
205
+ }
206
+
207
+ this.classList.add('rk-reveal--visible');
208
+ }
209
+ }
210
+
211
+ customElements.define('rk-reveal-element', RkRevealElement);
212
+ }
@@ -0,0 +1,233 @@
1
+ {% comment %}
2
+ Block: RK Heading
3
+ Renders a heading element using rk-typography snippet.
4
+ Fully featured with size presets, custom styling, and spacing controls.
5
+ {% endcomment %}
6
+
7
+ {%- liquid
8
+ assign bs = block.settings
9
+ assign el_tag = bs.tag | default: 'h2'
10
+ assign el_size = bs.size | default: '2xl'
11
+ assign el_text = bs.text
12
+ -%}
13
+
14
+ <rk-heading-block
15
+ id="block-{{ block.id }}"
16
+ {{ block.shopify_attributes }}
17
+ class="rk-block rk-block--heading rk-spacing-style"
18
+ style="
19
+ {% render 'rk-spacing-style', settings: bs %}
20
+ {% if bs.custom_color != blank %}--rk-heading-color: {{ bs.custom_color }};{% endif %}
21
+ {% if bs.max_width != 'none' %}--rk-heading-max-width: {{ bs.max_width }};{% endif %}
22
+ {% if bs.line_height != 'default' %}--rk-heading-line-height: var(--rk-line-height-{{ bs.line_height }});{% endif %}
23
+ {% if bs.letter_spacing != 'normal' %}--rk-heading-letter-spacing: var(--rk-letter-spacing-{{ bs.letter_spacing }});{% endif %}
24
+ "
25
+ >
26
+ {% render 'rk-typography',
27
+ text: el_text,
28
+ tag: el_tag,
29
+ size: el_size,
30
+ align: bs.alignment,
31
+ bold: bs.bold,
32
+ muted: bs.muted,
33
+ uppercase: bs.uppercase,
34
+ custom_class: 'rk-block__heading'
35
+ %}
36
+ </rk-heading-block>
37
+
38
+ {% stylesheet %}
39
+ .rk-block--heading .rk-block__heading {
40
+ margin: 0;
41
+ color: var(--rk-heading-color, inherit);
42
+ max-width: var(--rk-heading-max-width, none);
43
+ line-height: var(--rk-heading-line-height, var(--rk-line-height-tight));
44
+ letter-spacing: var(--rk-heading-letter-spacing, normal);
45
+ text-wrap: var(--rk-heading-wrap, pretty);
46
+ }
47
+ {% endstylesheet %}
48
+
49
+ {% schema %}
50
+ {
51
+ "name": "RK | Título",
52
+ "tag": null,
53
+ "settings": [
54
+ {
55
+ "type": "text",
56
+ "id": "text",
57
+ "label": "Texto",
58
+ "default": "Seu título aqui"
59
+ },
60
+ {
61
+ "type": "header",
62
+ "content": "Tipografia"
63
+ },
64
+ {
65
+ "type": "select",
66
+ "id": "tag",
67
+ "label": "Tag HTML",
68
+ "options": [
69
+ { "value": "h1", "label": "H1" },
70
+ { "value": "h2", "label": "H2" },
71
+ { "value": "h3", "label": "H3" },
72
+ { "value": "h4", "label": "H4" },
73
+ { "value": "h5", "label": "H5" },
74
+ { "value": "h6", "label": "H6" },
75
+ { "value": "p", "label": "Parágrafo" },
76
+ { "value": "span", "label": "Span" }
77
+ ],
78
+ "default": "h2",
79
+ "info": "Define a semântica HTML. O tamanho visual é controlado separadamente."
80
+ },
81
+ {
82
+ "type": "select",
83
+ "id": "size",
84
+ "label": "Tamanho Visual",
85
+ "options": [
86
+ { "value": "xs", "label": "XS (12px)" },
87
+ { "value": "sm", "label": "SM (14px)" },
88
+ { "value": "md", "label": "MD (16px)" },
89
+ { "value": "lg", "label": "LG (18px)" },
90
+ { "value": "xl", "label": "XL (20px)" },
91
+ { "value": "2xl", "label": "2XL (24px)" },
92
+ { "value": "3xl", "label": "3XL (30px)" },
93
+ { "value": "4xl", "label": "4XL (36px)" }
94
+ ],
95
+ "default": "2xl"
96
+ },
97
+ {
98
+ "type": "checkbox",
99
+ "id": "bold",
100
+ "label": "Negrito",
101
+ "default": true
102
+ },
103
+ {
104
+ "type": "checkbox",
105
+ "id": "uppercase",
106
+ "label": "Caixa Alta",
107
+ "default": false
108
+ },
109
+ {
110
+ "type": "checkbox",
111
+ "id": "muted",
112
+ "label": "Cor Suave (muted)",
113
+ "default": false
114
+ },
115
+ {
116
+ "type": "select",
117
+ "id": "line_height",
118
+ "label": "Altura da Linha",
119
+ "options": [
120
+ { "value": "default", "label": "Padrão" },
121
+ { "value": "tight", "label": "Compacto" },
122
+ { "value": "normal", "label": "Normal" },
123
+ { "value": "relaxed", "label": "Espaçado" }
124
+ ],
125
+ "default": "default"
126
+ },
127
+ {
128
+ "type": "select",
129
+ "id": "letter_spacing",
130
+ "label": "Espaçamento entre Letras",
131
+ "options": [
132
+ { "value": "normal", "label": "Normal" },
133
+ { "value": "tight", "label": "Compacto" },
134
+ { "value": "wide", "label": "Espaçado" }
135
+ ],
136
+ "default": "normal"
137
+ },
138
+ {
139
+ "type": "header",
140
+ "content": "Layout"
141
+ },
142
+ {
143
+ "type": "select",
144
+ "id": "alignment",
145
+ "label": "Alinhamento",
146
+ "options": [
147
+ { "value": "left", "label": "Esquerda" },
148
+ { "value": "center", "label": "Centro" },
149
+ { "value": "right", "label": "Direita" }
150
+ ],
151
+ "default": "left"
152
+ },
153
+ {
154
+ "type": "select",
155
+ "id": "max_width",
156
+ "label": "Largura Máxima",
157
+ "options": [
158
+ { "value": "none", "label": "Sem limite" },
159
+ { "value": "20ch", "label": "Curta (20ch)" },
160
+ { "value": "30ch", "label": "Média (30ch)" },
161
+ { "value": "40ch", "label": "Longa (40ch)" }
162
+ ],
163
+ "default": "none"
164
+ },
165
+ {
166
+ "type": "header",
167
+ "content": "Cor"
168
+ },
169
+ {
170
+ "type": "color",
171
+ "id": "custom_color",
172
+ "label": "Cor Personalizada",
173
+ "info": "Deixe vazio para herdar a cor do contexto."
174
+ },
175
+ {
176
+ "type": "header",
177
+ "content": "Espaçamento"
178
+ },
179
+ {
180
+ "type": "range",
181
+ "id": "padding-block-start",
182
+ "label": "Topo",
183
+ "min": 0,
184
+ "max": 100,
185
+ "step": 2,
186
+ "unit": "px",
187
+ "default": 0
188
+ },
189
+ {
190
+ "type": "range",
191
+ "id": "padding-block-end",
192
+ "label": "Base",
193
+ "min": 0,
194
+ "max": 100,
195
+ "step": 2,
196
+ "unit": "px",
197
+ "default": 0
198
+ },
199
+ {
200
+ "type": "range",
201
+ "id": "padding-inline-start",
202
+ "label": "Esquerda",
203
+ "min": 0,
204
+ "max": 100,
205
+ "step": 2,
206
+ "unit": "px",
207
+ "default": 0
208
+ },
209
+ {
210
+ "type": "range",
211
+ "id": "padding-inline-end",
212
+ "label": "Direita",
213
+ "min": 0,
214
+ "max": 100,
215
+ "step": 2,
216
+ "unit": "px",
217
+ "default": 0
218
+ }
219
+ ],
220
+ "presets": [
221
+ {
222
+ "name": "RK | Título",
223
+ "category": "Rook UI",
224
+ "settings": {
225
+ "text": "Seu título aqui",
226
+ "tag": "h2",
227
+ "size": "2xl",
228
+ "bold": true
229
+ }
230
+ }
231
+ ]
232
+ }
233
+ {% endschema %}