@gemboss/gem-framework 0.18.13 → 0.18.16

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 CHANGED
@@ -93,6 +93,60 @@ Từ 0.8.2 mỗi module có `.d.ts` cạnh nó (`mount(root?: ParentNode | null,
93
93
 
94
94
  ## Nhật ký thay đổi
95
95
 
96
+ - **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
+ (slot-sidebar, box-size-first, routine-kit, tier-bar, system-list, reward-tiles, silhouette-tray, gift-box, grid-milestone-panel)
98
+ 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ộ
99
+ đếm, kit + bước, hộp quà có nắp), nên engine là mô hình và bộ điều khiển, không vẽ.
100
+ - Section xin engine bằng `data-bb-root`.
101
+ - Engine giữ luật (`data-bb-rules`: min, max, per, repeat, tiers, price_mode, sizes, kits + steps), món đã chọn, cỡ hộp, kit và
102
+ bước, giá. Có ba cách tính: `totals` (theo bậc bộ sẽ đạt), `totalsNow` (theo bậc đang đạt), `unitNow` (giá một món trên thẻ).
103
+ - Engine xử lý mọi thao tác đổi bộ: `[data-bb-add]` / `[data-bb-inc]`, `[data-bb-dec]` / `[data-bb-minus]`, `[data-bb-uncheck]`,
104
+ `[data-bb-remove]`, `[data-bb-clear]`, ô số `input[data-bb-count]`, cách mua `input[data-bb-plan]` (radio hoặc checkbox),
105
+ `input[data-bb-size]` (cỡ hộp, hoặc biến thể của một món khi có `data-bb-variant`), `[data-bb-kit-tab]`, `[data-bb-step-nav]`,
106
+ `[data-bb-cta]` / `[data-bb-skip]` (nút mang `aria-disabled="true"` thì không gửi).
107
+ - Bật theo thuộc tính, không đổi gì của section không khai: cỡ hộp `data-bb-sizes="trim|queue"` (hạ cỡ thì bỏ món cuối, hoặc
108
+ giữ chúng chờ vào lại), bấm lại để bỏ `data-bb-add-toggle`, tiền "1.234đ" `data-bb-money="vi"`, chữ ở `data-bb-labels`; theo
109
+ dữ liệu của món: biến thể theo thẻ (`data-bb-variants` + `data-bb-current`, khoá `<thẻ>~<chỉ số>`), món quà `data-bb-gift`
110
+ (vào giỏ cùng bộ, không tính số món).
111
+ - Giỏ: MỘT `gem:cart:add-many`, mỗi dòng mang `_bundle_id`, `_bundle`, `_bundle_rule`, cộng `sellingPlanId` khi định kỳ. Có cầu giỏ
112
+ gemsite thì chờ `gem:cart:added` / `gem:cart:add-failed`. Phần riêng của section nối vào qua `setBase(giá)` (giá vỏ hộp vào tổng)
113
+ và `setCartHead(fn)` (dòng vỏ hộp đứng đầu, mang thuộc tính nắp / quà).
114
+ - Sau mỗi lần đổi phát `gem:bundle:change` (kèm `action`) trên gốc. API `root.gemBundle` (`state()`, `money`, `fill`, `say`, toán
115
+ bậc / giá, các thao tác) cho `paint()` của section.
116
+ - Đầu ra chung tuỳ chọn `[data-bb-out]` cho trang không tự vẽ.
117
+
118
+ Đối chiếu trong Chromium, 9 section, 1280 và 390: behavior.js gốc so với engine + behavior chỉ còn phần vẽ, cùng chuỗi thao tác
119
+ (thêm, +, −, ✓, ×, xoá hết, ô số, đổi cách mua, tần suất, cỡ hộp, biến thể, chip, kit, bước, nắp, quà, sheet, nút chính + nội
120
+ dung `gem:cart:add-many`). Trạng thái mọi phần tử mang hook, focus và giỏ trùng từng bước (0 khác biệt); engine cố ý làm sai một
121
+ bậc thì cả 9 lệch. Kèm vá của `reviews`: dải ảnh nhỏ trong lightbox đặt `loading` trước `src`.
122
+ - **0.18.15** (20/10/2026): plugin `reviews`, engine danh sách review (GemFactory gap #44). 8 section product-reviews của
123
+ GemFactory mỗi cái mang một bản riêng của cùng một lõi trong behavior.js (14–27 KB mỗi cái); nay lõi đó là MỘT plugin:
124
+ `gem-runtime.js` (load `idle`) hoặc `import { mount } from "@gemboss/gem-framework/plugins/reviews"`. Section xin engine bằng
125
+ `data-rv-root` trên gốc của nó; section không có dấu đó (vẫn mang lõi riêng) không bị gắn, nên lõi riêng và plugin không chạy
126
+ chồng. Hợp đồng chỉ là thuộc tính `data-rv-*` (đầy đủ ở đầu `reviews-engine.ts`):
127
+ - Lõi: sort (menu hoặc select, 13 khoá), lọc sao / có ảnh / chủ đề / quốc gia / tìm (trễ 180 ms) / xoá lọc, trang số (một hay
128
+ nhiều thanh, cửa sổ cố định hoặc `1 2 3 4 … n`) hoặc xem thêm, đếm, đọc thêm, menu, sheet lọc, hữu ích một lần mỗi review
129
+ (`gem:review:vote` trên hub và trên window), lightbox.
130
+ - Chỗ 8 section khác nhau thành thuộc tính: thu gọn N review đầu (`data-rv-collapsed` + `data-rv-expand`), Show N mỗi trang,
131
+ nhãn trang, sao giữ khi bấm lại (`data-rv-star-keep`), chip bộ lọc đang bật, sheet có Apply / Reset, vùng bật tắt và vùng tự
132
+ bỏ lọc khi đóng, dải trượt, tô chữ tìm thấy, chữ số ít (`data-rv-count-one`, `data-rv-status-one`), số theo locale
133
+ (`data-rv-locale`), lightbox (đầu cuối ẩn / mờ / vòng, tập ảnh theo review / nhóm / danh sách JSON của thẻ, bỏ trùng, ảnh kế
134
+ ló ra, vuốt, nền mờ, bỏ phiếu từ trong lightbox hoặc chỉ xem).
135
+ - Khác bản cũ có chủ đích: chọn một mục trong menu thì focus về nút của menu (bản cũ để focus rơi xuống `<body>`); cửa sổ trang
136
+ cố định không còn vẽ dấu … trước trang đầu cửa sổ.
137
+ - Còn ở behavior của từng section: masonry, carousel vòng vô tận + tự chạy, hộp đọc cả review, dịch, sắp xếp hỏi đáp, thẻ chen
138
+ giữa. Chúng vẽ lại theo `gem:reviews:render` (phát trên hub VÀ trên gốc section), và nhờ engine vẽ lại bằng
139
+ `gem:reviews:refresh` trên gốc.
140
+ Đối chiếu trong Chromium trên markup thật của cả 8 section, cùng một chuỗi thao tác (sort, sao, lọc, trang, xem thêm, đọc thêm,
141
+ hữu ích, lightbox, menu; 1280 và 390), behavior.js gốc so với engine: trạng thái trùng từng bước, trừ khác biệt có chủ đích ở trên
142
+ và phần còn ở section.
143
+ - **0.18.14** (20/10/2026): lớp phủ tối là một token (GemFactory gap #17). Khoá màu TUỲ CHỌN `color.scrim` (phát thành
144
+ `--gem-color-scrim`) là màu lớp phủ ở độ đậm tối đa; nền sau dialog popup (55%), lớp tối trên ảnh nền popup (35% → 60%), nền sau
145
+ cart drawer (36%) và lớp tối trên ảnh của hero mặc định (32%) đọc nó qua `gemScrim(N)` = `color-mix(in srgb,
146
+ var(--gem-color-scrim, #000) N%, transparent)`. Không đặt thì ra ĐÚNG như trước (đo Chromium: 5/5 độ đậm trùng từng giá trị
147
+ pixel với màu viết cứng cũ); preset đặt màu có alpha (`rgb(0 0 0 / .7)`) thì mọi lớp phủ nhạt cùng tỉ lệ. Băng dark / accent
148
+ không đảo khoá này. Cảnh báo `untokenized_visual_value` trên một màu đen có alpha giờ chỉ thẳng cách viết bằng token, giữ đúng
149
+ độ đậm.
96
150
  - **0.18.13** (20/10/2026): ô nhập, select, textarea có trạng thái hover và lỗi (§9.2; GemFactory Framework › Controls đo cả
97
151
  hai là INERT). Hover dày viền thêm 1px theo màu chữ của ô (vòng theo hộp; ô gạch chân và ô tô nền chỉ dày cạnh đáy), chỉ trên
98
152
  máy có chuột (`@media (hover: hover)`), không áp cho ô disabled hay ô lỗi. Lỗi (`aria-invalid="true"`, do form đặt) tô viền
package/dist/SOURCE.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "package": "@gemboss/gem-framework",
3
- "version": "0.18.13",
4
- "gemtheme_sha": "74aee046a4ea5b333966a87c337ec1d855508775",
3
+ "version": "0.18.16",
4
+ "gemtheme_sha": "bd2fed3a51b065235fd57cda3526879241ed2332",
5
5
  "gemtheme_dirty": false,
6
- "framework_version": "0.18.13"
6
+ "framework_version": "0.18.16"
7
7
  }
package/dist/editions.js CHANGED
@@ -83,6 +83,8 @@ var GEM_MOTION_SCALE_DEFAULTS = {
83
83
  blur: "8px"
84
84
  };
85
85
  var optionalGemTokenKeys = {
86
+ // Lớp phủ tối (GemFactory gap #17): xem GEM_SCRIM_FALLBACK / gemScrim bên dưới.
87
+ color: ["scrim"],
86
88
  typography: [...GEM_TYPE_RAMP_KEYS, ...GEM_LINE_HEIGHT_TOKEN_KEYS, ...GEM_BUTTON_TOKEN_KEYS.typography],
87
89
  spacing: [...Object.keys(GEM_DERIVED_SPACE_STEPS), ...GEM_PHONE_TOKEN_TARGETS.spacing.map((k) => `${k}Mobile`)],
88
90
  layout: GEM_PHONE_TOKEN_TARGETS.layout.map((k) => `${k}Mobile`),
@@ -9,6 +9,7 @@
9
9
  --gem-color-success: #008060;
10
10
  --gem-color-warning: #9a661a;
11
11
  --gem-color-danger: #d72c0d;
12
+ --gem-color-scrim: #000;
12
13
  --gem-font-body: Inter, system-ui, sans-serif;
13
14
  --gem-font-heading: Inter, system-ui, sans-serif;
14
15
  --gem-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
@@ -1295,7 +1296,7 @@ a {
1295
1296
  .gem-cart-drawer__overlay {
1296
1297
  position: fixed;
1297
1298
  inset: 0;
1298
- background: rgba(0, 0, 0, 0.36);
1299
+ background: color-mix(in srgb, var(--gem-color-scrim, #000) 36%, transparent);
1299
1300
  }
1300
1301
  @keyframes gem-cart-drawer-panel-in { from { translate: calc(var(--gem-drawer-offset, 24px) * var(--gem-drawer-dir, 1)) 0; opacity: 0; } }
1301
1302
  @keyframes gem-cart-drawer-overlay-in { from { opacity: 0; } }
@@ -1571,7 +1572,7 @@ a {
1571
1572
  width: min(880px, 92vw); max-width: none; max-height: min(640px, 92vh); overflow: hidden;
1572
1573
  border-radius: var(--gem-radius-lg); box-shadow: var(--gem-shadow-md);
1573
1574
  }
1574
- .gem-popup-shell__dialog::backdrop { background: rgb(0 0 0 / 0.55); }
1575
+ .gem-popup-shell__dialog::backdrop { background: color-mix(in srgb, var(--gem-color-scrim, #000) 55%, transparent); }
1575
1576
  .gem-popup-shell__frame { position: relative; display: grid; grid-template-columns: 1fr 1fr; min-height: 520px; height: 100%; }
1576
1577
  .gem-popup-shell__media { position: relative; min-width: 0; overflow: hidden; background: var(--gem-color-surface); }
1577
1578
  .gem-popup-shell__image { display: block; width: 100%; height: 100%; object-fit: cover; }
@@ -1594,7 +1595,7 @@ a {
1594
1595
  .gem-popup-shell[data-gem-popup-desktop='card-over-image'] .gem-popup-shell__panel {
1595
1596
  width: min(420px, 86%); margin: var(--gem-space-xl) 0; background: var(--gem-color-background); border-radius: var(--gem-radius-lg); box-shadow: var(--gem-shadow-sm);
1596
1597
  }
1597
- .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__media::after { content: ''; position: absolute; inset: 0; background: linear-gradient(rgb(0 0 0 / 0.35), rgb(0 0 0 / 0.6)); }
1598
+ .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__media::after { content: ''; position: absolute; inset: 0; background: linear-gradient(color-mix(in srgb, var(--gem-color-scrim, #000) 35%, transparent), color-mix(in srgb, var(--gem-color-scrim, #000) 60%, transparent)); }
1598
1599
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__panel,
1599
1600
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__close { color: #fff; }
1600
1601
  .gem-popup-shell__close {
@@ -1641,10 +1642,10 @@ a {
1641
1642
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__frame { grid-template-rows: 1fr; }
1642
1643
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__media { position: absolute; inset: 0; max-height: none; }
1643
1644
  }
1644
- :root:not(:has([data-gem-canvas])) .gem-popup-shell[data-gem-popup-preview='true'] { position: fixed; inset: 0; z-index: 1000; padding: 0; background: rgb(0 0 0 / 0.55); }
1645
+ :root:not(:has([data-gem-canvas])) .gem-popup-shell[data-gem-popup-preview='true'] { position: fixed; inset: 0; z-index: 1000; padding: 0; background: color-mix(in srgb, var(--gem-color-scrim, #000) 55%, transparent); }
1645
1646
  :root:not(:has([data-gem-canvas])) .gem-popup-shell[data-gem-popup-preview='true'] .gem-popup-shell__dialog { position: fixed; inset: 0; display: block; }
1646
1647
  :root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'] .gem-popup-shell__dialog { position: relative; display: block; inset: auto; }
1647
- :root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'] { padding: var(--gem-space-xl) var(--gem-layout-gutter); background: rgb(0 0 0 / 0.55); }
1648
+ :root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'] { padding: var(--gem-space-xl) var(--gem-layout-gutter); background: color-mix(in srgb, var(--gem-color-scrim, #000) 55%, transparent); }
1648
1649
  @media (min-width: 750px) {
1649
1650
  :root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'][data-gem-popup-desktop='fullscreen'] { padding: 0; }
1650
1651
  :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; }
@@ -2474,7 +2475,7 @@ main > .gem-section:first-child { padding-top: 0; }
2474
2475
  .gem-hero-banner--default { position: relative; overflow: hidden; padding: 0; color: white; place-items: stretch; }
2475
2476
  .gem-hero-banner--default .gem-hero-banner__inner { position: static; width: 100%; min-height: 720px; height: 100%; display: grid; align-items: end; justify-items: center; padding-bottom: 0; }
2476
2477
  .gem-hero-banner--default .gem-hero-banner__media { position: absolute; inset: 0; z-index: 0; }
2477
- .gem-hero-banner--default .gem-hero-banner__media::after { content: ''; position: absolute; inset: 0; background: rgb(0 0 0 / 32%); }
2478
+ .gem-hero-banner--default .gem-hero-banner__media::after { content: ''; position: absolute; inset: 0; background: color-mix(in srgb, var(--gem-color-scrim, #000) 32%, transparent); }
2478
2479
  .gem-hero-banner--default .gem-hero-banner__image, .gem-hero-banner--default .gem-hero-banner__placeholder { width: 100%; height: 100%; object-fit: cover; display: block; }
2479
2480
  .gem-hero-banner--default .gem-hero-banner__copy { position: absolute; z-index: 1; left: 50%; bottom: 50px; transform: translateX(-50%); width: min(702px, 100%); margin: 0; padding: 40px 0; text-align: center; display: block; }
2480
2481
  .gem-hero-banner--default .gem-section__heading { margin: 0; font-size: 52px; line-height: 67.6px; color: currentColor; }