@gemboss/gem-framework 0.18.11 → 0.18.13

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,16 @@ 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.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
+ 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
+ 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
99
+ 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
100
+ (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.
101
+ - **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
102
+ 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ỡ
103
+ 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
104
+ (`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ộ
105
+ form; `.gem-input` theo sàn của primitive như mọi field.
96
106
  - **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
107
  (`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
108
  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.13",
4
+ "gemtheme_sha": "74aee046a4ea5b333966a87c337ec1d855508775",
5
5
  "gemtheme_dirty": false,
6
- "framework_version": "0.18.11"
6
+ "framework_version": "0.18.13"
7
7
  }
@@ -412,6 +412,30 @@ a {
412
412
  block-size: var(--gem-checkbox-size);
413
413
  accent-color: var(--gem-checkbox-accent);
414
414
  }
415
+ [data-gem-form-set] :is(.gem-quantity-input) {
416
+ min-height: var(--gem-input-height);
417
+ border: var(--gem-input-border-width) solid var(--gem-input-border-color);
418
+ border-radius: min(var(--gem-input-radius), 22px);
419
+ }
420
+ [data-gem-form-set] :is(.gem-quantity-input__button, .gem-quantity-input__field) {
421
+ background: var(--gem-input-bg);
422
+ font-size: var(--gem-input-font-size);
423
+ }
424
+ [data-gem-form-set][data-gem-field-shape="underline"] :is(.gem-quantity-input__button, .gem-quantity-input__field) {
425
+ background: transparent;
426
+ }
427
+ @media (hover: hover) {
428
+ :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) {
429
+ border-color: currentColor;
430
+ box-shadow: 0 0 0 1px currentColor;
431
+ }
432
+ [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) {
433
+ box-shadow: 0 1px 0 0 currentColor;
434
+ }
435
+ }
436
+ :is(.gem-input, .gem-select, .gem-textarea, .gem-shell-footer__newsletter .gem-input):where(:not(.gem-search-form .gem-input))[aria-invalid="true"] {
437
+ border-color: color-mix(in srgb, var(--gem-color-danger, #b42318) 70%, currentColor);
438
+ }
415
439
  .gem-submit {
416
440
  cursor: pointer;
417
441
  }
@@ -924,6 +948,10 @@ a {
924
948
  color: var(--gem-color-text);
925
949
  text-align: center;
926
950
  }
951
+ .gem-quantity-input__field {
952
+ display: grid;
953
+ place-items: center;
954
+ }
927
955
  .gem-quantity-input__button {
928
956
  font-weight: 750;
929
957
  }
@@ -1793,7 +1821,7 @@ sticky-header.header-wrapper {
1793
1821
  .newsletter-form__field-wrapper, .field {
1794
1822
  position: relative;
1795
1823
  }
1796
- .field__input, .field input, .gem-input {
1824
+ .field__input, .field input {
1797
1825
  min-height: 4.5rem;
1798
1826
  border-radius: var(--inputs-radius, 0);
1799
1827
  }
package/dist/index.js CHANGED
@@ -435,7 +435,7 @@ function gemButtonGradientEndCss(accent = "var(--gem-color-accent)", accentText
435
435
  }
436
436
 
437
437
  // ../../packages/gem-framework/src/tokens.ts
438
- var gemFrameworkVersion = "0.18.11";
438
+ var gemFrameworkVersion = "0.18.13";
439
439
  var GEM_FOCUS_RING_CSS_VAR = "--gem-focus-ring: 2px solid var(--gem-color-accent);";
440
440
  var GEM_TAP_MIN_CSS_VAR = "--gem-tap-min: 44px;";
441
441
  var GEM_FOCUS_OFFSET_CSS_VAR = "--gem-focus-offset: 3px;";
@@ -1721,6 +1721,23 @@ var GEM_FORM_SET_CSS_LINES = [
1721
1721
  " inline-size: var(--gem-checkbox-size);",
1722
1722
  " block-size: var(--gem-checkbox-size);",
1723
1723
  " accent-color: var(--gem-checkbox-accent);",
1724
+ "}",
1725
+ // THE QUANTITY STEPPER IS A FIELD OF THE SET (20/10/2026): its box takes the set's height, edge and corner (capped at
1726
+ // 22px, so a pill set draws a round stepper, not a 999px lozenge), its -/+ and number the set's fill and text size, and
1727
+ // the underline shape paints no cell, as it paints no field. The storefront carried these (gemsite #1001/#1007, with its
1728
+ // own element stepper in the :is() lists); the package did not, so every section the kho previewed drew a square 44px
1729
+ // stepper beside 48px pill fields (GemFactory Framework › Controls, measured 20/10/2026).
1730
+ "[data-gem-form-set] :is(.gem-quantity-input) {",
1731
+ " min-height: var(--gem-input-height);",
1732
+ " border: var(--gem-input-border-width) solid var(--gem-input-border-color);",
1733
+ " border-radius: min(var(--gem-input-radius), 22px);",
1734
+ "}",
1735
+ "[data-gem-form-set] :is(.gem-quantity-input__button, .gem-quantity-input__field) {",
1736
+ " background: var(--gem-input-bg);",
1737
+ " font-size: var(--gem-input-font-size);",
1738
+ "}",
1739
+ '[data-gem-form-set][data-gem-field-shape="underline"] :is(.gem-quantity-input__button, .gem-quantity-input__field) {',
1740
+ " background: transparent;",
1724
1741
  "}"
1725
1742
  ];
1726
1743
  var GEM_FORM_SET_CSS = GEM_FORM_SET_CSS_LINES.join("\n");
@@ -2169,8 +2186,9 @@ function createPrimitiveCss() {
2169
2186
  " width: auto;",
2170
2187
  " min-width: min(10rem, 50%);",
2171
2188
  "}",
2172
- // A wrapped field gets the join's height too: the dawn-parity profile sets .gem-input { min-height: 4.5rem }, and
2173
- // only the direct-child rule above outranked it, so a wrapped input stood 72px next to a 44px one (02/10/2026).
2189
+ // A wrapped field gets the join's height too: the dawn-parity profile's 4.5rem floor named .gem-input until 0.18.12, and
2190
+ // only the direct-child rule above outranked it, so a wrapped input stood 72px next to a 44px one (02/10/2026). The
2191
+ // floor no longer names it; this keeps a wrapped field at the join's height whatever a profile floors it at.
2174
2192
  ".gem-field-join > .gem-form-field > :is(.gem-input, .gem-select) {",
2175
2193
  " width: 100%;",
2176
2194
  " height: 100%;",
@@ -2208,6 +2226,32 @@ function createPrimitiveCss() {
2208
2226
  " accent-color: var(--gem-color-accent);",
2209
2227
  "}",
2210
2228
  ...GEM_FORM_SET_CSS_LINES,
2229
+ // HOVER AND ERROR (§9.2 state matrix, 20/10/2026): GemFactory Framework › Controls measured both INERT, so a field a
2230
+ // shopper points at, or one a form marked invalid, looked like any other. Written once for every field, with or
2231
+ // without a form set.
2232
+ // HOVER thickens the edge by 1px in the field's own ink, as Dawn does (`box-shadow: 0 0 0 calc(border + .1rem)`): a
2233
+ // colour change alone was invisible under every form set, whose edge is already the ink (measured #1f2224 → #202223).
2234
+ // The ring follows the box and its corner; an underline or filled field has only a bottom rule, so only that thickens.
2235
+ // ERROR (`aria-invalid="true"`, set by the form) takes the danger colour mixed 70/30 with that ink, which keeps the
2236
+ // edge at 3:1 or more on a white field (9.7:1), an underline field on a dark band (4.2:1) and on an accent band
2237
+ // (3.0:1); plain #b42318 there was 2.4:1 and 1.8:1. Only the edge colour changes, so every shape keeps its edges.
2238
+ // Hover skips a disabled or invalid field (an error must not lose its colour under the pointer), a focused one (the
2239
+ // focus halo on a dark or accent band is a box-shadow, and a click focuses a text field as :focus-visible, so the
2240
+ // pointer still on it would wipe the halo: review) and touch screens, where a tap would leave it stuck.
2241
+ // Specificity: hover (0,6,0; 0,8,0 per shape) outranks the set's field rule (0,3,0); error (0,3,0) ties it and wins by
2242
+ // coming later in the sheet.
2243
+ "@media (hover: hover) {",
2244
+ ' :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) {',
2245
+ " border-color: currentColor;",
2246
+ " box-shadow: 0 0 0 1px currentColor;",
2247
+ " }",
2248
+ ' [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) {',
2249
+ " box-shadow: 0 1px 0 0 currentColor;",
2250
+ " }",
2251
+ "}",
2252
+ ':is(.gem-input, .gem-select, .gem-textarea, .gem-shell-footer__newsletter .gem-input):where(:not(.gem-search-form .gem-input))[aria-invalid="true"] {',
2253
+ " border-color: color-mix(in srgb, var(--gem-color-danger, #b42318) 70%, currentColor);",
2254
+ "}",
2211
2255
  ".gem-submit {",
2212
2256
  " cursor: pointer;",
2213
2257
  "}",
@@ -2767,6 +2811,12 @@ function createPrimitiveCss() {
2767
2811
  " color: var(--gem-color-text);",
2768
2812
  " text-align: center;",
2769
2813
  "}",
2814
+ // The number in the middle of its cell, as the -/+ beside it (a <button> centres its label; an <output> is inline and
2815
+ // put the digit 8-9px above the middle; Framework › Controls, 20/10/2026).
2816
+ ".gem-quantity-input__field {",
2817
+ " display: grid;",
2818
+ " place-items: center;",
2819
+ "}",
2770
2820
  ".gem-quantity-input__button {",
2771
2821
  " font-weight: 750;",
2772
2822
  "}",
@@ -4363,7 +4413,10 @@ function createShellCss() {
4363
4413
  ".newsletter-form__field-wrapper, .field {",
4364
4414
  " position: relative;",
4365
4415
  "}",
4366
- ".field__input, .field input, .gem-input {",
4416
+ // Dawn's field floor, for Dawn's own field classes. It named .gem-input too, and 4.5rem is Dawn's 45px only on Dawn's
4417
+ // 10px root: on this 16px root a .gem-input stood 72px beside a 44px select and stepper whenever no form set ruled it
4418
+ // (GemFactory Framework › Controls, 20/10/2026). A .gem-input takes the primitive's floor, as every field does.
4419
+ ".field__input, .field input {",
4367
4420
  " min-height: 4.5rem;",
4368
4421
  " border-radius: var(--inputs-radius, 0);",
4369
4422
  "}",
@@ -321,6 +321,30 @@ a {
321
321
  block-size: var(--gem-checkbox-size);
322
322
  accent-color: var(--gem-checkbox-accent);
323
323
  }
324
+ [data-gem-form-set] :is(.gem-quantity-input) {
325
+ min-height: var(--gem-input-height);
326
+ border: var(--gem-input-border-width) solid var(--gem-input-border-color);
327
+ border-radius: min(var(--gem-input-radius), 22px);
328
+ }
329
+ [data-gem-form-set] :is(.gem-quantity-input__button, .gem-quantity-input__field) {
330
+ background: var(--gem-input-bg);
331
+ font-size: var(--gem-input-font-size);
332
+ }
333
+ [data-gem-form-set][data-gem-field-shape="underline"] :is(.gem-quantity-input__button, .gem-quantity-input__field) {
334
+ background: transparent;
335
+ }
336
+ @media (hover: hover) {
337
+ :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) {
338
+ border-color: currentColor;
339
+ box-shadow: 0 0 0 1px currentColor;
340
+ }
341
+ [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) {
342
+ box-shadow: 0 1px 0 0 currentColor;
343
+ }
344
+ }
345
+ :is(.gem-input, .gem-select, .gem-textarea, .gem-shell-footer__newsletter .gem-input):where(:not(.gem-search-form .gem-input))[aria-invalid="true"] {
346
+ border-color: color-mix(in srgb, var(--gem-color-danger, #b42318) 70%, currentColor);
347
+ }
324
348
  .gem-submit {
325
349
  cursor: pointer;
326
350
  }
@@ -833,6 +857,10 @@ a {
833
857
  color: var(--gem-color-text);
834
858
  text-align: center;
835
859
  }
860
+ .gem-quantity-input__field {
861
+ display: grid;
862
+ place-items: center;
863
+ }
836
864
  .gem-quantity-input__button {
837
865
  font-weight: 750;
838
866
  }
@@ -1262,6 +1262,23 @@ var GEM_FORM_SET_CSS_LINES = [
1262
1262
  " inline-size: var(--gem-checkbox-size);",
1263
1263
  " block-size: var(--gem-checkbox-size);",
1264
1264
  " accent-color: var(--gem-checkbox-accent);",
1265
+ "}",
1266
+ // THE QUANTITY STEPPER IS A FIELD OF THE SET (20/10/2026): its box takes the set's height, edge and corner (capped at
1267
+ // 22px, so a pill set draws a round stepper, not a 999px lozenge), its -/+ and number the set's fill and text size, and
1268
+ // the underline shape paints no cell, as it paints no field. The storefront carried these (gemsite #1001/#1007, with its
1269
+ // own element stepper in the :is() lists); the package did not, so every section the kho previewed drew a square 44px
1270
+ // stepper beside 48px pill fields (GemFactory Framework › Controls, measured 20/10/2026).
1271
+ "[data-gem-form-set] :is(.gem-quantity-input) {",
1272
+ " min-height: var(--gem-input-height);",
1273
+ " border: var(--gem-input-border-width) solid var(--gem-input-border-color);",
1274
+ " border-radius: min(var(--gem-input-radius), 22px);",
1275
+ "}",
1276
+ "[data-gem-form-set] :is(.gem-quantity-input__button, .gem-quantity-input__field) {",
1277
+ " background: var(--gem-input-bg);",
1278
+ " font-size: var(--gem-input-font-size);",
1279
+ "}",
1280
+ '[data-gem-form-set][data-gem-field-shape="underline"] :is(.gem-quantity-input__button, .gem-quantity-input__field) {',
1281
+ " background: transparent;",
1265
1282
  "}"
1266
1283
  ];
1267
1284
  var GEM_FORM_SET_CSS = GEM_FORM_SET_CSS_LINES.join("\n");
@@ -1703,8 +1720,9 @@ function createPrimitiveCss() {
1703
1720
  " width: auto;",
1704
1721
  " min-width: min(10rem, 50%);",
1705
1722
  "}",
1706
- // A wrapped field gets the join's height too: the dawn-parity profile sets .gem-input { min-height: 4.5rem }, and
1707
- // only the direct-child rule above outranked it, so a wrapped input stood 72px next to a 44px one (02/10/2026).
1723
+ // A wrapped field gets the join's height too: the dawn-parity profile's 4.5rem floor named .gem-input until 0.18.12, and
1724
+ // only the direct-child rule above outranked it, so a wrapped input stood 72px next to a 44px one (02/10/2026). The
1725
+ // floor no longer names it; this keeps a wrapped field at the join's height whatever a profile floors it at.
1708
1726
  ".gem-field-join > .gem-form-field > :is(.gem-input, .gem-select) {",
1709
1727
  " width: 100%;",
1710
1728
  " height: 100%;",
@@ -1742,6 +1760,32 @@ function createPrimitiveCss() {
1742
1760
  " accent-color: var(--gem-color-accent);",
1743
1761
  "}",
1744
1762
  ...GEM_FORM_SET_CSS_LINES,
1763
+ // HOVER AND ERROR (§9.2 state matrix, 20/10/2026): GemFactory Framework › Controls measured both INERT, so a field a
1764
+ // shopper points at, or one a form marked invalid, looked like any other. Written once for every field, with or
1765
+ // without a form set.
1766
+ // HOVER thickens the edge by 1px in the field's own ink, as Dawn does (`box-shadow: 0 0 0 calc(border + .1rem)`): a
1767
+ // colour change alone was invisible under every form set, whose edge is already the ink (measured #1f2224 → #202223).
1768
+ // The ring follows the box and its corner; an underline or filled field has only a bottom rule, so only that thickens.
1769
+ // ERROR (`aria-invalid="true"`, set by the form) takes the danger colour mixed 70/30 with that ink, which keeps the
1770
+ // edge at 3:1 or more on a white field (9.7:1), an underline field on a dark band (4.2:1) and on an accent band
1771
+ // (3.0:1); plain #b42318 there was 2.4:1 and 1.8:1. Only the edge colour changes, so every shape keeps its edges.
1772
+ // Hover skips a disabled or invalid field (an error must not lose its colour under the pointer), a focused one (the
1773
+ // focus halo on a dark or accent band is a box-shadow, and a click focuses a text field as :focus-visible, so the
1774
+ // pointer still on it would wipe the halo: review) and touch screens, where a tap would leave it stuck.
1775
+ // Specificity: hover (0,6,0; 0,8,0 per shape) outranks the set's field rule (0,3,0); error (0,3,0) ties it and wins by
1776
+ // coming later in the sheet.
1777
+ "@media (hover: hover) {",
1778
+ ' :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) {',
1779
+ " border-color: currentColor;",
1780
+ " box-shadow: 0 0 0 1px currentColor;",
1781
+ " }",
1782
+ ' [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) {',
1783
+ " box-shadow: 0 1px 0 0 currentColor;",
1784
+ " }",
1785
+ "}",
1786
+ ':is(.gem-input, .gem-select, .gem-textarea, .gem-shell-footer__newsletter .gem-input):where(:not(.gem-search-form .gem-input))[aria-invalid="true"] {',
1787
+ " border-color: color-mix(in srgb, var(--gem-color-danger, #b42318) 70%, currentColor);",
1788
+ "}",
1745
1789
  ".gem-submit {",
1746
1790
  " cursor: pointer;",
1747
1791
  "}",
@@ -2301,6 +2345,12 @@ function createPrimitiveCss() {
2301
2345
  " color: var(--gem-color-text);",
2302
2346
  " text-align: center;",
2303
2347
  "}",
2348
+ // The number in the middle of its cell, as the -/+ beside it (a <button> centres its label; an <output> is inline and
2349
+ // put the digit 8-9px above the middle; Framework › Controls, 20/10/2026).
2350
+ ".gem-quantity-input__field {",
2351
+ " display: grid;",
2352
+ " place-items: center;",
2353
+ "}",
2304
2354
  ".gem-quantity-input__button {",
2305
2355
  " font-weight: 750;",
2306
2356
  "}",
@@ -3897,7 +3947,10 @@ function createShellCss() {
3897
3947
  ".newsletter-form__field-wrapper, .field {",
3898
3948
  " position: relative;",
3899
3949
  "}",
3900
- ".field__input, .field input, .gem-input {",
3950
+ // Dawn's field floor, for Dawn's own field classes. It named .gem-input too, and 4.5rem is Dawn's 45px only on Dawn's
3951
+ // 10px root: on this 16px root a .gem-input stood 72px beside a 44px select and stepper whenever no form set ruled it
3952
+ // (GemFactory Framework › Controls, 20/10/2026). A .gem-input takes the primitive's floor, as every field does.
3953
+ ".field__input, .field input {",
3901
3954
  " min-height: 4.5rem;",
3902
3955
  " border-radius: var(--inputs-radius, 0);",
3903
3956
  "}",
package/dist/sections.css CHANGED
@@ -163,7 +163,7 @@ sticky-header.header-wrapper {
163
163
  .newsletter-form__field-wrapper, .field {
164
164
  position: relative;
165
165
  }
166
- .field__input, .field input, .gem-input {
166
+ .field__input, .field input {
167
167
  min-height: 4.5rem;
168
168
  border-radius: var(--inputs-radius, 0);
169
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gemboss/gem-framework",
3
- "version": "0.18.11",
3
+ "version": "0.18.13",
4
4
  "description": "gem-framework của GemTheme (CSS, runtime, token, bộ kiểm) cộng đúng bộ kiểm section gemtheme dùng khi duyệt. Build từ gemtheme, không sửa ở đây.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",