@gemboss/gem-framework 0.18.16 → 0.20.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.
- package/README.md +109 -1
- package/dist/SOURCE.json +3 -3
- package/dist/gem-framework.css +23 -1
- package/dist/gem-runtime.js +97 -54
- package/dist/index.js +2511 -44
- package/dist/motion/core.d.ts +208 -0
- package/dist/motion/core.js +1 -0
- package/dist/motion/orbit.d.ts +4 -0
- package/dist/motion/orbit.js +1 -0
- package/dist/motion/quick-view.d.ts +4 -0
- package/dist/motion/quick-view.js +1 -0
- package/dist/motion/rail.d.ts +5 -0
- package/dist/motion/rail.js +1 -0
- package/dist/orbit-base.css +39 -0
- package/dist/plugins/before_after.js +1 -1
- package/dist/plugins/bundle.js +1 -1
- package/dist/plugins/carousel.js +1 -1
- package/dist/plugins/cart-drawer.js +1 -1
- package/dist/plugins/context-adapter.js +1 -1
- package/dist/plugins/countdown.js +1 -1
- package/dist/plugins/delivery-estimate.d.ts +9 -0
- package/dist/plugins/delivery-estimate.js +1 -0
- package/dist/plugins/disclosure.js +1 -1
- package/dist/plugins/hero-slideshow.js +1 -1
- package/dist/plugins/img-in.js +1 -1
- package/dist/plugins/localization.js +1 -1
- package/dist/plugins/media-gallery.js +1 -1
- package/dist/plugins/mobile-menu.js +1 -1
- package/dist/plugins/offer-engine.js +1 -1
- package/dist/plugins/orbit.d.ts +9 -0
- package/dist/plugins/orbit.js +1 -0
- package/dist/plugins/personalize.d.ts +9 -0
- package/dist/plugins/personalize.js +1 -0
- package/dist/plugins/predictive-search.js +1 -1
- package/dist/plugins/print-preview.d.ts +9 -0
- package/dist/plugins/print-preview.js +1 -0
- package/dist/plugins/promo-popup.js +1 -1
- package/dist/plugins/quantity-input.js +1 -1
- package/dist/plugins/quick-view.js +1 -69
- package/dist/plugins/rail.d.ts +9 -0
- package/dist/plugins/rail.js +1 -0
- package/dist/plugins/recently-viewed.js +1 -1
- package/dist/plugins/reveal-on-scroll.js +1 -1
- package/dist/plugins/reviews.js +1 -1
- package/dist/plugins/runtime-slot-loader.js +1 -1
- package/dist/plugins/seasonal-effects.js +1 -1
- package/dist/plugins/size-guide.d.ts +9 -0
- package/dist/plugins/size-guide.js +38 -0
- package/dist/plugins/sticky-atc.js +1 -1
- package/dist/plugins/tabs.js +1 -1
- package/dist/plugins/variant-selects.js +1 -1
- package/dist/pod/core.d.ts +26 -0
- package/dist/pod/core.js +1 -0
- package/dist/pod/delivery-estimate.d.ts +28 -0
- package/dist/pod/delivery-estimate.js +1 -0
- package/dist/pod/personalize.d.ts +25 -0
- package/dist/pod/personalize.js +1 -0
- package/dist/pod/print-preview.d.ts +8 -0
- package/dist/pod/print-preview.js +1 -0
- package/dist/pod-buybox.css +55 -0
- package/dist/popup-shell.css +15 -0
- package/dist/primitives.css +4 -1
- package/dist/rail-base.css +48 -0
- package/dist/runtime-core.d.ts +2 -0
- package/dist/runtime-core.js +26 -15
- package/dist/section-gate.js +2522 -36
- package/dist/tokens.css +4 -0
- package/package.json +20 -1
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ gem-framework của GemTheme đóng thành package, cho các repo **ngoài** mon
|
|
|
9
9
|
| `…/sections` | Các hàm `headless.tsx` được import: `elementOverrides`, `imageSrcSet`, `subscriptionPlans`, `defaultPlan`; từ 0.15.0 thêm bộ đọc nhấn trong câu `inlineMarkRuns`, `phraseMarkRuns`, `markClassName`, `inlineMarkText`, `safeMarkHref`, `MARK_KINDS`; từ 0.16.0 thêm giá thẻ sản phẩm `cardPrice` (`card-price.ts`: nhiều giá thì "From" + giá thấp nhất, Sale chỉ khi biến thể mua được đang giảm, hết hàng, biến thể mà giá nói tới). `product-card-grid` quick-view-grid và featured từ section-library 1.39.6 import chúng: đồng bộ hai section đó khi đã lên 0.16.0 | `packages/gem-sections/src` |
|
|
10
10
|
| `…/section-gate` | `validateSectionArtifacts`: ĐÚNG bộ kiểm section gemtheme dùng khi duyệt. Từ 0.3.0 thêm ba cổng của thư viện cho MỘT section: `layoutKnobCheck` (+ `LAYOUT_ATTRS`), `slotContractDrift` (+ `slotsReadBy`, `slotSourceFromElementTree`), `cssPairingOrphans` (+ `classesStyledBy`, truyền CSS từ `createGemThemeCss`). Từ 0.3.2 thêm hai cổng pháp lý: `fabricatedProofInSource` / `fabricatedProofInSchema` (điểm, số lượt, người, lời hứa bịa) và `inventedTestimonyIn` (hằng DEMO_*_QUOTE/REVIEW… làm giá trị khi thiếu dữ liệu). Từ 0.3.3 hai cổng này đọc được tiếng Việt ("4,8/5", "đã bán", "miễn phí vận chuyển", "199k", cả chữ NFD), tra giá trị của `DEMO.<key>` ở mọi chỗ đọc nó (`??`, `\|\|`, toán tử ba ngôi, dòng bị Prettier ngắt), và bỏ qua số lượng hiển thị (`reviews_to_show`…). Nâng từ 0.3.2 lên có thể làm đỏ section trước đó xanh: đó là lời khẳng định nằm trong DEMO. Từ 0.3.5 bắt thêm chữ ký người Việt (tên kèm nơi chốn như "Chị Lan, Hà Nội", hoặc họ + Thị/Văn như "Nguyễn Thị Hoa"; tiêu đề viết hoa từng chữ thì không), điểm sao ("4,9 ★"), `rating: 4.9` / `reviewCount: "1.284"` trong mảng sản phẩm mẫu, và hằng demo ở nhánh else của toán tử ba ngôi (`bound ? [] : DEMO_REVIEWS`). Mỗi thứ bịa chỉ báo MỘT lần: tại dòng DEMO nếu dòng đó tự lộ, không thì tại chỗ đọc. "đồng hồ" không còn bị đọc là giá. Từ 0.3.6 bắt lời hứa có thời hạn đứng sau từ khoá ("Dùng thử 30 ngày", "Bảo hành 12 tháng", "Nhận trong 2–4 ngày", "Giao 2 giờ") và "hoàn tiền" không kèm số; "Chính sách hoàn tiền" vẫn là nhãn. Từ 0.3.7 câu hỏi (kết thúc bằng "?") không bao giờ là lời hứa, "không hợp lệ"/số bước/nhịp giao không bị báo, và bắt thêm "trong vòng N ngày", "Giao hàng 2–4 ngày", "Bảo hành 1 đổi 1…", "Try it free for 30 days", "Ships in 24h". Từ 0.3.8 xuất thêm `claimIn` (câu hỏi "một chuỗi này có phải lời khẳng định, người bịa, hay lời hứa?"). Từ 0.3.9 bắt "cancel anytime", "guaranteed delivery", "huỷ bất cứ lúc nào" (chính sách huỷ gói và giao hàng). Từ 0.3.10 lời từ chối ("cannot be canceled", "Không thể hủy") không bị báo, "giao dịch/giao diện" không phải giao hàng, "Delivery not guaranteed" là lời miễn trừ, một chuỗi chỉ gồm con số viết tắt ("199k", "12k+") mang nhãn trung tính "hardcoded figure" (không phân biệt được giá với số đếm), và `stringLiteralsIn(line)` thay cho hằng `STRING_LITERAL` (đã gỡ: một regex `/g` dùng chung rò `lastIndex`; 0 chỗ dùng ở gemboss/gemfactory/gemsite). Từ 0.3.11 "Huỷ bỏ lọc…" và "Hủy đơn, gọi chúng tôi bất cứ lúc nào" không bị đọc là chính sách huỷ; "huỷ đăng ký" là bỏ nhận tin (như "Unsubscribe anytime"); `SOURCE.json` ghi `gemtheme_dirty`, và `npm pack` từ chối khi ĐẦU VÀO của gói (`packages/`, gói này trừ `package.json`, `scripts/lib/`) có thay đổi chưa commit hoặc git không trả lời được. Từ 0.3.12 bắt lời hứa hoàn tiền không có chữ "hoàn tiền" liền nhau ("nhận lại toàn bộ tiền", "Hoàn lại 100% tiền", "Trả lại tiền nếu không hài lòng"); lời từ chối ("Không được hoàn lại tiền") và tiền thừa/thối khi thanh toán không bị bắt Từ 0.4.0: `demoOutsidePreview(tsx)` (chữ mẫu DEMO chỉ hiện trong bản xem trước, tức khi `section.demo === true`; plan xuất bản không bao giờ mang cờ đó) và schema `SectionSpec` khai trường `demo` | `packages/gem-sections/src/section-artifacts.ts`, `section-contract.ts` |
|
|
11
11
|
| `…/settings-schema` | `validateSettingsSchema` | `scripts/lib/settings-schema-contract.mjs` |
|
|
12
|
-
| `…/gem-framework.css`, `…/gem-runtime.js` | CSS token mặc định + runtime mọi plugin, dựng sẵn. `gem-runtime.js` tự chạy khi tải: đăng ký và lên lịch đủ
|
|
12
|
+
| `…/gem-framework.css`, `…/gem-runtime.js` | CSS token mặc định + runtime mọi plugin, dựng sẵn. `gem-runtime.js` tự chạy khi tải: đăng ký và lên lịch đủ 31 plugin (lazy chunk: chỉ stub) | build |
|
|
13
13
|
| `…/tokens.css`, `…/primitives.css`, `…/sections.css`, `…/popup-shell.css` | Từ 0.8.0: `gem-framework.css` cắt ba (từ 0.8.3 cắt bốn), nối lại theo đúng thứ tự đó thì bằng `gem-framework.css` từng byte (build dừng nếu không bằng). `tokens.css`: biến `:root` + núm style. `primitives.css`: reset, phần tử nền, tiện ích, mọi primitive `.gem-*`. `sections.css`: khung header/footer, lớp section chung, profile theme. `popup-shell.css` (0.8.3): khung popup `.gem-popup-shell`, chỉ trang có popup mới cần tải, đặt sau `primitives.css` và trước `sections.css` (thứ tự nối: tokens, primitives, popup-shell, sections). CSS riêng của từng section (`section-library/**/gem.css`) KHÔNG nằm trong gói | `createGemThemeCssParts` |
|
|
14
14
|
| `…/plugins/<key>` (`…/plugins/<key>.js`), `…/runtime-core` | Từ 0.8.0: mỗi plugin của runtime là một ES module, `export function mount(root = document, options?)` trả về `unmount()`, import không làm gì. Cùng dòng mã với `gem-runtime.js` (một bảng trong `runtime.ts`), chỉ khác: mọi `x.addEventListener(` thành `__gemListen(x, ` để unmount gỡ được. `runtime-core.js` là phần dùng chung: `mountPlugin`, `createGemApi`, `gemApi` | `runtime.ts`, `runtime-modules.ts`, `runtime-core.js` |
|
|
15
|
+
| `…/pod/<file>`, `…/pod-buybox.css` | Từ 0.20.0: buy box Boss Theme POD. `pod/core.js`, `pod/personalize.js`, `pod/print-preview.js`, `pod/delivery-estimate.js` (minify, kèm `.d.ts`) là mã các chunk `plugins/personalize`, `print-preview`, `delivery-estimate` import; `pod/delivery-estimate` có `deliveryEstimate` / `formatDeliveryRange` thuần cho server. `pod-buybox.css` là dáng bốn element POD lúc server vẽ, nạp cạnh element | `packages/gem-framework/src/pod/`, `pod-engine.ts` |
|
|
15
16
|
|
|
16
17
|
## Cài
|
|
17
18
|
|
|
@@ -93,6 +94,113 @@ Từ 0.8.2 mỗi module có `.d.ts` cạnh nó (`mount(root?: ParentNode | null,
|
|
|
93
94
|
|
|
94
95
|
## Nhật ký thay đổi
|
|
95
96
|
|
|
97
|
+
- **0.20.0** (30/10/2026): **buy box Boss Theme POD** (research gemboss `docs/research/2026-10-30-pod-boss-theme`): ba plugin lười
|
|
98
|
+
theo đúng khuôn chunk của motion engine (#952), cho bốn element gemsite vẽ (seam 1.0.115: `product-personalization` v2,
|
|
99
|
+
`product-print-preview`, `product-delivery-estimate`, `product-design-switcher`).
|
|
100
|
+
- **`personalize`** (`[data-gem-personalize]`, một wrapper mỗi ô; `data-gem-personalize-name|type|required|max|when|accept|max-mb|
|
|
101
|
+
endpoint|message-<lý do>`): ô bắt buộc CHẶN lệnh thêm giỏ của chính sản phẩm đó (form `/cart/add` submit, `gem:cart:add`,
|
|
102
|
+
`gem:cart:add-many`; lệnh gửi từ `window` khớp sản phẩm theo `variantId` với `data-gem-variant-id`), báo lỗi ngay dưới ô, focus
|
|
103
|
+
ô đầu, bắn `gem:personalize:blocked` và trả `gem:cart:add-failed { reason: "personalization" }` sau một nhịp; đủ thì ghi
|
|
104
|
+
`properties` (form Liquid: input ẩn `properties[<tên>]`; sự kiện: `detail.properties`, add-many: dòng của chính sản phẩm),
|
|
105
|
+
ảnh tải lên = URL + `_<tên>_upload` (khoá lưu trữ). Ô điều kiện `data-gem-personalize-when` (`Tên`, `!Tên`, `Tên = a|b`,
|
|
106
|
+
`Tên != a`): ẩn thì disabled, không bắt buộc, không ghi. Bộ đếm theo max_length. Tải ảnh: kiểm `accept` / `max_mb` ở trình
|
|
107
|
+
duyệt, rồi HAI BƯỚC (hợp đồng gemsite 30/10, thân hàm serverless tối đa 4,5 MB): POST JSON `{ type, size, max_mb }` tới endpoint
|
|
108
|
+
→ `{ url, key, upload: { url, method, headers } }`, PUT file thẳng tới `upload.url` đúng `headers`, không credentials; 415 / 413
|
|
109
|
+
báo loại / dung lượng, 404 hoặc không có endpoint = ô không dùng được (ẩn, không bao giờ chặn); host không trả `upload` thì
|
|
110
|
+
POST multipart file như cũ. Đang tải thì chặn thêm giỏ. Mọi thay đổi bắn `gem:personalize:change { name, value, type, color,
|
|
111
|
+
preview }` từ ô. Chữ cho người mua là MỘT bộ: `PERSONALIZE_MESSAGES` (required = "Fill this in to add to cart."), gemsite
|
|
112
|
+
import cùng bộ này.
|
|
113
|
+
- **`print-preview`** (`[data-gem-print-preview]`): theo `gem:personalize:change` trong cùng sản phẩm, vẽ chữ (`textContent`,
|
|
114
|
+
co bằng `transform` khi tràn) và ảnh khách trong vùng in `--gem-print-preview-x|y|w|h` (% ảnh) trên ảnh hiện tại; màu hex hoặc
|
|
115
|
+
`from:<thuộc tính>` theo ô màu (chỉ hex tới được CSS). Chưa nhập thì không vẽ gì. Nạp sau khi khách đã gõ vẫn vẽ đúng.
|
|
116
|
+
- **`delivery-estimate`** (`[data-gem-delivery-estimate]`): tính lại "Đặt · Gửi · Nhận" theo ngày của người mua (ngày làm việc,
|
|
117
|
+
giờ chốt theo múi giờ store), `Intl.DateTimeFormat` theo `lang` của trang; `deliveryEstimate()` / `formatDeliveryRange()`
|
|
118
|
+
thuần, server dùng chung được (`@gemboss/gem-framework/pod/delivery-estimate`).
|
|
119
|
+
- **Lười:** `gem-runtime.js` chỉ thêm ba dòng stub (bộ nạp là của motion engine); chunk `plugins/<key>.js` + `pod/*.js` (minify;
|
|
120
|
+
personalize và print-preview chung `pod/core.js`) chỉ tải khi trang có hook. **CSS:** `pod-buybox.css` (`GEM_POD_BUYBOX_CSS`,
|
|
121
|
+
dáng bốn element lúc server vẽ, đi cùng element) và CSS lúc chạy trong chunk; không dòng nào vào `gem-framework.css`. Lớp
|
|
122
|
+
`gem-el-product-personalization*`, `-print-preview*`, `-delivery-estimate*`, `-design-switcher*` đăng ký nhóm `pod`.
|
|
123
|
+
Plugin mới `personalize`, `print-preview`, `delivery-estimate` (31 plugin).
|
|
124
|
+
|
|
125
|
+
- **0.19.0** (30/10/2026): **motion engine**, nền tảng chuyển động cho section (Chris chọn 30/10: "Motion engine trong gem-framework",
|
|
126
|
+
như engine bundle #41 và reviews #44), port từ gemmotion. Ba effect đầu tiên: `orbit`, `rail`, `quick-view`.
|
|
127
|
+
- **Nền tảng** (`dist/motion/core.js`, `@gemboss/gem-framework/motion/core`): `defineEffect({ key, hook, settings, renderer, tier,
|
|
128
|
+
fallback, budget, mount })` + `checkEffect` cho cổng; schema settings (kiểu, khoảng, mặc định, nhãn) đọc từ
|
|
129
|
+
`data-gem-<key>-<tên>`, setting `css` soi sang `--gem-<key>-<tên>`; MỘT vòng requestAnimationFrame cho mọi effect của trang
|
|
130
|
+
(ngủ khi không gì chuyển động, ngoài màn hình, tab ẩn; giảm chuyển động thì vẽ ngay, không xin khung nào), chặn khung chậm
|
|
131
|
+
(> 20 ms, 30 khung liền) hạ mọi effect xuống `light`; renderer `dom` có sẵn, `canvas2d` / `video` / `webgl` là interface +
|
|
132
|
+
`defineRenderer(key, () => import(...))` (chunk lười, không nạp được thì giữ fallback tĩnh); bậc `static` / `light` / `full`
|
|
133
|
+
(giảm chuyển động, con trỏ thô, save-data, ≤ 2 GB); dịch vụ dùng chung: `ringPose`, `follow`, `spring`, `throwTarget`,
|
|
134
|
+
`scrollProgress`, kéo / ném, bánh xe, phím mũi tên, FLIP, quan sát hiển thị và kích thước, tải ảnh, `priceText`.
|
|
135
|
+
- **`orbit`** (`[data-gem-orbit]`): vòng nghiêng kéo / ném, nút ‹ ›, ← →, bánh xe ngang; bấm món bên thì quay tới, bấm món trước
|
|
136
|
+
thì mở quick view. **`rail`** (`[data-gem-rail]`): giá treo, món gần con trỏ xoay nghiêng → chính diện (pair / sprite / flip),
|
|
137
|
+
móc đung đưa, dưới 700 px là hàng vuốt. **`quick-view`**: lõi, mọi phần tử mang `data-gem-product` +
|
|
138
|
+
`data-gem-quick-view-trigger` mở nó; ảnh bay ra (FLIP) và về, ‹ › theo host, màu / size, `gem:cart:add
|
|
139
|
+
{ variantId: String(id), quantity: 1 }`; dialog dựng trong gốc host (khung popup-shell). Hợp đồng surface cũ
|
|
140
|
+
(`[data-gem-quick-view]` + `[data-gem-quick-view-surface]`) giữ nguyên trong chunk.
|
|
141
|
+
- Effect NHẬN markup server vẽ, không dựng lại chữ hay ảnh: giá theo `price_text` (cardPrice) nguyên văn, dự phòng `price` (cent)
|
|
142
|
+
+ `data-gem-money-format`.
|
|
143
|
+
- **Lười:** `gem-runtime.js` chỉ mang một dòng stub mỗi plugin + bộ nạp; chunk `plugins/<key>.js` (đã minify) chỉ tải khi trang có
|
|
144
|
+
hook (orbit / rail khi tới gần màn hình, quick-view khi chạm trigger lần đầu hoặc lúc rảnh); hook đến muộn (canvas, editor,
|
|
145
|
+
điều hướng phía client) bắt bằng MutationObserver + `shopify:section:load`. Trang không có hook: 0 byte JS motion.
|
|
146
|
+
- **CSS chia hai**, không dòng nào vào `gem-framework.css`: (a) CSS nền `orbit-base.css` / `rail-base.css` (`GEM_ORBIT_BASE_CSS`,
|
|
147
|
+
`GEM_RAIL_BASE_CSS`) đi cùng section, vẽ tư thế đầu bằng `sin()` / `cos()` nên gắn plugin không xê dịch gì; (b) CSS lúc chạy
|
|
148
|
+
nằm trong chunk, adopt khi mount. Lớp `gem-orbit*`, `gem-rail*`, `gem-turn*`, `gem-quick-view__*` đăng ký nhóm `motion`.
|
|
149
|
+
- `runtime-core.js`: `ResizeObserver` vào sổ, `onUnmount(fn)` cho plugin. Plugin mới `orbit`, `rail` (28 plugin);
|
|
150
|
+
`quick-view` load `idle` (chỉ là stub).
|
|
151
|
+
|
|
152
|
+
- **0.18.22** (29/10/2026): `hasExternalDependency`, luật duy nhất cho `external_dependency`, đọc ở `validateGemFrameworkArtifact`,
|
|
153
|
+
test library-files của gem-sections và `validatePerformanceBudget` của validators (trước đó mỗi nơi chép regex riêng). Miễn:
|
|
154
|
+
URL `www.w3.org` như cũ nhưng chỉ cắt ký tự của chính URL (không còn nuốt phần phía sau), và endpoint share-intent của Facebook,
|
|
155
|
+
X, Pinterest, WhatsApp, Threads tới dấu `?` ở đúng hai dạng của một link chia sẻ: `<a href>`, hoặc giá trị object literal có
|
|
156
|
+
key là tên mạng đó. Mọi chỗ khác (kể cả `.href =`, vì `<link>`/`<use>` tải href) vẫn đỏ; phần sau `?` vẫn bị đọc. Lint bắt
|
|
157
|
+
phụ thuộc lỡ tay, không phải sandbox: URL đi qua biến không được lần theo.
|
|
158
|
+
- **0.18.21** (26/10/2026): bậc chữ `text-xs`, P3 của thang GemPages v7 (Chris duyệt 26/10: H1–H6, P1–P3 + Custom; P1 là `body`,
|
|
159
|
+
P2 là `text-sm`). Số 0.18.20 để lại cho #893 đang mở.
|
|
160
|
+
- `--gem-text-xs: calc(var(--gem-type-base) / (var(--gem-type-scale) * var(--gem-type-scale)))`: base ÷ scale², một bậc dưới
|
|
161
|
+
`--gem-text-sm`, 10,75px ở mặc định (16px, 1.22). Theo `--gem-type-base` / `--gem-type-scale` như cả thang, nên style pack và
|
|
162
|
+
base điện thoại kéo nó theo.
|
|
163
|
+
- Thêm, không đổi tên, không bỏ: không giá trị nào đang vẽ thay đổi. Danh sách `type-step` (framework-names.json) và `steps` của
|
|
164
|
+
`gem_font_size` / `font_size` ở seam 1.0.110 có thêm `text-xs` đứng đầu (nhỏ → lớn).
|
|
165
|
+
- **0.18.19** (22/10/2026): độ dày nét và gạch chân (GemFactory gap #14). `2px` viết tay ở 96 section; đo cả kho 22/10:
|
|
166
|
+
phần lớn là NÉT (border / border-width ~250 chỗ, vòng `box-shadow: inset 0 0 0 2px` của thẻ đã chọn 53, vạch 2px bằng
|
|
167
|
+
height/width 44) và gạch chân (`text-decoration-thickness` 38).
|
|
168
|
+
- `--gem-stroke` (1px) và `--gem-stroke-thick` (2px), nhân `--gem-style-border` như `--gem-border-subtle/strong`: style pack
|
|
169
|
+
làm mảnh / bỏ viền thì nét của section đi theo. Tên riêng (stroke): nhóm token `border` phát `--gem-border-<khoá>`.
|
|
170
|
+
- `--gem-underline-thickness` (2px, KHÔNG theo knob viền: style không viền vẫn giữ gạch chân); `.gem-mark-link:hover` của
|
|
171
|
+
framework đọc biến này (mặc định 2px như cũ).
|
|
172
|
+
- Không đổi: vòng focus vẫn `--gem-focus-ring` / `--gem-focus-offset` (knob viền 0 không được làm mất focus); khoảng 2px
|
|
173
|
+
(padding / gap / margin) là `--gem-space-2xs` có từ #6. Không giá trị nào đang vẽ thay đổi.
|
|
174
|
+
- **0.18.18** (22/10/2026): hình nút của MỘT section (GemFactory gap #69). Chấm khớp nguồn (nút vuông của boticario,
|
|
175
|
+
bellavitaluxe; viên thuốc của threebirdnest…) đỏ chỉ vì nút theo bo của preset, còn section ghi `border-radius` lên
|
|
176
|
+
`.gem-button` thì nút thôi theo preset luôn (cảnh báo `button_token_shadowed` của GemFactory).
|
|
177
|
+
- `data-gem-button-shape="square|soft|pill"` trên gốc section (hoặc trên một nút) đặt `--gem-radius-button` mà nút vốn đọc:
|
|
178
|
+
square 0, soft `--gem-radius-sm`, pill 999px. Không có thuộc tính (hay `preset`) thì preset quyết như trước.
|
|
179
|
+
- Square và pill là HÌNH nên không co giãn; soft là CỠ nên theo thang bo của preset (`--gem-style-radius`). Đo trong
|
|
180
|
+
Chromium: preset nút viên thuốc → section square ra 0, soft 4px; preset nút vuông → section pill ra 999px (trước đó không
|
|
181
|
+
làm được); thang bo 0 → soft 0, pill vẫn 999px.
|
|
182
|
+
- Bộ kiểm framework: biến đặt dưới một selector CHỈ là thuộc tính `[data-gem-…]` (một knob đặt lại token cho phần tử mang nó)
|
|
183
|
+
không còn bị tính là biến nội bộ, nên section vẫn đọc `--gem-radius-button` không bị báo biến lạ. Cùng luật đó `--gem-ambient-amp`
|
|
184
|
+
(đặt dưới `[data-gem-ambient]`) nay là biến công khai. Thuộc tính trên một lớp (`.gem-row[data-…]`) vẫn là nội bộ như cũ.
|
|
185
|
+
|
|
186
|
+
- **0.18.17** (21/10/2026): nút "Bảng size" của buybox mở bảng size ở band KHÁC (GemFactory gap #49). 12 section bảng size
|
|
187
|
+
(product-details/*) đã nghe `window` `gem:size-guide:open`, nhưng behavior.js không được chạm section khác và runtime chưa có
|
|
188
|
+
gì phát sự kiện đó.
|
|
189
|
+
- Plugin `size-guide` (load `idle`: nút phải có trước khi ai bấm): nút mang `data-gem-size-guide-trigger` (rỗng, hoặc id của
|
|
190
|
+
bảng nó mở), gốc bảng size mang `data-gem-size-guide` (rỗng, hoặc id đó). Bấm nút: `gem:size-guide:open`
|
|
191
|
+
`{ sectionId, productId }` lên `window` (chỗ các bảng size nghe) và `api.emit` như mọi plugin; `productId` đọc từ
|
|
192
|
+
`data-product-id` gần nhất.
|
|
193
|
+
- Nút vẽ sẵn `hidden`, plugin chỉ hiện nó khi trang có bảng nó mở; không có runtime hay không có bảng thì nút vẫn ẩn, không
|
|
194
|
+
thành nút bấm không làm gì.
|
|
195
|
+
- `gem-popup-shell` thêm `data-gem-popup-desktop="drawer-end"`: ngăn cao hết màn bám mép cuối dòng trên desktop (phải;
|
|
196
|
+
trái dưới RTL), rộng theo `--gem-popup-drawer-width` (mặc định 640px), trượt vào bằng `--gem-motion-base` /
|
|
197
|
+
`--gem-ease-enter`, tắt trượt khi giảm chuyển động; mobile vẫn takeover / sheet. Trong canvas editor ngăn đứng yên, cao 720px.
|
|
198
|
+
- Bộ kiểm framework biết các biến của popup-shell.css (trước đó section đặt `--gem-popup-drawer-width` sẽ bị báo
|
|
199
|
+
`unknown_gem_css_variable`).
|
|
200
|
+
- Bảng size tìm trên CẢ TRANG (`document.documentElement`): gắn như module lên chính section buybox (cách gemsite chạy behavior,
|
|
201
|
+
vd. `import { mount } from "@gemboss/gem-framework/plugins/size-guide"` trong behavior.js của buybox, như sticky-atc), `document`
|
|
202
|
+
của plugin chỉ thấy bên trong section đó, còn bảng size luôn ở band khác. Nút thì vẫn chỉ trong phần được gắn.
|
|
203
|
+
|
|
96
204
|
- **0.18.16** (20/10/2026): plugin `bundle`, mô hình bộ tự chọn BYOB (GemFactory gap #41). 9 section bundle-builder của GemFactory
|
|
97
205
|
(slot-sidebar, box-size-first, routine-kit, tier-bar, system-list, reward-tiles, silhouette-tray, gift-box, grid-milestone-panel)
|
|
98
206
|
mỗi cái chép một khối "LÕI BYOB" trong behavior.js. Phần chúng VẼ khác nhau nhiều (ô + tab, cỡ hộp + độ đa dạng, thang bậc + bộ
|
package/dist/SOURCE.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@gemboss/gem-framework",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gemtheme_sha": "
|
|
3
|
+
"version": "0.20.0",
|
|
4
|
+
"gemtheme_sha": "ec9e8ce0438f487143b562d9f8c8809adf547de0",
|
|
5
5
|
"gemtheme_dirty": false,
|
|
6
|
-
"framework_version": "0.
|
|
6
|
+
"framework_version": "0.20.0"
|
|
7
7
|
}
|
package/dist/gem-framework.css
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
--gem-morphology-policy-tone: standard;
|
|
63
63
|
--gem-type-base: 1rem;
|
|
64
64
|
--gem-type-scale: 1.22;
|
|
65
|
+
--gem-text-xs: calc(var(--gem-type-base) / (var(--gem-type-scale) * var(--gem-type-scale)));
|
|
65
66
|
--gem-text-sm: calc(var(--gem-type-base) / var(--gem-type-scale));
|
|
66
67
|
--gem-body: var(--gem-type-base);
|
|
67
68
|
--gem-h6: calc(var(--gem-type-base) * var(--gem-type-scale));
|
|
@@ -73,6 +74,9 @@
|
|
|
73
74
|
--gem-focus-ring: 2px solid var(--gem-color-accent);
|
|
74
75
|
--gem-tap-min: 44px;
|
|
75
76
|
--gem-focus-offset: 3px;
|
|
77
|
+
--gem-stroke: calc(1px * var(--gem-style-border, 1));
|
|
78
|
+
--gem-stroke-thick: calc(2px * var(--gem-style-border, 1));
|
|
79
|
+
--gem-underline-thickness: 2px;
|
|
76
80
|
}
|
|
77
81
|
:where(:root [data-gem-section-background='dark'], :root [data-gem-section-background='accent']) {
|
|
78
82
|
--gem-border-subtle: calc(1px * var(--gem-style-border, 1)) solid var(--gem-color-border);
|
|
@@ -195,6 +199,9 @@ a {
|
|
|
195
199
|
cursor: not-allowed;
|
|
196
200
|
opacity: 0.55;
|
|
197
201
|
}
|
|
202
|
+
[data-gem-button-shape='square'] { --gem-radius-button: 0px; }
|
|
203
|
+
[data-gem-button-shape='soft'] { --gem-radius-button: var(--gem-radius-sm); }
|
|
204
|
+
[data-gem-button-shape='pill'] { --gem-radius-button: 999px; }
|
|
198
205
|
.gem-input:disabled, .gem-select:disabled, .gem-textarea:disabled, .gem-quantity-input__button:disabled {
|
|
199
206
|
cursor: not-allowed;
|
|
200
207
|
opacity: 0.6;
|
|
@@ -1160,7 +1167,7 @@ a {
|
|
|
1160
1167
|
:where(.gem-mark--alert) { color: var(--gem-color-danger, #b42318); }
|
|
1161
1168
|
:where(.gem-mark--highlight.gem-mark--accent), :where(.gem-mark--highlight.gem-mark--alert) { color: var(--gem-mark-highlight-ink, #1f1d18); }
|
|
1162
1169
|
.gem-mark-link { color: var(--gem-color-accent); text-decoration-line: underline; text-decoration-thickness: from-font; text-underline-offset: 0.18em; }
|
|
1163
|
-
.gem-mark-link:hover { text-decoration-thickness: 2px; }
|
|
1170
|
+
.gem-mark-link:hover { text-decoration-thickness: var(--gem-underline-thickness, 2px); }
|
|
1164
1171
|
.gem-mark-link.gem-mark--highlight, .gem-mark--highlight .gem-mark-link { color: var(--gem-mark-highlight-ink, #1f1d18); }
|
|
1165
1172
|
@keyframes gem-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(calc(-1 * var(--gem-ambient-amp, 7px))); } }
|
|
1166
1173
|
@keyframes gem-sheen { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
|
|
@@ -1626,6 +1633,19 @@ a {
|
|
|
1626
1633
|
.gem-popup-shell[data-gem-popup-media='background']:is([data-gem-popup-desktop='split-left'], [data-gem-popup-desktop='split-right'], [data-gem-popup-desktop='card']) .gem-popup-shell__close { color: var(--gem-color-text); }
|
|
1627
1634
|
.gem-popup-shell[data-gem-popup-media='background']:is([data-gem-popup-desktop='split-left'], [data-gem-popup-desktop='split-right'], [data-gem-popup-desktop='card']) .gem-popup-shell__media::after { content: none; }
|
|
1628
1635
|
}
|
|
1636
|
+
@keyframes gem-popup-drawer-in { from { translate: calc(100% * var(--gem-popup-drawer-dir, 1)) 0; } }
|
|
1637
|
+
.gem-popup-shell__dialog:dir(rtl) { --gem-popup-drawer-dir: -1; }
|
|
1638
|
+
@media (min-width: 750px) {
|
|
1639
|
+
.gem-popup-shell[data-gem-popup-desktop='drawer-end'] .gem-popup-shell__dialog {
|
|
1640
|
+
width: min(var(--gem-popup-drawer-width, 640px), 100vw); max-width: 100vw; height: 100dvh; max-height: 100dvh;
|
|
1641
|
+
margin-block: 0; margin-inline: auto 0; border-radius: 0;
|
|
1642
|
+
animation: gem-popup-drawer-in var(--gem-motion-base, 180ms) var(--gem-ease-enter, ease-out);
|
|
1643
|
+
}
|
|
1644
|
+
.gem-popup-shell[data-gem-popup-desktop='drawer-end'] .gem-popup-shell__frame { grid-template-columns: 1fr; align-content: start; min-height: 100%; }
|
|
1645
|
+
.gem-popup-shell[data-gem-popup-desktop='drawer-end'] .gem-popup-shell__media { max-height: 240px; }
|
|
1646
|
+
.gem-popup-shell[data-gem-popup-desktop='drawer-end'] .gem-popup-shell__panel { justify-content: flex-start; align-items: stretch; text-align: start; }
|
|
1647
|
+
}
|
|
1648
|
+
@media (prefers-reduced-motion: reduce) { .gem-popup-shell[data-gem-popup-desktop='drawer-end'] .gem-popup-shell__dialog { animation: none; } }
|
|
1629
1649
|
@media (max-width: 749px) {
|
|
1630
1650
|
.gem-popup-shell__dialog { width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh; margin: 0; border-radius: 0; }
|
|
1631
1651
|
.gem-popup-shell[data-gem-popup-mobile='sheet'] .gem-popup-shell__dialog { height: auto; max-height: 88dvh; margin: auto 0 0; border-radius: var(--gem-radius-lg) var(--gem-radius-lg) 0 0; }
|
|
@@ -1649,6 +1669,8 @@ a {
|
|
|
1649
1669
|
@media (min-width: 750px) {
|
|
1650
1670
|
:root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'][data-gem-popup-desktop='fullscreen'] { padding: 0; }
|
|
1651
1671
|
:root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'][data-gem-popup-desktop='fullscreen'] .gem-popup-shell__dialog { height: 720px; max-height: none; }
|
|
1672
|
+
:root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'][data-gem-popup-desktop='drawer-end'] { padding-inline: 0; }
|
|
1673
|
+
:root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'][data-gem-popup-desktop='drawer-end'] .gem-popup-shell__dialog { height: 720px; max-height: none; animation: none; }
|
|
1652
1674
|
}
|
|
1653
1675
|
@media (max-width: 749px) {
|
|
1654
1676
|
:root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'] { padding: 0; }
|
package/dist/gem-runtime.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
(() => {
|
|
2
|
-
const config = {"planId":"gem-framework-package","base":"gem-native","dawnAdapter":false,"plugins":[{"key":"cart-drawer","loadStrategy":"interaction","dependsOn":[]},{"key":"mobile-menu","loadStrategy":"interaction","dependsOn":[]},{"key":"quantity-input","loadStrategy":"interaction","dependsOn":[]},{"key":"variant-selects","loadStrategy":"interaction","dependsOn":[]},{"key":"predictive-search","loadStrategy":"critical","dependsOn":[]},{"key":"localization","loadStrategy":"interaction","dependsOn":[]},{"key":"disclosure","loadStrategy":"interaction","dependsOn":[]},{"key":"tabs","loadStrategy":"interaction","dependsOn":[]},{"key":"media-gallery","loadStrategy":"viewport","dependsOn":[]},{"key":"carousel","loadStrategy":"viewport","dependsOn":[]},{"key":"hero-slideshow","loadStrategy":"idle","dependsOn":[]},{"key":"quick-view","loadStrategy":"
|
|
2
|
+
const config = {"planId":"gem-framework-package","base":"gem-native","dawnAdapter":false,"plugins":[{"key":"cart-drawer","loadStrategy":"interaction","dependsOn":[]},{"key":"mobile-menu","loadStrategy":"interaction","dependsOn":[]},{"key":"quantity-input","loadStrategy":"interaction","dependsOn":[]},{"key":"variant-selects","loadStrategy":"interaction","dependsOn":[]},{"key":"predictive-search","loadStrategy":"critical","dependsOn":[]},{"key":"localization","loadStrategy":"interaction","dependsOn":[]},{"key":"disclosure","loadStrategy":"interaction","dependsOn":[]},{"key":"tabs","loadStrategy":"interaction","dependsOn":[]},{"key":"media-gallery","loadStrategy":"viewport","dependsOn":[]},{"key":"carousel","loadStrategy":"viewport","dependsOn":[]},{"key":"hero-slideshow","loadStrategy":"idle","dependsOn":[]},{"key":"quick-view","loadStrategy":"idle","dependsOn":[]},{"key":"promo-popup","loadStrategy":"idle","dependsOn":[]},{"key":"before_after","loadStrategy":"viewport","dependsOn":[]},{"key":"runtime-slot-loader","loadStrategy":"idle","dependsOn":[]},{"key":"sticky-atc","loadStrategy":"viewport","dependsOn":[]},{"key":"recently-viewed","loadStrategy":"idle","dependsOn":[]},{"key":"countdown","loadStrategy":"viewport","dependsOn":[]},{"key":"reveal-on-scroll","loadStrategy":"viewport","dependsOn":[]},{"key":"img-in","loadStrategy":"critical","dependsOn":[]},{"key":"seasonal-effects","loadStrategy":"idle","dependsOn":[]},{"key":"context-adapter","loadStrategy":"idle","dependsOn":[]},{"key":"offer-engine","loadStrategy":"interaction","dependsOn":[]},{"key":"reviews","loadStrategy":"idle","dependsOn":[]},{"key":"bundle","loadStrategy":"idle","dependsOn":[]},{"key":"size-guide","loadStrategy":"idle","dependsOn":[]},{"key":"orbit","loadStrategy":"idle","dependsOn":[]},{"key":"rail","loadStrategy":"idle","dependsOn":[]},{"key":"personalize","loadStrategy":"idle","dependsOn":[]},{"key":"print-preview","loadStrategy":"idle","dependsOn":[]},{"key":"delivery-estimate","loadStrategy":"idle","dependsOn":[]}]};
|
|
3
3
|
const target = window;
|
|
4
4
|
const existing = target.GemTheme || {};
|
|
5
5
|
const listeners = existing.listeners || {};
|
|
@@ -68,6 +68,74 @@
|
|
|
68
68
|
if (readyEvent) api.emit(readyEvent, { source: 'gem-runtime' });
|
|
69
69
|
});
|
|
70
70
|
};
|
|
71
|
+
const chunkBase = (document.currentScript && document.currentScript.src) || '';
|
|
72
|
+
const chunks = {};
|
|
73
|
+
let chunkWatch = null;
|
|
74
|
+
const loadChunkModule = (chunk) => {
|
|
75
|
+
if (chunk.state) return;
|
|
76
|
+
chunk.state = 1;
|
|
77
|
+
Promise.resolve().then(() => {
|
|
78
|
+
const url = new URL('plugins/' + chunk.key + '.js', chunk.api.pluginBase || chunkBase || document.baseURI).href;
|
|
79
|
+
return chunk.api.importChunk ? chunk.api.importChunk(url) : import(url);
|
|
80
|
+
}).then((module) => {
|
|
81
|
+
chunk.module = module;
|
|
82
|
+
module.mount(document, { api: chunk.api, window: target });
|
|
83
|
+
chunk.state = 2;
|
|
84
|
+
if (chunk.replay) chunk.replay.click();
|
|
85
|
+
chunk.replay = null;
|
|
86
|
+
}, (error) => { chunk.state = 0; chunk.replay = null; chunk.api.emit('gem:plugin:error', { key: chunk.key, error: String(error) }); });
|
|
87
|
+
};
|
|
88
|
+
const armChunk = (chunk, hooks) => {
|
|
89
|
+
const load = () => loadChunkModule(chunk);
|
|
90
|
+
if (chunk.when === 'viewport') {
|
|
91
|
+
if (!target.IntersectionObserver) { load(); return; }
|
|
92
|
+
chunk.io = chunk.io || new target.IntersectionObserver((entries) => { if (entries.some((entry) => entry.isIntersecting)) { chunk.io.disconnect(); load(); } }, { rootMargin: '200px' });
|
|
93
|
+
hooks.forEach((hookEl) => chunk.io.observe(hookEl));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
hooks.forEach((hookEl) => {
|
|
97
|
+
['pointerover', 'focusin', 'touchstart'].forEach((type) => hookEl.addEventListener(type, load, { once: true, passive: true }));
|
|
98
|
+
hookEl.addEventListener('click', (event) => {
|
|
99
|
+
if (chunk.state === 2 || event.defaultPrevented) return;
|
|
100
|
+
const host = hookEl.closest && hookEl.closest('[data-gem-quick-view-host]');
|
|
101
|
+
if (host && host.gemQuickViewHost && !host.gemQuickViewHost.opens(hookEl)) return;
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
chunk.replay = hookEl;
|
|
104
|
+
load();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
if (!chunk.idle) { chunk.idle = true; onIdle(load); }
|
|
108
|
+
};
|
|
109
|
+
const scanChunk = (chunk, scope) => {
|
|
110
|
+
const hooks = Array.prototype.slice.call(scope.querySelectorAll ? scope.querySelectorAll(chunk.hook) : []);
|
|
111
|
+
if (scope !== document && scope.matches && scope.matches(chunk.hook)) hooks.unshift(scope);
|
|
112
|
+
if (!hooks.length) return false;
|
|
113
|
+
if (chunk.state === 2) hooks.forEach((hookEl) => chunk.module.mount(hookEl, { api: chunk.api, window: target }));
|
|
114
|
+
else armChunk(chunk, hooks);
|
|
115
|
+
return true;
|
|
116
|
+
};
|
|
117
|
+
const watchChunks = () => {
|
|
118
|
+
if (chunkWatch || !target.MutationObserver) return;
|
|
119
|
+
let added = [];
|
|
120
|
+
let timer = 0;
|
|
121
|
+
const flush = () => {
|
|
122
|
+
timer = 0;
|
|
123
|
+
const nodes = added;
|
|
124
|
+
added = [];
|
|
125
|
+
nodes.forEach((node) => { if (node.isConnected) Object.keys(chunks).forEach((key) => scanChunk(chunks[key], node)); });
|
|
126
|
+
};
|
|
127
|
+
chunkWatch = new target.MutationObserver((records) => {
|
|
128
|
+
records.forEach((record) => record.addedNodes.forEach((node) => { if (node.nodeType === 1) added.push(node); }));
|
|
129
|
+
if (added.length && !timer) timer = setTimeout(flush, 50);
|
|
130
|
+
});
|
|
131
|
+
chunkWatch.observe(document.documentElement, { childList: true, subtree: true });
|
|
132
|
+
if (canListen(document)) document.addEventListener('shopify:section:load', (event) => { if (event.target && event.target.nodeType === 1) Object.keys(chunks).forEach((key) => scanChunk(chunks[key], event.target)); });
|
|
133
|
+
};
|
|
134
|
+
const loadChunk = (key, hook, when, api) => {
|
|
135
|
+
const chunk = (chunks[key] = { key, hook, when, api, state: 0, module: null, replay: null });
|
|
136
|
+
watchChunks();
|
|
137
|
+
if (!scanChunk(chunk, document)) api.emit('gem:' + key + ':ready', { source: 'gem-runtime', count: 0 });
|
|
138
|
+
};
|
|
71
139
|
registerPlugin('cart-drawer', ({ api }) => {
|
|
72
140
|
const drawers = document.querySelectorAll('[data-gem-cart-drawer], [data-gem-cart-canvas], .gem-cart-drawer');
|
|
73
141
|
const cartLinks = document.querySelectorAll('[data-gem-cart-drawer-trigger], [data-gem-cart-toggle], a[href*="/cart"]');
|
|
@@ -522,59 +590,7 @@
|
|
|
522
590
|
});
|
|
523
591
|
api.emit('gem:hero-slideshow:ready', { source: 'gem-runtime', count: bound });
|
|
524
592
|
});
|
|
525
|
-
registerPlugin(
|
|
526
|
-
const triggers = document.querySelectorAll('[data-gem-quick-view], [data-gem-action="quick-view"]');
|
|
527
|
-
const surface = document.querySelector('[data-gem-quick-view-surface], .gem-quick-view, .gem-modal[data-gem-component="quick-view"]');
|
|
528
|
-
let bound = 0;
|
|
529
|
-
const close = (reason = 'close-control') => {
|
|
530
|
-
if (surface) {
|
|
531
|
-
surface.dataset.gemQuickViewState = 'closed';
|
|
532
|
-
surface.removeAttribute && surface.removeAttribute('open');
|
|
533
|
-
surface.setAttribute && surface.setAttribute('aria-hidden', 'true');
|
|
534
|
-
}
|
|
535
|
-
api.emit('gem:quick-view:close', { source: 'gem-runtime', reason });
|
|
536
|
-
};
|
|
537
|
-
const openSurface = (detail) => {
|
|
538
|
-
if (surface) {
|
|
539
|
-
surface.dataset.gemQuickViewState = 'open';
|
|
540
|
-
surface.dataset.gemProductHandle = detail.productHandle || '';
|
|
541
|
-
surface.dataset.gemProductUrl = detail.productUrl || '';
|
|
542
|
-
surface.setAttribute && surface.setAttribute('open', '');
|
|
543
|
-
surface.setAttribute && surface.setAttribute('aria-hidden', 'false');
|
|
544
|
-
surface.setAttribute && surface.setAttribute('role', 'dialog');
|
|
545
|
-
surface.setAttribute && surface.setAttribute('aria-modal', 'true');
|
|
546
|
-
const content = surface.querySelector && surface.querySelector('[data-gem-quick-view-content]');
|
|
547
|
-
if (content && content.textContent !== undefined) content.textContent = 'Quick view for ' + (detail.productHandle || 'product');
|
|
548
|
-
else if (surface.textContent !== undefined) surface.textContent = 'Quick view for ' + (detail.productHandle || 'product');
|
|
549
|
-
const closeButton = surface.querySelector && surface.querySelector('[data-gem-quick-view-close]');
|
|
550
|
-
if (closeButton && closeButton.focus) closeButton.focus();
|
|
551
|
-
}
|
|
552
|
-
api.emit('gem:quick-view:open', { source: 'gem-runtime', ...detail, hasSurface: Boolean(surface) });
|
|
553
|
-
};
|
|
554
|
-
triggers.forEach((trigger) => {
|
|
555
|
-
if (!mark(trigger, 'quick-view')) return;
|
|
556
|
-
if (!canListen(trigger)) return;
|
|
557
|
-
trigger.addEventListener('click', (event) => {
|
|
558
|
-
const productUrl = trigger.getAttribute && (trigger.getAttribute('data-gem-product-url') || trigger.getAttribute('href'));
|
|
559
|
-
const productHandle = trigger.getAttribute && trigger.getAttribute('data-gem-product-handle');
|
|
560
|
-
if (!productUrl && !productHandle) return;
|
|
561
|
-
event.preventDefault && event.preventDefault();
|
|
562
|
-
openSurface({ productUrl, productHandle });
|
|
563
|
-
});
|
|
564
|
-
bound += 1;
|
|
565
|
-
});
|
|
566
|
-
if (surface && mark(surface, 'quick-view-surface')) {
|
|
567
|
-
surface.dataset.gemQuickViewState = 'closed';
|
|
568
|
-
surface.setAttribute && surface.setAttribute('aria-hidden', 'true');
|
|
569
|
-
if (canListen(surface)) surface.addEventListener('click', (event) => {
|
|
570
|
-
const target = event.target;
|
|
571
|
-
if (target && target.closest && target.closest('[data-gem-quick-view-close]')) close('close-control');
|
|
572
|
-
});
|
|
573
|
-
if (canListen(surface)) surface.addEventListener('keydown', (event) => { if (event.key === 'Escape') close('escape'); });
|
|
574
|
-
}
|
|
575
|
-
api.quickView = { open: (detail = {}) => openSurface({ ...detail }), close };
|
|
576
|
-
api.emit('gem:quick-view:ready', { source: 'gem-runtime', count: bound, hasSurface: Boolean(surface) });
|
|
577
|
-
});
|
|
593
|
+
registerPlugin("quick-view", ({ api }) => loadChunk("quick-view", "[data-gem-quick-view-trigger], [data-gem-quick-view], [data-gem-action=\"quick-view\"]", "interaction", api));
|
|
578
594
|
registerPlugin('promo-popup', ({ api }) => {
|
|
579
595
|
const popups = document.querySelectorAll('[data-gem-promo-popup]');
|
|
580
596
|
let bound = 0;
|
|
@@ -2086,6 +2102,33 @@
|
|
|
2086
2102
|
});
|
|
2087
2103
|
api.emit('gem:bundle:ready', { source: 'gem-runtime', count: boundBundles });
|
|
2088
2104
|
});
|
|
2105
|
+
registerPlugin('size-guide', ({ api }) => {
|
|
2106
|
+
const page = document.documentElement && typeof document.documentElement.querySelectorAll === 'function' ? document.documentElement : document;
|
|
2107
|
+
const guides = Array.prototype.slice.call(page.querySelectorAll('[data-gem-size-guide]'));
|
|
2108
|
+
const opens = (target) => guides.some((guide) => !target || guide.getAttribute('data-gem-size-guide') === target || guide.id === target);
|
|
2109
|
+
let bound = 0;
|
|
2110
|
+
document.querySelectorAll('[data-gem-size-guide-trigger]').forEach((trigger) => {
|
|
2111
|
+
const target = trigger.getAttribute('data-gem-size-guide-trigger') || '';
|
|
2112
|
+
if (!opens(target)) { trigger.hidden = true; return; }
|
|
2113
|
+
if (!mark(trigger, 'size-guide')) return;
|
|
2114
|
+
trigger.hidden = false;
|
|
2115
|
+
if (!canListen(trigger)) return;
|
|
2116
|
+
trigger.addEventListener('click', (event) => {
|
|
2117
|
+
event.preventDefault && event.preventDefault();
|
|
2118
|
+
const holder = trigger.closest ? trigger.closest('[data-product-id]') : null;
|
|
2119
|
+
const detail = { source: 'gem-runtime', sectionId: target || null, productId: holder ? holder.getAttribute('data-product-id') : null };
|
|
2120
|
+
if (typeof window.CustomEvent === 'function') window.dispatchEvent(new window.CustomEvent('gem:size-guide:open', { detail }));
|
|
2121
|
+
api.emit('gem:size-guide:open', detail);
|
|
2122
|
+
});
|
|
2123
|
+
bound += 1;
|
|
2124
|
+
});
|
|
2125
|
+
api.emit('gem:size-guide:ready', { source: 'gem-runtime', count: bound, guides: guides.length });
|
|
2126
|
+
});
|
|
2127
|
+
registerPlugin("orbit", ({ api }) => loadChunk("orbit", "[data-gem-orbit]", "viewport", api));
|
|
2128
|
+
registerPlugin("rail", ({ api }) => loadChunk("rail", "[data-gem-rail]", "viewport", api));
|
|
2129
|
+
registerPlugin("personalize", ({ api }) => loadChunk("personalize", "[data-gem-personalize]", "viewport", api));
|
|
2130
|
+
registerPlugin("print-preview", ({ api }) => loadChunk("print-preview", "[data-gem-print-preview]", "interaction", api));
|
|
2131
|
+
registerPlugin("delivery-estimate", ({ api }) => loadChunk("delivery-estimate", "[data-gem-delivery-estimate]", "viewport", api));
|
|
2089
2132
|
registerNoopPlugin("seasonal-effects", "gem:seasonal:ready");
|
|
2090
2133
|
registerNoopPlugin("context-adapter", "gem:context:ready");
|
|
2091
2134
|
const mark = (element, key) => {
|