@elasticias/ui 1.0.48 → 1.0.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elasticias/ui",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^21.0.0",
6
6
  "@angular/core": "^21.0.0",
@@ -14,6 +14,7 @@
14
14
  "@primeng/themes": "^21.0.0",
15
15
  "chart.js": "^4.5.0",
16
16
  "primeng": "^21.0.0",
17
+ "rxjs": "~7.8.0",
17
18
  "@angular/platform-browser": "21.2"
18
19
  },
19
20
  "sideEffects": false,
@@ -114,6 +114,11 @@
114
114
  --hit-base: 40px; /* standard buttons */
115
115
  --hit-touch: 48px; /* POS / mobile primary actions */
116
116
  --hit-pos: 64px; /* POS number pad, big totals */
117
+ /* Form fields (inputs, selects, the datepicker trigger). --hit-base with
118
+ a mouse, --hit-touch on a coarse pointer: see the block at the end of
119
+ this file. A separate token because --hit-base itself must not move on
120
+ touch, the top-bar icons and toolbar buttons paint from it. */
121
+ --hit-field: var(--hit-base);
117
122
 
118
123
  /* ── Layers ──────────────────────────────────────────────────── */
119
124
  /* Toasts sit above every modal and overlay. PrimeNG numbers its layers
@@ -285,3 +290,15 @@
285
290
  transition-duration: 0.01ms !important;
286
291
  }
287
292
  }
293
+
294
+ /* -- Touch tier for form fields --------------------------------------
295
+ A field is a tap target too, so on a coarse pointer (touch phone,
296
+ tablet, touch laptop) it grows to --hit-touch (48px), like .btn does
297
+ in _patterns.scss. Keyed on pointer rather than width, as ef-pill-group
298
+ and ef-file-upload are: a narrow desktop window keeps 40px fields. Last
299
+ in the file so the base :root declaration does not win. */
300
+ @media (pointer: coarse) {
301
+ :root {
302
+ --hit-field: var(--hit-touch);
303
+ }
304
+ }
@@ -1221,11 +1221,8 @@
1221
1221
 
1222
1222
  /* ──────────── DETAIL GRID + CARD ───────────────────────────────── */
1223
1223
 
1224
- @media (min-width: 1100px) {
1225
- .detail-grid {
1226
- grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr);
1227
- }
1228
- }
1224
+ /* .detail-grid is defined once, with its 1280px split, next to .detail-stack
1225
+ further down. */
1229
1226
 
1230
1227
  /* ──────────── FORM GRID + FIELD ────────────────────────────────── */
1231
1228
 
@@ -1684,12 +1681,26 @@
1684
1681
  }
1685
1682
 
1686
1683
  .row-actions__item[disabled],
1684
+ .row-actions__item[aria-disabled='true'],
1687
1685
  .ef-menu__item[disabled] {
1688
1686
  opacity: 0.45;
1689
1687
  cursor: not-allowed;
1690
1688
  background: transparent;
1691
1689
  }
1692
1690
 
1691
+ .row-actions__item[aria-disabled='true'] {
1692
+ flex-wrap: wrap;
1693
+ }
1694
+
1695
+ /* Why an item is disabled: a second line under its label. */
1696
+ .row-actions__item .reason {
1697
+ flex: 0 0 100%;
1698
+ padding-inline-start: 28px;
1699
+ font-size: 11.5px;
1700
+ font-weight: 500;
1701
+ color: var(--text-mute);
1702
+ }
1703
+
1693
1704
  .row-actions__item .ico,
1694
1705
  .row-actions__item i,
1695
1706
  .ef-menu__item .ico,
@@ -1977,7 +1988,7 @@
1977
1988
  border-radius: var(--r-md);
1978
1989
  padding: 0 14px;
1979
1990
  width: 100%;
1980
- min-height: var(--hit-base);
1991
+ min-height: var(--hit-field);
1981
1992
  display: inline-flex;
1982
1993
  align-items: center;
1983
1994
  gap: 10px;
@@ -2038,14 +2049,14 @@
2038
2049
 
2039
2050
  /* Inline clear ✕ for the field-mode trigger (single mode only). Overlaid as a
2040
2051
  sibling — not nested in the trigger <button> — just left of the caret,
2041
- vertically centred on the --hit-base trigger box. */
2052
+ vertically centred on the --hit-field trigger box. */
2042
2053
  .dp .ef-field-control {
2043
2054
  position: relative;
2044
2055
  }
2045
2056
 
2046
2057
  .dp__field-clear {
2047
2058
  position: absolute;
2048
- top: calc(var(--hit-base) / 2);
2059
+ top: calc(var(--hit-field) / 2);
2049
2060
  inset-inline-end: 30px;
2050
2061
  transform: translateY(-50%);
2051
2062
  }
@@ -2890,14 +2901,15 @@
2890
2901
  }
2891
2902
  }
2892
2903
 
2893
- /* 2/3 + 1/3 column grid that collapses below 1100px. */
2904
+ /* 2/3 + 1/3 column grid from the desktop breakpoint (1280px, the one
2905
+ EfViewportService and the shell use); one column below it. */
2894
2906
  .detail-grid {
2895
2907
  display: grid;
2896
2908
  grid-template-columns: minmax(0, 1fr);
2897
2909
  gap: var(--s-3);
2898
2910
  }
2899
2911
 
2900
- @media (min-width: 1100px) {
2912
+ @media (min-width: 1280px) {
2901
2913
  .detail-grid {
2902
2914
  grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr);
2903
2915
  }
@@ -3216,6 +3228,17 @@
3216
3228
  color: var(--text);
3217
3229
  }
3218
3230
 
3231
+ .kpi-hint {
3232
+ margin: 0;
3233
+ font-size: 12px;
3234
+ line-height: 1.35;
3235
+ color: var(--text-mute);
3236
+ white-space: nowrap;
3237
+ overflow: hidden;
3238
+ text-overflow: ellipsis;
3239
+ min-width: 0;
3240
+ }
3241
+
3219
3242
  .kpi-value .unit {
3220
3243
  font-family: var(--font-mono);
3221
3244
  font-size: 0.32em;
@@ -3314,7 +3337,7 @@
3314
3337
  border-radius: var(--r-md);
3315
3338
  color: var(--text);
3316
3339
  outline: 0;
3317
- height: var(--hit-base);
3340
+ height: var(--hit-field);
3318
3341
  width: 100%;
3319
3342
  transition:
3320
3343
  border-color var(--t-fast) var(--ease-out),
@@ -3384,6 +3407,19 @@
3384
3407
  color: var(--st-cancelled-fg);
3385
3408
  }
3386
3409
 
3410
+ /* Help text under an ef-* control (`hint` / `hintKey`), linked to the
3411
+ control by aria-describedby. Page text: it keeps the page direction
3412
+ (see forms/_field-direction.scss) and aligns to the start edge, so it
3413
+ reads right to left in Arabic. */
3414
+ .ef-field-hint {
3415
+ display: block;
3416
+ margin-top: 4px;
3417
+ font-size: 12px;
3418
+ line-height: 1.35;
3419
+ color: var(--text-mute);
3420
+ text-align: start;
3421
+ }
3422
+
3387
3423
  /* Native select chevron — replaces the browser default. */
3388
3424
  .ef-select-native,
3389
3425
  .ef-field select {
@@ -3396,6 +3432,13 @@
3396
3432
  cursor: pointer;
3397
3433
  }
3398
3434
 
3435
+ /* The chevron is a background image, which has no logical position: in Arabic
3436
+ the reserved padding sits on the left, so the chevron moves there too. */
3437
+ [dir='rtl'] .ef-select-native,
3438
+ [dir='rtl'] .ef-field select {
3439
+ background-position: left 12px center;
3440
+ }
3441
+
3399
3442
  /* Native checkbox — keeps the underlying <input> for native keyboard
3400
3443
  handling, hidden visually; the .ef-checkbox-box gets the visuals. */
3401
3444
  .ef-checkbox-native {
@@ -3485,7 +3528,10 @@
3485
3528
  border: 1px solid var(--rule);
3486
3529
  border-radius: var(--r-md);
3487
3530
  overflow: hidden;
3488
- min-height: 40px;
3531
+ /* border-box, as .ef-input: the 1px border sits inside --hit-field, so
3532
+ a grouped field (suffix, stepper, currency) matches a bare one. */
3533
+ box-sizing: border-box;
3534
+ min-height: var(--hit-field);
3489
3535
  width: 100%;
3490
3536
  transition:
3491
3537
  border-color var(--t-fast) var(--ease-out),
@@ -3503,6 +3549,10 @@
3503
3549
  }
3504
3550
 
3505
3551
  .ef-input-group input {
3552
+ /* The lib owns the box model and line height of every part of a grouped
3553
+ field, as .ef-input does, so it measures the same whether or not the
3554
+ app loads a CSS reset (ERP loads Tailwind without preflight). */
3555
+ box-sizing: border-box;
3506
3556
  flex: 1;
3507
3557
  border: 0;
3508
3558
  background: transparent;
@@ -3510,11 +3560,13 @@
3510
3560
  padding: 10px 12px;
3511
3561
  font: inherit;
3512
3562
  font-size: 14px;
3563
+ line-height: 1.2;
3513
3564
  color: var(--text);
3514
3565
  min-width: 0;
3515
3566
  }
3516
3567
 
3517
3568
  .ef-input-group .ef-input-addon {
3569
+ box-sizing: border-box;
3518
3570
  background: var(--paper);
3519
3571
  padding: 9px 12px;
3520
3572
  font-family: var(--font-mono);
@@ -3536,7 +3588,16 @@
3536
3588
  /* Stepper buttons (±) sit at either end of the group. They reuse the
3537
3589
  addon base so border weights stay consistent; the button override
3538
3590
  gives them affordance (cursor, hover/active feedback). */
3591
+ /* A currency symbol is a word, not a code: the mono face drew the Arabic
3592
+ "د.م." with detached, oversized dots. The interface face renders it
3593
+ as the locale writes it. */
3594
+ .ef-input-group .ef-input-addon.is-currency {
3595
+ font-family: inherit;
3596
+ font-size: 13px;
3597
+ }
3598
+
3539
3599
  .ef-input-group button.ef-input-addon.is-stepper {
3600
+ box-sizing: border-box;
3540
3601
  appearance: none;
3541
3602
  background: var(--paper);
3542
3603
  border: 0;
@@ -3619,8 +3680,9 @@ input.ef-input[type='number'] {
3619
3680
  background: var(--paper-alt);
3620
3681
  border: 1px solid var(--rule);
3621
3682
  border-radius: var(--r-md);
3622
- min-height: var(--hit-base);
3623
- padding: 0 10px 0 14px;
3683
+ min-height: var(--hit-field);
3684
+ padding-block: 0;
3685
+ padding-inline: 14px 10px;
3624
3686
  display: inline-flex;
3625
3687
  align-items: center;
3626
3688
  gap: var(--es-gap);
@@ -3657,7 +3719,7 @@ input.ef-input[type='number'] {
3657
3719
  .es--sm.p-multiselect {
3658
3720
  font-size: 12.5px;
3659
3721
  min-height: 32px;
3660
- padding: 0 8px 0 12px;
3722
+ padding-inline: 12px 8px;
3661
3723
  border-radius: var(--r-sm);
3662
3724
  }
3663
3725
 
@@ -4278,6 +4340,19 @@ input.ef-input[type='number'] {
4278
4340
  flex: 1;
4279
4341
  }
4280
4342
 
4343
+ /* Product picker (ef-product-picker): 720px on a tablet or desktop, between
4344
+ the lg and xl presets; three classes so it beats the size preset. Rows are
4345
+ 48px (--hit-touch) at every width, since the whole row is the toggle. */
4346
+ .es-dialog.ef-product-picker-dialog.p-dialog {
4347
+ width: 720px;
4348
+ max-width: 92vw;
4349
+ }
4350
+
4351
+ .ef-product-picker-dialog .tbl tbody td {
4352
+ --tbl-cell-pad-block: 6px;
4353
+ height: var(--hit-touch);
4354
+ }
4355
+
4281
4356
  /* ── Header inner structure (rendered via #header content template).
4282
4357
  `flex:1` on the title-block keeps the close right-aligned with or
4283
4358
  without the leading icon. */
@@ -4676,6 +4751,14 @@ input.ef-input[type='number'] {
4676
4751
  margin: calc((var(--hit-touch) - var(--ef-clear-size)) / 2);
4677
4752
  }
4678
4753
 
4754
+ /* Inside a grouped field the clear is a flex item, so its block margin
4755
+ sized the group: 48px plus the group's 1px borders drew a 50px field
4756
+ beside 48px ones. The group's inner height is the whole target here;
4757
+ the margin stops 1px short on each side so the group stays 48px. */
4758
+ .ef-input-group .ef-clear {
4759
+ margin-block: calc((var(--hit-touch) - var(--ef-clear-size)) / 2 - 1px);
4760
+ }
4761
+
4679
4762
  /* The select / multiselect ✕ sits in the trigger's flex row, between the
4680
4763
  label and the caret, 10px (--es-gap) from each. It reserves only the
4681
4764
  inline room its 48px target still lacks after that gap, so the target
@@ -4770,33 +4853,38 @@ input.ef-input[type='number'] {
4770
4853
 
4771
4854
  /* Toolbar buttons collapse to icon-only below 767px, so the blanket
4772
4855
  min-block-size above stretched a 40px-wide button to 48px tall and
4773
- produced a lozenge four-across in the header. Here the VISUAL stays
4774
- 40px square and the TOUCH TARGET reaches the tier through a hit
4775
- slop, which is the point of the tier: a thumb needs 48px, an eye
4776
- does not.
4856
+ produced a lozenge four-across in the header. On a touch phone the
4857
+ button is now a true 48px (--hit-touch) square: the old 40px box with an
4858
+ invisible 48px slop measured 32px to anyone checking the box, and a
4859
+ detail screen keeps at most a few of them inline because
4860
+ ef-detail-toolbar moves the rest into its overflow menu.
4777
4861
 
4778
4862
  Scoped to the same 767px as the rule that hides the label. Keyed on
4779
4863
  pointer type alone it also caught every tablet, which kept its label
4780
4864
  and got a 40px box: "Nouvelle commande" spilled out of the button and
4781
4865
  the detail actions piled into each other. Above 767px a touch screen
4782
- keeps the labelled button, 48px tall from the generic rule above. */
4866
+ keeps the labelled button, 48px tall from the generic rule above.
4867
+ Desktop (fine pointer) is unchanged. */
4783
4868
  @media (pointer: coarse) and (max-width: 767px) {
4784
4869
  .detail-toolbar .right .btn,
4785
4870
  .search-toolbar .right .btn {
4786
- position: relative;
4787
- inline-size: var(--hit-base);
4788
- block-size: var(--hit-base);
4871
+ inline-size: var(--hit-touch);
4872
+ block-size: var(--hit-touch);
4789
4873
  min-inline-size: 0;
4790
4874
  min-block-size: 0;
4791
4875
  padding: 0;
4792
4876
  }
4877
+ }
4793
4878
 
4794
- .detail-toolbar .right .btn::after,
4795
- .search-toolbar .right .btn::after {
4796
- content: '';
4797
- position: absolute;
4798
- /* (48 - 40) / 2 outward on every side */
4799
- inset: calc((var(--hit-touch) - var(--hit-base)) / -2);
4879
+ /* The toolbar's overflow trigger is the shared row-actions kebab. Inside
4880
+ the toolbar it is a real 48px target (not the table's overhang trick,
4881
+ which needs a row to reserve the margin), and a flex item that may not
4882
+ shrink. */
4883
+ @media (pointer: coarse) {
4884
+ .detail-toolbar .right .row-actions__trigger {
4885
+ --ef-row-trigger-w: var(--hit-touch);
4886
+ --ef-row-trigger-h: var(--hit-touch);
4887
+ margin: 0;
4800
4888
  }
4801
4889
  }
4802
4890
 
@@ -4816,3 +4904,75 @@ input.ef-input[type='number'] {
4816
4904
  --app-bottom-bar: 64px;
4817
4905
  }
4818
4906
  }
4907
+
4908
+ /* Product picker on a phone: edge to edge, not the 48px-gutter full preset.
4909
+ The body scrolls between the fixed header and footer. */
4910
+ @media (max-width: 767px) {
4911
+ .es-dialog.ef-product-picker-dialog--fullscreen.p-dialog {
4912
+ width: 100vw;
4913
+ max-width: 100vw;
4914
+ height: 100dvh;
4915
+ max-height: 100dvh;
4916
+ margin: 0;
4917
+ border-radius: 0;
4918
+ }
4919
+
4920
+ .ef-product-picker-dialog--fullscreen .p-dialog-content {
4921
+ max-height: none;
4922
+ flex: 1;
4923
+ }
4924
+ }
4925
+
4926
+ /* ef-file-upload on a touch device: the painted button reaches the 48px touch
4927
+ target. By pointer rather than width, like ef-pill-group, so a touch laptop
4928
+ or tablet gets it too. Last in the file so the base .btn rule does not win. */
4929
+ @media (pointer: coarse) {
4930
+ .ef-file-upload .btn {
4931
+ min-height: var(--hit-touch);
4932
+ }
4933
+ }
4934
+
4935
+ /* -- Order builder: room for its floating total ----------------------
4936
+ ef-order-builder pins its total pill (`.ob-fab`, --ef-order-fab-h tall)
4937
+ 20px above the bottom of the viewport's usable area: above the phone
4938
+ tab bar, which is an in-flow row under the scroll area, so the scroll
4939
+ area already ends where the bar begins. The pill shows up to 1279px,
4940
+ and on a wider screen once the builder is collapsed.
4941
+
4942
+ The reserve goes at the end of the scroll area, not in the builder, so
4943
+ it follows whatever the host renders after the builder (a delivery card,
4944
+ say) and nothing but the pill's own footprint is left blank: offset +
4945
+ pill + one grid gap, plus the home indicator where no tab bar already
4946
+ pads it. The tab bar is not added; the scroll area already stops above
4947
+ it.
4948
+
4949
+ `.order-builder .ob-fab` inside :has() rather than the element name, so
4950
+ the selector outranks ef-app-main's own (encapsulated) padding rule.
4951
+ Last in the file, after every rule it overrides. */
4952
+ :root {
4953
+ --ef-order-fab-h: 56px;
4954
+ }
4955
+
4956
+ /* Phone: the tab bar is in flow under the scroll area and already pads the
4957
+ home indicator, so the scroll area ends above it and the reserve does not
4958
+ add env(safe-area-inset-bottom) again (34px of blank space on a notched
4959
+ iPhone otherwise). */
4960
+ @media (max-width: 767px) {
4961
+ .ef-app-main__content:has(.order-builder .ob-fab) {
4962
+ padding-block-end: calc(20px + var(--ef-order-fab-h) + var(--s-3));
4963
+ }
4964
+ }
4965
+
4966
+ /* Tablet and collapsed desktop: no tab bar, so the scroll area reaches the
4967
+ bottom edge and the pill clears the home indicator itself. */
4968
+ @media (min-width: 768px) and (max-width: 1279px) {
4969
+ .ef-app-main__content:has(.order-builder .ob-fab) {
4970
+ padding-block-end: calc(env(safe-area-inset-bottom, 0px) + 20px + var(--ef-order-fab-h) + var(--s-3));
4971
+ }
4972
+ }
4973
+
4974
+ @media (min-width: 1280px) {
4975
+ .ef-app-main__content:has(.order-builder.ob-collapsed) {
4976
+ padding-block-end: calc(env(safe-area-inset-bottom, 0px) + 20px + var(--ef-order-fab-h) + var(--s-3));
4977
+ }
4978
+ }