@filip.mazev/blocks 1.4.15 → 1.4.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.
@@ -1,5 +1,10 @@
1
+ @use '@angular/cdk' as cdk;
2
+
1
3
  @forward 'motion';
2
4
 
5
+ // Emit the CDK utility once through the shared global accessibility entry point.
6
+ @include cdk.a11y-visually-hidden;
7
+
3
8
  :root {
4
9
  --bx-reduced-motion: 0;
5
10
 
@@ -14,9 +14,18 @@
14
14
  display: none;
15
15
  }
16
16
 
17
- &:focus-within:not(.is-disabled, :disabled),
18
- &:focus-visible:not(:disabled),
19
- &.is-open:not(:disabled) {
17
+ &:focus-within:not(
18
+ .is-disabled,
19
+ :disabled,
20
+ .is-readonly,
21
+ [readonly],
22
+ [aria-readonly='true'],
23
+ :has([readonly], [aria-readonly='true'])
24
+ ),
25
+ &:focus-visible:not(:disabled, [readonly], [aria-readonly='true']),
26
+ &.is-open:not(:disabled),
27
+ &.is-readonly.is-keyboard-focused:focus-within,
28
+ &:has(.bx-readonly.is-keyboard-focused):focus-within {
20
29
  --bx-glass-edge: var(--bx-border-brand);
21
30
 
22
31
  outline: 2px solid var(--bx-border-brand);
@@ -5,3 +5,4 @@
5
5
  @forward 'motion';
6
6
  @forward 'field-state';
7
7
  @forward 'choice-finish';
8
+ @forward 'text-overflow';
@@ -15,3 +15,4 @@
15
15
  @forward 'slider';
16
16
  @forward 'validation';
17
17
  @forward 'overlay';
18
+ @forward 'text';
@@ -18,8 +18,8 @@
18
18
  @mixin thumb {
19
19
  appearance: none;
20
20
  box-sizing: border-box;
21
- width: spacing(6);
22
- height: spacing(6);
21
+ width: var(--bx-slider-thumb-size, #{spacing(6)});
22
+ height: var(--bx-slider-thumb-size, #{spacing(6)});
23
23
  border-radius: radius(pill);
24
24
  border: 1px solid var(--bx-border-strong);
25
25
  background: var(--bx-bg-surface);
@@ -50,7 +50,38 @@
50
50
  display: block;
51
51
  }
52
52
 
53
- // The native thumb remains the hit target; the opaque decorative thumb reflects nearby colors.
53
+ &::before {
54
+ content: '';
55
+ display: var(--bx-slider-fill-display, block);
56
+ position: absolute;
57
+ top: var(--bx-slider-center-y, 50%);
58
+ left: var(--bx-slider-track-start, 0);
59
+ width: calc(
60
+ var(--bx-slider-thumb-size, #{spacing(6)}) / 2 + var(--bx-slider-position, 0.5) *
61
+ (var(--bx-slider-track-width, 100%) - var(--bx-slider-thumb-size, #{spacing(6)}))
62
+ );
63
+ height: spacing(2);
64
+ transform: translateY(-50%);
65
+ border-radius: radius(pill);
66
+ background: var(--bx-accent);
67
+ pointer-events: none;
68
+ z-index: 1;
69
+ }
70
+
71
+ &:dir(rtl)::before {
72
+ left: auto;
73
+ right: var(--bx-slider-track-start, 0);
74
+ }
75
+
76
+ input.bx-slider::-webkit-slider-runnable-track {
77
+ background: var(--bx-bg-neutral);
78
+ }
79
+
80
+ input.bx-slider::-moz-range-track {
81
+ background: var(--bx-bg-neutral);
82
+ }
83
+
84
+ // The native thumb remains the hit target; the decorative lens paints over it.
54
85
  input.bx-slider::-webkit-slider-thumb {
55
86
  background: transparent;
56
87
  border-color: transparent;
@@ -73,11 +104,41 @@
73
104
  position: absolute;
74
105
  top: var(--bx-slider-center-y, 50%);
75
106
  left: calc(
76
- var(--bx-slider-track-start, 0px) + var(--bx-slider-position, 0.5) *
77
- (var(--bx-slider-track-width, 100%) - #{spacing(6)})
107
+ var(--bx-slider-track-start, 0px) +
108
+ var(--bx-slider-physical-position, var(--bx-slider-position, 0.5)) *
109
+ (var(--bx-slider-track-width, 100%) - var(--bx-slider-thumb-size, #{spacing(6)}))
78
110
  );
79
111
  transform: translateY(-50%);
80
112
  pointer-events: none;
113
+ z-index: 2;
114
+ overflow: hidden;
115
+ background:
116
+ radial-gradient(circle at 35% 0%, rgb(255 255 255 / 45%), transparent 65%),
117
+ color-mix(in srgb, var(--bx-bg-surface) 24%, transparent);
118
+ border-color: color-mix(in srgb, var(--bx-text-primary) 35%, var(--bx-border-strong));
119
+ backdrop-filter: blur(var(--bx-slider-lens-blur, 1.5px));
120
+
121
+ --bx-slider-lens-width: calc(
122
+ var(--bx-slider-track-width) * var(--bx-slider-magnification, 1.5)
123
+ );
124
+ --bx-slider-lens-inset: calc(
125
+ var(--bx-slider-thumb-size, #{spacing(6)}) / 2 * var(--bx-slider-magnification, 1.5)
126
+ );
127
+ --bx-slider-lens-travel: calc(
128
+ (var(--bx-slider-track-width) - var(--bx-slider-thumb-size, #{spacing(6)})) *
129
+ var(--bx-slider-magnification, 1.5)
130
+ );
131
+ --bx-slider-lens-start-offset: calc(
132
+ var(--bx-slider-lens-inset) + var(--bx-slider-lens-start, 0) * var(--bx-slider-lens-travel)
133
+ );
134
+ --bx-slider-lens-end-offset: calc(
135
+ var(--bx-slider-lens-inset) + var(--bx-slider-lens-end, var(--bx-slider-position, 0.5)) *
136
+ var(--bx-slider-lens-travel)
137
+ );
138
+
139
+ &:dir(rtl) {
140
+ --bx-slider-physical-position: calc(1 - var(--bx-slider-position, 0.5));
141
+ }
81
142
 
82
143
  &::after {
83
144
  @include bx-transition(opacity, 0.2s, ease-out);
@@ -101,6 +162,38 @@
101
162
  }
102
163
  }
103
164
 
165
+ // Reuse the rail geometry in CSS instead of sampling screen pixels on drag.
166
+ // A separate rounded fill keeps the magnified selection's end caps curved.
167
+ .bx-slider-lens {
168
+ display: var(--bx-slider-lens-display, block);
169
+ position: absolute;
170
+ top: 50%;
171
+ left: calc(
172
+ var(--bx-slider-thumb-size, #{spacing(6)}) / 2 -
173
+ 1px - var(--bx-slider-lens-inset) - var(
174
+ --bx-slider-physical-position,
175
+ var(--bx-slider-position, 0.5)
176
+ ) *
177
+ var(--bx-slider-lens-travel)
178
+ );
179
+ width: var(--bx-slider-lens-width);
180
+ height: calc(#{spacing(2)} * var(--bx-slider-magnification, 1.5));
181
+ transform: translateY(-50%);
182
+ border-radius: radius(pill);
183
+ background: var(--bx-slider-lens-track-color, var(--bx-bg-neutral));
184
+ filter: blur(var(--bx-slider-lens-blur, 1.5px));
185
+
186
+ &::after {
187
+ content: '';
188
+ position: absolute;
189
+ inset-block: 0;
190
+ inset-inline: var(--bx-slider-lens-start-offset)
191
+ calc(var(--bx-slider-lens-width) - var(--bx-slider-lens-end-offset));
192
+ background: var(--bx-accent);
193
+ border-radius: inherit;
194
+ }
195
+ }
196
+
104
197
  input.bx-slider {
105
198
  appearance: none;
106
199
  width: 100%;
@@ -127,7 +220,7 @@ input.bx-slider {
127
220
  &::-webkit-slider-thumb {
128
221
  @include thumb;
129
222
 
130
- margin-top: calc((#{spacing(2)} - #{spacing(6)}) / 2 - 1px);
223
+ margin-top: calc((#{spacing(2)} - var(--bx-slider-thumb-size, #{spacing(6)})) / 2 - 1px);
131
224
  }
132
225
 
133
226
  &::-moz-range-thumb {
@@ -140,6 +233,10 @@ input.bx-slider {
140
233
  border-radius: radius(pill);
141
234
  }
142
235
 
236
+ &[readonly] {
237
+ cursor: default;
238
+ }
239
+
143
240
  &:disabled {
144
241
  opacity: 0.5;
145
242
  cursor: not-allowed;
@@ -147,6 +244,8 @@ input.bx-slider {
147
244
  }
148
245
 
149
246
  @mixin opaque-thumb {
247
+ --bx-slider-lens-display: none;
248
+
150
249
  background: var(--bx-bg-surface);
151
250
  backdrop-filter: none;
152
251
  }
@@ -217,3 +316,14 @@ input.bx-slider {
217
316
  box-shadow: none;
218
317
  }
219
318
  }
319
+
320
+ .bx-slider-value-label {
321
+ display: block;
322
+ color: var(--bx-text-secondary);
323
+ font-size: 0.75rem;
324
+ text-align: center;
325
+ }
326
+
327
+ .bx-slider-value-label[hidden] {
328
+ display: none;
329
+ }
@@ -0,0 +1,7 @@
1
+ // Shared ellipsis mechanics. Include through helpers when styling a text surface.
2
+ @mixin truncate-text {
3
+ min-inline-size: 0;
4
+ overflow: hidden;
5
+ text-overflow: ellipsis;
6
+ white-space: nowrap;
7
+ }
@@ -0,0 +1,5 @@
1
+ @use 'text-overflow' as *;
2
+
3
+ .bx-truncate {
4
+ @include truncate-text;
5
+ }
@@ -1,4 +1,5 @@
1
1
  @use 'field-state' as *;
2
+ @use 'mixins' as *;
2
3
 
3
4
  // A projected surface inside a form field receives the complete field material,
4
5
  // including its valid focus state, without application-specific effects CSS.
@@ -20,3 +21,50 @@
20
21
  outline-color: Mark;
21
22
  }
22
23
  }
24
+
25
+ .bx-native-control {
26
+ box-sizing: border-box;
27
+ inline-size: 100%;
28
+ padding: spacing(3);
29
+ border: 1px solid var(--bx-border-strong);
30
+ border-radius: radius(sm);
31
+ background: var(--bx-bg-neutral);
32
+ color: var(--bx-text-primary);
33
+ font: inherit;
34
+
35
+ &:focus-visible:not([readonly], [aria-readonly='true']),
36
+ &.is-keyboard-focused:focus-visible {
37
+ outline: 2px solid var(--bx-border-brand);
38
+ outline-offset: 2px;
39
+ }
40
+
41
+ &:disabled {
42
+ opacity: 0.6;
43
+ cursor: not-allowed;
44
+ }
45
+
46
+ &[readonly]:not(:disabled) {
47
+ cursor: default;
48
+ }
49
+
50
+ &[aria-invalid='true'] {
51
+ border-color: var(--bx-border-danger);
52
+ }
53
+ }
54
+
55
+ .bx-field-prefix,
56
+ .bx-field-suffix {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ flex: 0 0 auto;
60
+ }
61
+
62
+ .bx-field-prefix {
63
+ padding-inline: var(--bx-field-adornment-padding, #{spacing(3)})
64
+ var(--bx-field-adornment-gap, #{spacing(2)});
65
+ }
66
+
67
+ .bx-field-suffix {
68
+ padding-inline: var(--bx-field-adornment-gap, #{spacing(2)})
69
+ var(--bx-field-adornment-padding, #{spacing(3)});
70
+ }
@@ -1,11 +1,12 @@
1
1
  import * as i0 from "@angular/core";
2
- import { DOCUMENT, DestroyRef, Directive, Injectable, InjectionToken, NgZone, PLATFORM_ID, computed, effect, inject, input, isDevMode, signal } from "@angular/core";
2
+ import { DOCUMENT, DestroyRef, Directive, ElementRef, Injectable, InjectionToken, NgZone, PLATFORM_ID, afterNextRender, booleanAttribute, computed, effect, inject, input, isDevMode, signal } from "@angular/core";
3
3
  import { DOCUMENT as DOCUMENT$1, isPlatformBrowser } from "@angular/common";
4
4
  import { fromEvent } from "rxjs";
5
5
  import { debounceTime, distinctUntilChanged, map } from "rxjs/operators";
6
- import { toObservable } from "@angular/core/rxjs-interop";
6
+ import { takeUntilDestroyed, toObservable } from "@angular/core/rxjs-interop";
7
7
  import { configureAppearance } from "@filip.mazev/blocks/effects/runtime";
8
8
  import { ConnectionPositionPair, FlexibleConnectedPositionStrategy, Overlay } from "@angular/cdk/overlay";
9
+ import { FocusMonitor } from "@angular/cdk/a11y";
9
10
  import { NgControl, Validators } from "@angular/forms";
10
11
  var DesktopOS;
11
12
  (function(DesktopOS) {
@@ -753,14 +754,13 @@ i0.ɵɵngDeclareClassMetadata({
753
754
  }]
754
755
  });
755
756
  var BxBaseControl = class BxBaseControl {
756
- isFieldRequired = computed(() => {
757
- if (this.required && this.required()) return true;
758
- const control = this.ngControl?.control;
759
- return control?.hasValidator ? control.hasValidator(Validators.required) : false;
760
- }, ...ngDevMode ? [{ debugName: "isFieldRequired" }] : /* istanbul ignore next */ []);
757
+ focusMonitor = inject(FocusMonitor);
758
+ focusElement = inject(ElementRef).nativeElement;
759
+ destroyRef = inject(DestroyRef);
761
760
  internalValue = signal(null, ...ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []);
762
761
  isTouched = signal(false, ...ngDevMode ? [{ debugName: "isTouched" }] : /* istanbul ignore next */ []);
763
762
  isDisabled = signal(false, ...ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []);
763
+ keyboardFocused = signal(false, ...ngDevMode ? [{ debugName: "keyboardFocused" }] : /* istanbul ignore next */ []);
764
764
  ngControl = inject(NgControl, {
765
765
  optional: true,
766
766
  self: true
@@ -772,18 +772,31 @@ var BxBaseControl = class BxBaseControl {
772
772
  ariaLabel = input(void 0, ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
773
773
  errorMessages = input({}, ...ngDevMode ? [{ debugName: "errorMessages" }] : /* istanbul ignore next */ []);
774
774
  required = input(false, ...ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []);
775
+ readonly = input(false, {
776
+ ...ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {},
777
+ transform: booleanAttribute
778
+ });
779
+ readOnly = input(false, {
780
+ ...ngDevMode ? { debugName: "readOnly" } : /* istanbul ignore next */ {},
781
+ transform: booleanAttribute
782
+ });
783
+ isReadonly = computed(() => this.readonly() || this.readOnly(), ...ngDevMode ? [{ debugName: "isReadonly" }] : /* istanbul ignore next */ []);
775
784
  loading = input(false, ...ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []);
776
785
  disabled = input(false, ...ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []);
777
786
  onChange = () => {};
778
787
  onTouched = () => {};
779
788
  constructor() {
789
+ afterNextRender(() => {
790
+ this.focusMonitor.monitor(this.focusElement, true).pipe(takeUntilDestroyed(this.destroyRef)).subscribe((origin) => this.keyboardFocused.set(origin === "keyboard"));
791
+ this.destroyRef.onDestroy(() => this.focusMonitor.stopMonitoring(this.focusElement));
792
+ });
793
+ effect(() => {
794
+ this.isDisabled.set(this.disabled() || this.ngControl?.disabled === true);
795
+ });
780
796
  if (this.ngControl) this.ngControl.valueAccessor = this;
781
797
  }
782
798
  ngOnInit() {
783
799
  this.isDisabled.set(this.disabled() || this.ngControl?.disabled === true);
784
- if (isDevMode() && this.ngControl?.control?.validator) {
785
- if (!(Object.keys(this.errorMessages()).length > 0)) console.error(`[Blocks UI Strict Mode] The form control for this field has validators, but the [errorMessages] input is empty. You must provide error messages for all applied validators.`);
786
- }
787
800
  }
788
801
  writeValue(value) {
789
802
  this.internalValue.set(value);
@@ -798,15 +811,20 @@ var BxBaseControl = class BxBaseControl {
798
811
  this.isDisabled.set(isDisabled);
799
812
  }
800
813
  updateValue(value) {
801
- if (this.isDisabled()) return;
814
+ if (this.isDisabled() || this.isReadonly()) return;
802
815
  this.internalValue.set(value);
803
816
  this.onChange(value);
804
817
  }
805
818
  markAsTouched() {
806
- if (!this.isTouched()) {
807
- this.isTouched.set(true);
808
- this.onTouched();
809
- }
819
+ this.isTouched.set(true);
820
+ this.onTouched();
821
+ }
822
+ onControlPointerDown() {
823
+ if (this.isReadonly()) this.keyboardFocused.set(false);
824
+ }
825
+ isFieldRequired() {
826
+ const control = this.ngControl?.control;
827
+ return this.required() || !!control?.hasValidator?.(Validators.required) || !!control?.hasValidator?.(Validators.requiredTrue);
810
828
  }
811
829
  get errorMessage() {
812
830
  if (this.ngControl?.invalid && (this.ngControl.touched || this.ngControl.dirty)) {
@@ -815,7 +833,9 @@ var BxBaseControl = class BxBaseControl {
815
833
  const firstKey = Object.keys(errors)[0];
816
834
  const customErrorMap = this.errorMessages();
817
835
  if (customErrorMap && customErrorMap[firstKey]) return customErrorMap[firstKey];
818
- if (typeof errors[firstKey] === "string") return errors[firstKey];
836
+ const error = errors[firstKey];
837
+ if (typeof error === "string") return error;
838
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
819
839
  if (isDevMode()) console.error(`[Blocks UI Strict Mode] Validation failed for key '${firstKey}', but no message was provided in [errorMessages].`);
820
840
  return `[Missing error message: ${firstKey}]`;
821
841
  }
@@ -883,6 +903,20 @@ var BxBaseControl = class BxBaseControl {
883
903
  isRequired: false,
884
904
  transformFunction: null
885
905
  },
906
+ readonly: {
907
+ classPropertyName: "readonly",
908
+ publicName: "readonly",
909
+ isSignal: true,
910
+ isRequired: false,
911
+ transformFunction: null
912
+ },
913
+ readOnly: {
914
+ classPropertyName: "readOnly",
915
+ publicName: "readOnly",
916
+ isSignal: true,
917
+ isRequired: false,
918
+ transformFunction: null
919
+ },
886
920
  loading: {
887
921
  classPropertyName: "loading",
888
922
  publicName: "loading",
@@ -898,6 +932,10 @@ var BxBaseControl = class BxBaseControl {
898
932
  transformFunction: null
899
933
  }
900
934
  },
935
+ host: {
936
+ listeners: { "pointerdown": "onControlPointerDown()" },
937
+ properties: { "class.bx-readonly": "isReadonly()" }
938
+ },
901
939
  ngImport: i0
902
940
  });
903
941
  };
@@ -906,7 +944,13 @@ i0.ɵɵngDeclareClassMetadata({
906
944
  version: "22.2.3",
907
945
  ngImport: i0,
908
946
  type: BxBaseControl,
909
- decorators: [{ type: Directive }],
947
+ decorators: [{
948
+ type: Directive,
949
+ args: [{ host: {
950
+ "[class.bx-readonly]": "isReadonly()",
951
+ "(pointerdown)": "onControlPointerDown()"
952
+ } }]
953
+ }],
910
954
  ctorParameters: () => [],
911
955
  propDecorators: {
912
956
  label: [{
@@ -965,6 +1009,22 @@ i0.ɵɵngDeclareClassMetadata({
965
1009
  required: false
966
1010
  }]
967
1011
  }],
1012
+ readonly: [{
1013
+ type: i0.Input,
1014
+ args: [{
1015
+ isSignal: true,
1016
+ alias: "readonly",
1017
+ required: false
1018
+ }]
1019
+ }],
1020
+ readOnly: [{
1021
+ type: i0.Input,
1022
+ args: [{
1023
+ isSignal: true,
1024
+ alias: "readOnly",
1025
+ required: false
1026
+ }]
1027
+ }],
968
1028
  loading: [{
969
1029
  type: i0.Input,
970
1030
  args: [{
@@ -1271,6 +1331,11 @@ function trackOverlayOrigin(overlay, origin) {
1271
1331
  host.classList.remove("bx-overlay-tracked");
1272
1332
  };
1273
1333
  }
1274
- export { BREAKPOINTS, BX_I18N, BxA11yService, BxBaseControl, BxOverlayPositionService, BxShortcutService, DEFAULT_APPEARANCE_CONFIG, DEFAULT_DEBOUNCE_TIME, DesktopOS, DeviceTypeService, FALLBACK_COLOR, MobileOS, PALETTE_NAMES, SCROLL_LOCK_INSTANCE_IDENTIFIER, SEMANTIC_PAIRS, SHADE_NUMBERS, ScrollLockService, THEME_PALETTES, TOKEN_COLOR_DELIMITER, TOKEN_DELIMITER, TOKEN_PREFIX, ThemingService, WindowDimensionsService, getComplementaryToken, inheritOverlayTheme, invertPaletteToken, isOSShortcut, isPaletteToken, isTextWithKey, isThemedColor, resolveThemeId, resolveTokenToCssVar, trackOverlayOrigin, uuidv4 };
1334
+ function resolveText(text, adapter, params) {
1335
+ adapter?.version?.();
1336
+ if (text === void 0) return "";
1337
+ return isTextWithKey(text) ? adapter?.translate(text.key, params) ?? text.key : text;
1338
+ }
1339
+ export { BREAKPOINTS, BX_I18N, BxA11yService, BxBaseControl, BxOverlayPositionService, BxShortcutService, DEFAULT_APPEARANCE_CONFIG, DEFAULT_DEBOUNCE_TIME, DesktopOS, DeviceTypeService, FALLBACK_COLOR, MobileOS, PALETTE_NAMES, SCROLL_LOCK_INSTANCE_IDENTIFIER, SEMANTIC_PAIRS, SHADE_NUMBERS, ScrollLockService, THEME_PALETTES, TOKEN_COLOR_DELIMITER, TOKEN_DELIMITER, TOKEN_PREFIX, ThemingService, WindowDimensionsService, getComplementaryToken, inheritOverlayTheme, invertPaletteToken, isOSShortcut, isPaletteToken, isTextWithKey, isThemedColor, resolveText, resolveThemeId, resolveTokenToCssVar, trackOverlayOrigin, uuidv4 };
1275
1340
 
1276
1341
  //# sourceMappingURL=filip.mazev-blocks-core.mjs.map