@elasticias/ui 1.0.48 → 1.0.50

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.50",
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
+ }
@@ -890,6 +890,8 @@
890
890
  font-family: var(--font-mono);
891
891
  font-size: 12px;
892
892
  font-weight: 700;
893
+ /* Hyphenated codes (HA-100) break at the hyphen otherwise. */
894
+ white-space: nowrap;
893
895
  }
894
896
 
895
897
  .tbl .num {
@@ -1221,11 +1223,8 @@
1221
1223
 
1222
1224
  /* ──────────── DETAIL GRID + CARD ───────────────────────────────── */
1223
1225
 
1224
- @media (min-width: 1100px) {
1225
- .detail-grid {
1226
- grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr);
1227
- }
1228
- }
1226
+ /* .detail-grid is defined once, with its 1280px split, next to .detail-stack
1227
+ further down. */
1229
1228
 
1230
1229
  /* ──────────── FORM GRID + FIELD ────────────────────────────────── */
1231
1230
 
@@ -1684,12 +1683,26 @@
1684
1683
  }
1685
1684
 
1686
1685
  .row-actions__item[disabled],
1686
+ .row-actions__item[aria-disabled='true'],
1687
1687
  .ef-menu__item[disabled] {
1688
1688
  opacity: 0.45;
1689
1689
  cursor: not-allowed;
1690
1690
  background: transparent;
1691
1691
  }
1692
1692
 
1693
+ .row-actions__item[aria-disabled='true'] {
1694
+ flex-wrap: wrap;
1695
+ }
1696
+
1697
+ /* Why an item is disabled: a second line under its label. */
1698
+ .row-actions__item .reason {
1699
+ flex: 0 0 100%;
1700
+ padding-inline-start: 28px;
1701
+ font-size: 11.5px;
1702
+ font-weight: 500;
1703
+ color: var(--text-mute);
1704
+ }
1705
+
1693
1706
  .row-actions__item .ico,
1694
1707
  .row-actions__item i,
1695
1708
  .ef-menu__item .ico,
@@ -1977,7 +1990,7 @@
1977
1990
  border-radius: var(--r-md);
1978
1991
  padding: 0 14px;
1979
1992
  width: 100%;
1980
- min-height: var(--hit-base);
1993
+ min-height: var(--hit-field);
1981
1994
  display: inline-flex;
1982
1995
  align-items: center;
1983
1996
  gap: 10px;
@@ -2038,14 +2051,14 @@
2038
2051
 
2039
2052
  /* Inline clear ✕ for the field-mode trigger (single mode only). Overlaid as a
2040
2053
  sibling — not nested in the trigger <button> — just left of the caret,
2041
- vertically centred on the --hit-base trigger box. */
2054
+ vertically centred on the --hit-field trigger box. */
2042
2055
  .dp .ef-field-control {
2043
2056
  position: relative;
2044
2057
  }
2045
2058
 
2046
2059
  .dp__field-clear {
2047
2060
  position: absolute;
2048
- top: calc(var(--hit-base) / 2);
2061
+ top: calc(var(--hit-field) / 2);
2049
2062
  inset-inline-end: 30px;
2050
2063
  transform: translateY(-50%);
2051
2064
  }
@@ -2795,11 +2808,17 @@
2795
2808
  flex-wrap: wrap;
2796
2809
  }
2797
2810
 
2811
+ /* The identity takes a real basis, not 0: with `flex: 1` it always "fit",
2812
+ so a screen with many actions squeezed it to a sliver and cut its notices
2813
+ ("Prise en charge par ...") to an ellipsis. Now, when the identity and the
2814
+ actions cannot share a row at that width, the actions wrap onto a row of
2815
+ their own and the identity keeps the whole line. A screen may tune the
2816
+ basis with --detail-toolbar-identity-min. */
2798
2817
  .detail-toolbar .left {
2799
2818
  display: flex;
2800
2819
  align-items: center;
2801
2820
  gap: 10px;
2802
- flex: 1;
2821
+ flex: 1 1 var(--detail-toolbar-identity-min, 22rem);
2803
2822
  min-width: 0;
2804
2823
  flex-wrap: wrap;
2805
2824
  }
@@ -2815,6 +2834,22 @@
2815
2834
  color: var(--text-soft);
2816
2835
  }
2817
2836
 
2837
+ /* Keeps its width: removing it would narrow the group and let it unwrap. */
2838
+ .detail-toolbar .sep.is-hidden {
2839
+ visibility: hidden;
2840
+ }
2841
+
2842
+ /* Print … Save travel together with their separator, so a wrap never leaves
2843
+ the "|" at the end of one row and Save alone on the next. The group keeps
2844
+ the far end of its row, like Save on a phone. */
2845
+ .detail-toolbar .std {
2846
+ display: inline-flex;
2847
+ align-items: center;
2848
+ gap: 6px;
2849
+ flex-wrap: nowrap;
2850
+ margin-inline-start: auto;
2851
+ }
2852
+
2818
2853
  .detail-toolbar .right {
2819
2854
  display: flex;
2820
2855
  align-items: center;
@@ -2890,14 +2925,15 @@
2890
2925
  }
2891
2926
  }
2892
2927
 
2893
- /* 2/3 + 1/3 column grid that collapses below 1100px. */
2928
+ /* 2/3 + 1/3 column grid from the desktop breakpoint (1280px, the one
2929
+ EfViewportService and the shell use); one column below it. */
2894
2930
  .detail-grid {
2895
2931
  display: grid;
2896
2932
  grid-template-columns: minmax(0, 1fr);
2897
2933
  gap: var(--s-3);
2898
2934
  }
2899
2935
 
2900
- @media (min-width: 1100px) {
2936
+ @media (min-width: 1280px) {
2901
2937
  .detail-grid {
2902
2938
  grid-template-columns: minmax(0, 2fr) minmax(300px, 1fr);
2903
2939
  }
@@ -3216,6 +3252,17 @@
3216
3252
  color: var(--text);
3217
3253
  }
3218
3254
 
3255
+ .kpi-hint {
3256
+ margin: 0;
3257
+ font-size: 12px;
3258
+ line-height: 1.35;
3259
+ color: var(--text-mute);
3260
+ white-space: nowrap;
3261
+ overflow: hidden;
3262
+ text-overflow: ellipsis;
3263
+ min-width: 0;
3264
+ }
3265
+
3219
3266
  .kpi-value .unit {
3220
3267
  font-family: var(--font-mono);
3221
3268
  font-size: 0.32em;
@@ -3314,7 +3361,7 @@
3314
3361
  border-radius: var(--r-md);
3315
3362
  color: var(--text);
3316
3363
  outline: 0;
3317
- height: var(--hit-base);
3364
+ height: var(--hit-field);
3318
3365
  width: 100%;
3319
3366
  transition:
3320
3367
  border-color var(--t-fast) var(--ease-out),
@@ -3384,6 +3431,19 @@
3384
3431
  color: var(--st-cancelled-fg);
3385
3432
  }
3386
3433
 
3434
+ /* Help text under an ef-* control (`hint` / `hintKey`), linked to the
3435
+ control by aria-describedby. Page text: it keeps the page direction
3436
+ (see forms/_field-direction.scss) and aligns to the start edge, so it
3437
+ reads right to left in Arabic. */
3438
+ .ef-field-hint {
3439
+ display: block;
3440
+ margin-top: 4px;
3441
+ font-size: 12px;
3442
+ line-height: 1.35;
3443
+ color: var(--text-mute);
3444
+ text-align: start;
3445
+ }
3446
+
3387
3447
  /* Native select chevron — replaces the browser default. */
3388
3448
  .ef-select-native,
3389
3449
  .ef-field select {
@@ -3396,6 +3456,13 @@
3396
3456
  cursor: pointer;
3397
3457
  }
3398
3458
 
3459
+ /* The chevron is a background image, which has no logical position: in Arabic
3460
+ the reserved padding sits on the left, so the chevron moves there too. */
3461
+ [dir='rtl'] .ef-select-native,
3462
+ [dir='rtl'] .ef-field select {
3463
+ background-position: left 12px center;
3464
+ }
3465
+
3399
3466
  /* Native checkbox — keeps the underlying <input> for native keyboard
3400
3467
  handling, hidden visually; the .ef-checkbox-box gets the visuals. */
3401
3468
  .ef-checkbox-native {
@@ -3485,7 +3552,10 @@
3485
3552
  border: 1px solid var(--rule);
3486
3553
  border-radius: var(--r-md);
3487
3554
  overflow: hidden;
3488
- min-height: 40px;
3555
+ /* border-box, as .ef-input: the 1px border sits inside --hit-field, so
3556
+ a grouped field (suffix, stepper, currency) matches a bare one. */
3557
+ box-sizing: border-box;
3558
+ min-height: var(--hit-field);
3489
3559
  width: 100%;
3490
3560
  transition:
3491
3561
  border-color var(--t-fast) var(--ease-out),
@@ -3503,6 +3573,10 @@
3503
3573
  }
3504
3574
 
3505
3575
  .ef-input-group input {
3576
+ /* The lib owns the box model and line height of every part of a grouped
3577
+ field, as .ef-input does, so it measures the same whether or not the
3578
+ app loads a CSS reset (ERP loads Tailwind without preflight). */
3579
+ box-sizing: border-box;
3506
3580
  flex: 1;
3507
3581
  border: 0;
3508
3582
  background: transparent;
@@ -3510,11 +3584,13 @@
3510
3584
  padding: 10px 12px;
3511
3585
  font: inherit;
3512
3586
  font-size: 14px;
3587
+ line-height: 1.2;
3513
3588
  color: var(--text);
3514
3589
  min-width: 0;
3515
3590
  }
3516
3591
 
3517
3592
  .ef-input-group .ef-input-addon {
3593
+ box-sizing: border-box;
3518
3594
  background: var(--paper);
3519
3595
  padding: 9px 12px;
3520
3596
  font-family: var(--font-mono);
@@ -3536,7 +3612,16 @@
3536
3612
  /* Stepper buttons (±) sit at either end of the group. They reuse the
3537
3613
  addon base so border weights stay consistent; the button override
3538
3614
  gives them affordance (cursor, hover/active feedback). */
3615
+ /* A currency symbol is a word, not a code: the mono face drew the Arabic
3616
+ "د.م." with detached, oversized dots. The interface face renders it
3617
+ as the locale writes it. */
3618
+ .ef-input-group .ef-input-addon.is-currency {
3619
+ font-family: inherit;
3620
+ font-size: 13px;
3621
+ }
3622
+
3539
3623
  .ef-input-group button.ef-input-addon.is-stepper {
3624
+ box-sizing: border-box;
3540
3625
  appearance: none;
3541
3626
  background: var(--paper);
3542
3627
  border: 0;
@@ -3619,8 +3704,9 @@ input.ef-input[type='number'] {
3619
3704
  background: var(--paper-alt);
3620
3705
  border: 1px solid var(--rule);
3621
3706
  border-radius: var(--r-md);
3622
- min-height: var(--hit-base);
3623
- padding: 0 10px 0 14px;
3707
+ min-height: var(--hit-field);
3708
+ padding-block: 0;
3709
+ padding-inline: 14px 10px;
3624
3710
  display: inline-flex;
3625
3711
  align-items: center;
3626
3712
  gap: var(--es-gap);
@@ -3657,7 +3743,7 @@ input.ef-input[type='number'] {
3657
3743
  .es--sm.p-multiselect {
3658
3744
  font-size: 12.5px;
3659
3745
  min-height: 32px;
3660
- padding: 0 8px 0 12px;
3746
+ padding-inline: 12px 8px;
3661
3747
  border-radius: var(--r-sm);
3662
3748
  }
3663
3749
 
@@ -4278,6 +4364,19 @@ input.ef-input[type='number'] {
4278
4364
  flex: 1;
4279
4365
  }
4280
4366
 
4367
+ /* Product picker (ef-product-picker): 720px on a tablet or desktop, between
4368
+ the lg and xl presets; three classes so it beats the size preset. Rows are
4369
+ 48px (--hit-touch) at every width, since the whole row is the toggle. */
4370
+ .es-dialog.ef-product-picker-dialog.p-dialog {
4371
+ width: 720px;
4372
+ max-width: 92vw;
4373
+ }
4374
+
4375
+ .ef-product-picker-dialog .tbl tbody td {
4376
+ --tbl-cell-pad-block: 6px;
4377
+ height: var(--hit-touch);
4378
+ }
4379
+
4281
4380
  /* ── Header inner structure (rendered via #header content template).
4282
4381
  `flex:1` on the title-block keeps the close right-aligned with or
4283
4382
  without the leading icon. */
@@ -4676,6 +4775,14 @@ input.ef-input[type='number'] {
4676
4775
  margin: calc((var(--hit-touch) - var(--ef-clear-size)) / 2);
4677
4776
  }
4678
4777
 
4778
+ /* Inside a grouped field the clear is a flex item, so its block margin
4779
+ sized the group: 48px plus the group's 1px borders drew a 50px field
4780
+ beside 48px ones. The group's inner height is the whole target here;
4781
+ the margin stops 1px short on each side so the group stays 48px. */
4782
+ .ef-input-group .ef-clear {
4783
+ margin-block: calc((var(--hit-touch) - var(--ef-clear-size)) / 2 - 1px);
4784
+ }
4785
+
4679
4786
  /* The select / multiselect ✕ sits in the trigger's flex row, between the
4680
4787
  label and the caret, 10px (--es-gap) from each. It reserves only the
4681
4788
  inline room its 48px target still lacks after that gap, so the target
@@ -4770,33 +4877,60 @@ input.ef-input[type='number'] {
4770
4877
 
4771
4878
  /* Toolbar buttons collapse to icon-only below 767px, so the blanket
4772
4879
  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.
4880
+ produced a lozenge four-across in the header. On a touch phone the
4881
+ button is now a true 48px (--hit-touch) square: the old 40px box with an
4882
+ invisible 48px slop measured 32px to anyone checking the box, and a
4883
+ detail screen keeps at most a few of them inline because
4884
+ ef-detail-toolbar moves the rest into its overflow menu.
4777
4885
 
4778
4886
  Scoped to the same 767px as the rule that hides the label. Keyed on
4779
4887
  pointer type alone it also caught every tablet, which kept its label
4780
4888
  and got a 40px box: "Nouvelle commande" spilled out of the button and
4781
4889
  the detail actions piled into each other. Above 767px a touch screen
4782
- keeps the labelled button, 48px tall from the generic rule above. */
4890
+ keeps the labelled button, 48px tall from the generic rule above.
4891
+ Desktop (fine pointer) is unchanged. */
4783
4892
  @media (pointer: coarse) and (max-width: 767px) {
4784
4893
  .detail-toolbar .right .btn,
4785
4894
  .search-toolbar .right .btn {
4786
- position: relative;
4787
- inline-size: var(--hit-base);
4788
- block-size: var(--hit-base);
4895
+ inline-size: var(--hit-touch);
4896
+ block-size: var(--hit-touch);
4789
4897
  min-inline-size: 0;
4790
4898
  min-block-size: 0;
4791
4899
  padding: 0;
4792
4900
  }
4901
+ }
4793
4902
 
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);
4903
+ /* A keepInView action is the screen's next step (confirm, prepare, ...): on a
4904
+ phone it keeps its word beside the icon. Icons alone could not tell
4905
+ "Confirmer" (pi-check) from "Livrer" (pi-check-circle). Declared after the
4906
+ two rules above that hide toolbar labels and square the buttons, because
4907
+ a media query adds no specificity. */
4908
+ @media (max-width: 767px) {
4909
+ .detail-toolbar .right .btn.btn--keep-label > span {
4910
+ display: inline;
4911
+ }
4912
+
4913
+ .detail-toolbar .right .btn.btn--keep-label {
4914
+ padding: 7px 14px;
4915
+ }
4916
+ }
4917
+
4918
+ @media (pointer: coarse) and (max-width: 767px) {
4919
+ .detail-toolbar .right .btn.btn--keep-label {
4920
+ inline-size: auto;
4921
+ padding: 0 16px;
4922
+ }
4923
+ }
4924
+
4925
+ /* The toolbar's overflow trigger is the shared row-actions kebab. Inside
4926
+ the toolbar it is a real 48px target (not the table's overhang trick,
4927
+ which needs a row to reserve the margin), and a flex item that may not
4928
+ shrink. */
4929
+ @media (pointer: coarse) {
4930
+ .detail-toolbar .right .row-actions__trigger {
4931
+ --ef-row-trigger-w: var(--hit-touch);
4932
+ --ef-row-trigger-h: var(--hit-touch);
4933
+ margin: 0;
4800
4934
  }
4801
4935
  }
4802
4936
 
@@ -4816,3 +4950,87 @@ input.ef-input[type='number'] {
4816
4950
  --app-bottom-bar: 64px;
4817
4951
  }
4818
4952
  }
4953
+
4954
+ /* Product picker on a phone: edge to edge, not the 48px-gutter full preset.
4955
+ The body scrolls between the fixed header and footer. */
4956
+ @media (max-width: 767px) {
4957
+ .es-dialog.ef-product-picker-dialog--fullscreen.p-dialog {
4958
+ width: 100vw;
4959
+ max-width: 100vw;
4960
+ height: 100dvh;
4961
+ max-height: 100dvh;
4962
+ margin: 0;
4963
+ border-radius: 0;
4964
+ }
4965
+
4966
+ .ef-product-picker-dialog--fullscreen .p-dialog-content {
4967
+ max-height: none;
4968
+ flex: 1;
4969
+ }
4970
+ }
4971
+
4972
+ /* ef-file-upload on a touch device: the painted button reaches the 48px touch
4973
+ target. By pointer rather than width, like ef-pill-group, so a touch laptop
4974
+ or tablet gets it too. Last in the file so the base .btn rule does not win. */
4975
+ @media (pointer: coarse) {
4976
+ .ef-file-upload .btn {
4977
+ min-height: var(--hit-touch);
4978
+ }
4979
+ }
4980
+
4981
+ /* -- Order builder: room for its floating total ----------------------
4982
+ ef-order-builder pins its total pill (`.ob-fab`, --ef-order-fab-h tall)
4983
+ 20px above the bottom of the viewport's usable area: above the phone
4984
+ tab bar, which is an in-flow row under the scroll area, so the scroll
4985
+ area already ends where the bar begins. The pill shows up to 1279px,
4986
+ and on a wider screen once the builder is collapsed.
4987
+
4988
+ The reserve goes at the end of the scroll area, not in the builder, so
4989
+ it follows whatever the host renders after the builder (a delivery card,
4990
+ say) and nothing but the pill's own footprint is left blank: offset +
4991
+ pill + one grid gap, plus the home indicator where no tab bar already
4992
+ pads it. The tab bar is not added; the scroll area already stops above
4993
+ it.
4994
+
4995
+ `.order-builder .ob-fab` inside :has() rather than the element name, so
4996
+ the selector outranks ef-app-main's own (encapsulated) padding rule.
4997
+ Last in the file, after every rule it overrides. */
4998
+ :root {
4999
+ --ef-order-fab-h: 56px;
5000
+ }
5001
+
5002
+ /* Phone: the tab bar is in flow under the scroll area and already pads the
5003
+ home indicator, so the scroll area ends above it and the reserve does not
5004
+ add env(safe-area-inset-bottom) again (34px of blank space on a notched
5005
+ iPhone otherwise). */
5006
+ @media (max-width: 767px) {
5007
+ .ef-app-main__content:has(.order-builder .ob-fab) {
5008
+ padding-block-end: calc(20px + var(--ef-order-fab-h) + var(--s-3));
5009
+ }
5010
+ }
5011
+
5012
+ /* Tablet and collapsed desktop: no tab bar, so the scroll area reaches the
5013
+ bottom edge and the pill clears the home indicator itself. */
5014
+ @media (min-width: 768px) and (max-width: 1279px) {
5015
+ .ef-app-main__content:has(.order-builder .ob-fab) {
5016
+ padding-block-end: calc(env(safe-area-inset-bottom, 0px) + 20px + var(--ef-order-fab-h) + var(--s-3));
5017
+ }
5018
+ }
5019
+
5020
+ @media (min-width: 1280px) {
5021
+ .ef-app-main__content:has(.order-builder.ob-collapsed) {
5022
+ padding-block-end: calc(env(safe-area-inset-bottom, 0px) + 20px + var(--ef-order-fab-h) + var(--s-3));
5023
+ }
5024
+ }
5025
+
5026
+ /* .f-more ("More filters") on a touch device: reaches the 48px touch target.
5027
+ By pointer rather than width, like the other touch rules. Last in the file
5028
+ so the base .f-more min-height does not win. The row aligns its items to
5029
+ center, so the pills beside it keep their own height. */
5030
+ @media (pointer: coarse) {
5031
+ .f-pill,
5032
+ .f-more,
5033
+ .f-clear {
5034
+ min-height: var(--hit-touch);
5035
+ }
5036
+ }