@gemboss/gem-framework 0.18.11 → 0.18.15

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,44 @@ 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.15** (20/10/2026): plugin `reviews`, engine danh sách review (GemFactory gap #44). 8 section product-reviews của
97
+ 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:
98
+ `gem-runtime.js` (load `idle`) hoặc `import { mount } from "@gemboss/gem-framework/plugins/reviews"`. Section xin engine bằng
99
+ `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
100
+ chồng. Hợp đồng chỉ là thuộc tính `data-rv-*` (đầy đủ ở đầu `reviews-engine.ts`):
101
+ - 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
102
+ 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
103
+ (`gem:review:vote` trên hub và trên window), lightbox.
104
+ - 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,
105
+ 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ự
106
+ 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
107
+ (`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ế
108
+ ló ra, vuốt, nền mờ, bỏ phiếu từ trong lightbox hoặc chỉ xem).
109
+ - 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
110
+ cố định không còn vẽ dấu … trước trang đầu cửa sổ.
111
+ - 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
112
+ 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
113
+ `gem:reviews:refresh` trên gốc.
114
+ Đố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,
115
+ 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
116
+ và phần còn ở section.
117
+ - **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
118
+ `--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
119
+ 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,
120
+ 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ị
121
+ 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
122
+ 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
123
+ độ đậm.
124
+ - **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ả
125
+ 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
126
+ 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
127
+ bằng `--gem-color-danger` trộn 70/30 với màu chữ của ô: đạt 3:1 trở lên cả trên ô trắng lẫn ô gạch chân trong băng tối/accent
128
+ (màu danger thuần chỉ 2,4:1 và 1,8:1 ở đó). Áp cho mọi store, có hay không có bộ form.
129
+ - **0.18.12** (20/10/2026): control nền thành một bộ (đo trên GemFactory Framework › Controls). Ô số lượng là một field của
130
+ bộ form: hộp theo chiều cao, viền và góc của bộ (góc tối đa 22px, bộ pill ra ô số lượng tròn), −/+ và con số theo nền và cỡ
131
+ chữ của bộ, shape underline không tô ô (`GEM_FORM_SET_CSS`). Con số nằm giữa ô (`<output>` lệch lên 8–9px). Luật sàn của Dawn
132
+ (`min-height: 4.5rem`) không còn gọi `.gem-input`: trên gốc chữ 16px nó là 72px, ô nhập cao gấp rưỡi select khi không có bộ
133
+ form; `.gem-input` theo sàn của primitive như mọi field.
96
134
  - **0.18.11** (19/10/2026): `.gem-stars` nhận sao SVG (gap #45 của GemFactory). Hàng sao rộng đúng bằng các ngôi sao
97
135
  (`width: max-content`), và `svg` / `img` trong hàng sao không bị luật nền `max-width: 100%` bóp. Trước đó sao SVG trong
98
136
  lớp tô bị co theo bề rộng lớp tô (4,2 vẽ thành năm sao ép), và hàng sao là item bị giãn của grid / flex thì phần trăm tô
package/dist/SOURCE.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "package": "@gemboss/gem-framework",
3
- "version": "0.18.11",
4
- "gemtheme_sha": "8ba104ab54bc3c5e5e2e837a5a6a83a2d3fd14c4",
3
+ "version": "0.18.15",
4
+ "gemtheme_sha": "998fe4dc3096e9bc0a8e9d39e23b27081138b2c9",
5
5
  "gemtheme_dirty": false,
6
- "framework_version": "0.18.11"
6
+ "framework_version": "0.18.15"
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;
@@ -412,6 +413,30 @@ a {
412
413
  block-size: var(--gem-checkbox-size);
413
414
  accent-color: var(--gem-checkbox-accent);
414
415
  }
416
+ [data-gem-form-set] :is(.gem-quantity-input) {
417
+ min-height: var(--gem-input-height);
418
+ border: var(--gem-input-border-width) solid var(--gem-input-border-color);
419
+ border-radius: min(var(--gem-input-radius), 22px);
420
+ }
421
+ [data-gem-form-set] :is(.gem-quantity-input__button, .gem-quantity-input__field) {
422
+ background: var(--gem-input-bg);
423
+ font-size: var(--gem-input-font-size);
424
+ }
425
+ [data-gem-form-set][data-gem-field-shape="underline"] :is(.gem-quantity-input__button, .gem-quantity-input__field) {
426
+ background: transparent;
427
+ }
428
+ @media (hover: hover) {
429
+ :is(.gem-input, .gem-select, .gem-textarea, .gem-shell-footer__newsletter .gem-input):where(:not(.gem-search-form .gem-input)):hover:not(:disabled):not([aria-invalid="true"]):not(:focus-visible) {
430
+ border-color: currentColor;
431
+ box-shadow: 0 0 0 1px currentColor;
432
+ }
433
+ [data-gem-form-set]:is([data-gem-field-shape="underline"], [data-gem-field-shape="filled"]) :is(.gem-input, .gem-select, .gem-textarea, .gem-shell-footer__newsletter .gem-input):where(:not(.gem-search-form .gem-input)):hover:not(:disabled):not([aria-invalid="true"]):not(:focus-visible) {
434
+ box-shadow: 0 1px 0 0 currentColor;
435
+ }
436
+ }
437
+ :is(.gem-input, .gem-select, .gem-textarea, .gem-shell-footer__newsletter .gem-input):where(:not(.gem-search-form .gem-input))[aria-invalid="true"] {
438
+ border-color: color-mix(in srgb, var(--gem-color-danger, #b42318) 70%, currentColor);
439
+ }
415
440
  .gem-submit {
416
441
  cursor: pointer;
417
442
  }
@@ -924,6 +949,10 @@ a {
924
949
  color: var(--gem-color-text);
925
950
  text-align: center;
926
951
  }
952
+ .gem-quantity-input__field {
953
+ display: grid;
954
+ place-items: center;
955
+ }
927
956
  .gem-quantity-input__button {
928
957
  font-weight: 750;
929
958
  }
@@ -1267,7 +1296,7 @@ a {
1267
1296
  .gem-cart-drawer__overlay {
1268
1297
  position: fixed;
1269
1298
  inset: 0;
1270
- background: rgba(0, 0, 0, 0.36);
1299
+ background: color-mix(in srgb, var(--gem-color-scrim, #000) 36%, transparent);
1271
1300
  }
1272
1301
  @keyframes gem-cart-drawer-panel-in { from { translate: calc(var(--gem-drawer-offset, 24px) * var(--gem-drawer-dir, 1)) 0; opacity: 0; } }
1273
1302
  @keyframes gem-cart-drawer-overlay-in { from { opacity: 0; } }
@@ -1543,7 +1572,7 @@ a {
1543
1572
  width: min(880px, 92vw); max-width: none; max-height: min(640px, 92vh); overflow: hidden;
1544
1573
  border-radius: var(--gem-radius-lg); box-shadow: var(--gem-shadow-md);
1545
1574
  }
1546
- .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); }
1547
1576
  .gem-popup-shell__frame { position: relative; display: grid; grid-template-columns: 1fr 1fr; min-height: 520px; height: 100%; }
1548
1577
  .gem-popup-shell__media { position: relative; min-width: 0; overflow: hidden; background: var(--gem-color-surface); }
1549
1578
  .gem-popup-shell__image { display: block; width: 100%; height: 100%; object-fit: cover; }
@@ -1566,7 +1595,7 @@ a {
1566
1595
  .gem-popup-shell[data-gem-popup-desktop='card-over-image'] .gem-popup-shell__panel {
1567
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);
1568
1597
  }
1569
- .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)); }
1570
1599
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__panel,
1571
1600
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__close { color: #fff; }
1572
1601
  .gem-popup-shell__close {
@@ -1613,10 +1642,10 @@ a {
1613
1642
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__frame { grid-template-rows: 1fr; }
1614
1643
  .gem-popup-shell[data-gem-popup-media='background'] .gem-popup-shell__media { position: absolute; inset: 0; max-height: none; }
1615
1644
  }
1616
- :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); }
1617
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; }
1618
1647
  :root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'] .gem-popup-shell__dialog { position: relative; display: block; inset: auto; }
1619
- :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); }
1620
1649
  @media (min-width: 750px) {
1621
1650
  :root:has([data-gem-canvas]) .gem-popup-shell[data-gem-popup-preview='true'][data-gem-popup-desktop='fullscreen'] { padding: 0; }
1622
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; }
@@ -1793,7 +1822,7 @@ sticky-header.header-wrapper {
1793
1822
  .newsletter-form__field-wrapper, .field {
1794
1823
  position: relative;
1795
1824
  }
1796
- .field__input, .field input, .gem-input {
1825
+ .field__input, .field input {
1797
1826
  min-height: 4.5rem;
1798
1827
  border-radius: var(--inputs-radius, 0);
1799
1828
  }
@@ -2446,7 +2475,7 @@ main > .gem-section:first-child { padding-top: 0; }
2446
2475
  .gem-hero-banner--default { position: relative; overflow: hidden; padding: 0; color: white; place-items: stretch; }
2447
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; }
2448
2477
  .gem-hero-banner--default .gem-hero-banner__media { position: absolute; inset: 0; z-index: 0; }
2449
- .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); }
2450
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; }
2451
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; }
2452
2481
  .gem-hero-banner--default .gem-section__heading { margin: 0; font-size: 52px; line-height: 67.6px; color: currentColor; }