@basis-ng/primitives 0.0.1-alpha.105 → 0.0.1-alpha.107

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,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, Component, inject, ElementRef, signal, model, contentChildren, computed, effect, Injectable, contentChild, ViewEncapsulation, HostListener, Directive, forwardRef, linkedSignal, TemplateRef, ChangeDetectorRef, Pipe, afterRenderEffect, RendererFactory2, PLATFORM_ID } from '@angular/core';
2
+ import { input, output, Component, inject, ElementRef, signal, computed, model, booleanAttribute, forwardRef, ChangeDetectionStrategy, contentChildren, effect, Injectable, contentChild, ViewEncapsulation, HostListener, Directive, linkedSignal, TemplateRef, ChangeDetectorRef, Pipe, afterRenderEffect, RendererFactory2, PLATFORM_ID } from '@angular/core';
3
3
  import { NgIcon, provideIcons } from '@ng-icons/core';
4
- import { lucideX, lucideAArrowUp, lucideAArrowDown, lucideLoaderCircle, lucideLoader, lucideGripVertical } from '@ng-icons/lucide';
4
+ import { lucideX, lucideLoaderCircle, lucideLoader, lucideGripVertical } from '@ng-icons/lucide';
5
+ import { NG_VALUE_ACCESSOR, NgModel } from '@angular/forms';
5
6
  import { ActiveDescendantKeyManager, CdkTrapFocus } from '@angular/cdk/a11y';
6
7
  import * as i1 from '@angular/cdk/listbox';
7
8
  import { CdkOption, CdkListbox } from '@angular/cdk/listbox';
8
9
  import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
9
- import { NgModel, NG_VALUE_ACCESSOR } from '@angular/forms';
10
10
  import { Dialog as Dialog$1, CdkDialogContainer, DialogRef } from '@angular/cdk/dialog';
11
11
  import { CdkPortalOutlet } from '@angular/cdk/portal';
12
12
  import { CommonModule, NgTemplateOutlet, isPlatformBrowser } from '@angular/common';
@@ -248,50 +248,184 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImpor
248
248
  */
249
249
  class Checkbox {
250
250
  /**
251
- * Current boolean checked state.
251
+ * Internal checked state for the checkbox.
252
252
  */
253
- value = signal(false, ...(ngDevMode ? [{ debugName: "value" }] : []));
253
+ internalValue = signal(false, ...(ngDevMode ? [{ debugName: "internalValue" }] : []));
254
254
  /**
255
- * The host input element reference.
255
+ * Public checked state exposed to the template bindings.
256
256
  */
257
- el = inject(ElementRef);
257
+ checked = computed(() => this.internalValue(), ...(ngDevMode ? [{ debugName: "checked" }] : []));
258
258
  /**
259
- * Emitted when the checked state changes.
259
+ * Public two-way binding output emitted on user interaction.
260
260
  */
261
261
  valueChange = output();
262
262
  /**
263
263
  * Visual size of the checkbox.
264
264
  */
265
265
  size = model('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
266
- ngAfterViewInit() {
267
- this.value.set(this.el.nativeElement.checked);
266
+ /**
267
+ * Disabled flag coming from template bindings.
268
+ */
269
+ disabledBinding = input(false, ...(ngDevMode ? [{ debugName: "disabledBinding", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
270
+ /**
271
+ * Disabled flag controlled by Angular forms APIs.
272
+ */
273
+ disabledFromControl = signal(false, ...(ngDevMode ? [{ debugName: "disabledFromControl" }] : []));
274
+ /**
275
+ * Combined disabled state exposed to the template bindings.
276
+ */
277
+ isDisabled = computed(() => this.disabledBinding() || this.disabledFromControl(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
278
+ /**
279
+ * Internal change handler for the checkbox.
280
+ */
281
+ onChange = () => undefined;
282
+ /**
283
+ * Internal touched handler for the checkbox.
284
+ */
285
+ onTouched = () => undefined;
286
+ /**
287
+ * Toggle the checkbox when the user clicks on it.
288
+ */
289
+ onToggle() {
290
+ if (this.isDisabled()) {
291
+ return;
292
+ }
293
+ const next = !this.checked();
294
+ this.setChecked(next, true);
295
+ this.markTouched();
268
296
  }
269
297
  /**
270
- * Toggle the current value and emit the change.
298
+ * Prevent the page from scrolling when pressing the space key.
271
299
  */
272
- toggleValue() {
273
- const newValue = !this.value();
274
- this.value.set(newValue);
275
- this.valueChange.emit(newValue);
300
+ suppressSpace(event) {
301
+ if (!this.isDisabled()) {
302
+ event.preventDefault();
303
+ }
304
+ }
305
+ /**
306
+ * Mark the control as touched.
307
+ */
308
+ markTouched() {
309
+ this.onTouched();
310
+ }
311
+ /**
312
+ * Write the value to the internal form control.
313
+ * @param value - New value to write.
314
+ */
315
+ writeValue(value) {
316
+ this.internalValue.set(!!value);
317
+ }
318
+ /**
319
+ * Register a change handler for the checkbox.
320
+ * @param fn - Change callback.
321
+ */
322
+ registerOnChange(fn) {
323
+ this.onChange = fn;
324
+ }
325
+ /**
326
+ * Register a touched handler for the checkbox.
327
+ * @param fn - Touched callback.
328
+ */
329
+ registerOnTouched(fn) {
330
+ this.onTouched = fn;
331
+ }
332
+ /**
333
+ * Toggle disabled state on the checkbox.
334
+ * @param isDisabled - Whether the control is disabled.
335
+ */
336
+ setDisabledState(isDisabled) {
337
+ this.disabledFromControl.set(isDisabled);
338
+ }
339
+ /**
340
+ * Set the checked state internally and emit change events if needed.
341
+ * @param value - New checked value.
342
+ * @param emitChange - Whether to emit change events.
343
+ */
344
+ setChecked(value, emitChange) {
345
+ const normalized = !!value;
346
+ if (this.internalValue() !== normalized) {
347
+ this.internalValue.set(normalized);
348
+ }
349
+ if (emitChange) {
350
+ this.onChange(normalized);
351
+ this.valueChange.emit(normalized);
352
+ }
276
353
  }
277
354
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: Checkbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
278
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.6", type: Checkbox, isStandalone: true, selector: "input[b-checkbox]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", size: "sizeChange" }, host: { listeners: { "click": "toggleValue()", "keydown.enter": "toggleValue()", "keydown.space": "toggleValue()" }, properties: { "attr.role": "\"checkbox\"", "attr.checked": "value()", "attr.aria-checked": "value()" } }, ngImport: i0, template: ``, isInline: true });
355
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.6", type: Checkbox, isStandalone: true, selector: "button[b-checkbox]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabledBinding: { classPropertyName: "disabledBinding", publicName: "disabledBinding", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", size: "sizeChange" }, host: { attributes: { "type": "\"button\"" }, listeners: { "click": "onToggle()", "keydown.space": "suppressSpace($event)", "blur": "markTouched()" }, properties: { "attr.role": "\"checkbox\"", "attr.aria-checked": "checked()", "attr.data-state": "checked() ? \"checked\" : \"unchecked\"", "attr.aria-disabled": "isDisabled()", "disabled": "isDisabled()", "attr.data-disabled": "isDisabled() ? \"\" : null", "class.b-size-sm": "size() === \"sm\"", "class.b-size-md": "size() === \"md\"", "class.b-size-lg": "size() === \"lg\"" } }, providers: [
356
+ {
357
+ provide: NG_VALUE_ACCESSOR,
358
+ useExisting: forwardRef(() => Checkbox),
359
+ multi: true,
360
+ },
361
+ ], ngImport: i0, template: `
362
+ <span class="b-checkbox-indicator" aria-hidden="true">
363
+ @if (checked()) {
364
+ <svg
365
+ width="9"
366
+ height="9"
367
+ viewBox="0 0 9 9"
368
+ fill="currentColor"
369
+ xmlns="http://www.w3.org/2000/svg"
370
+ >
371
+ <path
372
+ fill-rule="evenodd"
373
+ clip-rule="evenodd"
374
+ d="M8.53547 0.62293C8.88226 0.849446 8.97976 1.3142 8.75325 1.66099L4.5083 8.1599C4.38833 8.34356 4.19397 8.4655 3.9764 8.49358C3.75883 8.52167 3.53987 8.45309 3.3772 8.30591L0.616113 5.80777C0.308959 5.52987 0.285246 5.05559 0.563148 4.74844C0.84105 4.44128 1.31533 4.41757 1.62249 4.69547L3.73256 6.60459L7.49741 0.840706C7.72393 0.493916 8.18868 0.396414 8.53547 0.62293Z"
375
+ />
376
+ </svg>
377
+ }
378
+ </span>
379
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
279
380
  }
280
381
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: Checkbox, decorators: [{
281
382
  type: Component,
282
383
  args: [{
283
- selector: 'input[b-checkbox]',
284
- template: ``,
384
+ selector: 'button[b-checkbox]',
385
+ template: `
386
+ <span class="b-checkbox-indicator" aria-hidden="true">
387
+ @if (checked()) {
388
+ <svg
389
+ width="9"
390
+ height="9"
391
+ viewBox="0 0 9 9"
392
+ fill="currentColor"
393
+ xmlns="http://www.w3.org/2000/svg"
394
+ >
395
+ <path
396
+ fill-rule="evenodd"
397
+ clip-rule="evenodd"
398
+ d="M8.53547 0.62293C8.88226 0.849446 8.97976 1.3142 8.75325 1.66099L4.5083 8.1599C4.38833 8.34356 4.19397 8.4655 3.9764 8.49358C3.75883 8.52167 3.53987 8.45309 3.3772 8.30591L0.616113 5.80777C0.308959 5.52987 0.285246 5.05559 0.563148 4.74844C0.84105 4.44128 1.31533 4.41757 1.62249 4.69547L3.73256 6.60459L7.49741 0.840706C7.72393 0.493916 8.18868 0.396414 8.53547 0.62293Z"
399
+ />
400
+ </svg>
401
+ }
402
+ </span>
403
+ `,
285
404
  host: {
405
+ type: '"button"',
286
406
  '[attr.role]': '"checkbox"',
287
- '[attr.checked]': 'value()',
288
- '[attr.aria-checked]': 'value()',
289
- '(click)': 'toggleValue()',
290
- '(keydown.enter)': 'toggleValue()',
291
- '(keydown.space)': 'toggleValue()',
407
+ '[attr.aria-checked]': 'checked()',
408
+ '[attr.data-state]': 'checked() ? "checked" : "unchecked"',
409
+ '[attr.aria-disabled]': 'isDisabled()',
410
+ '[disabled]': 'isDisabled()',
411
+ '[attr.data-disabled]': 'isDisabled() ? "" : null',
412
+ '[class.b-size-sm]': 'size() === "sm"',
413
+ '[class.b-size-md]': 'size() === "md"',
414
+ '[class.b-size-lg]': 'size() === "lg"',
415
+ '(click)': 'onToggle()',
416
+ '(keydown.space)': 'suppressSpace($event)',
417
+ '(blur)': 'markTouched()',
292
418
  },
419
+ providers: [
420
+ {
421
+ provide: NG_VALUE_ACCESSOR,
422
+ useExisting: forwardRef(() => Checkbox),
423
+ multi: true,
424
+ },
425
+ ],
426
+ changeDetection: ChangeDetectionStrategy.OnPush,
293
427
  }]
294
- }], propDecorators: { valueChange: [{ type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }, { type: i0.Output, args: ["sizeChange"] }] } });
428
+ }], propDecorators: { valueChange: [{ type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }, { type: i0.Output, args: ["sizeChange"] }], disabledBinding: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledBinding", required: false }] }] } });
295
429
 
296
430
  /**
297
431
  * Presents a list of selectable command options and manages keyboard navigation.
@@ -2188,48 +2322,54 @@ class SelectTrigger {
2188
2322
  */
2189
2323
  disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
2190
2324
  /**
2191
- * Whether the trigger is activated.
2325
+ * Whether the trigger has been activated to open the select.
2192
2326
  */
2193
2327
  triggered = signal(false, ...(ngDevMode ? [{ debugName: "triggered" }] : []));
2194
- /**
2195
- * Handle click events on the trigger.
2196
- */
2197
- handleClick() {
2198
- this.buttonClicked.emit();
2199
- this.triggered.set(!this.triggered());
2200
- }
2201
- /**
2202
- * Handle keydown events on the trigger.
2203
- * @param event - The keyboard event.
2204
- */
2205
- handleKeydown(event) {
2206
- if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
2207
- this.buttonClicked.emit();
2208
- this.triggered.set(event.key === 'ArrowUp');
2209
- }
2210
- }
2211
2328
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: SelectTrigger, deps: [], target: i0.ɵɵFactoryTarget.Component });
2212
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.6", type: SelectTrigger, isStandalone: true, selector: "button[b-select-trigger]", outputs: { buttonClicked: "buttonClicked" }, host: { listeners: { "keydown": "handleKeydown($event)", "click": "handleClick()" }, properties: { "disabled": "disabled()" } }, providers: [provideIcons({ lucideAArrowDown, lucideAArrowUp })], ngImport: i0, template: `
2329
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.6", type: SelectTrigger, isStandalone: true, selector: "button[b-select-trigger]", outputs: { buttonClicked: "buttonClicked" }, host: { listeners: { "keydown.arrowUp": "buttonClicked.emit()", "keydown.arrowDown": "buttonClicked.emit()", "click": "buttonClicked.emit()" }, properties: { "disabled": "disabled()" } }, ngImport: i0, template: `
2213
2330
  <ng-content />
2214
- <ng-icon [name]="triggered() ? 'lucide-a-arrow-up' : 'lucide-a-arrow-down'" />
2215
- `, isInline: true, dependencies: [{ kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }] });
2331
+ <svg
2332
+ [class.b-select-triggered]="triggered()"
2333
+ xmlns="http://www.w3.org/2000/svg"
2334
+ width="16"
2335
+ height="16"
2336
+ viewBox="0 0 24 24"
2337
+ fill="none"
2338
+ stroke="currentColor"
2339
+ stroke-width="2"
2340
+ stroke-linecap="round"
2341
+ stroke-linejoin="round"
2342
+ class="lucide lucide-chevron-down-icon lucide-chevron-down"
2343
+ >
2344
+ <path d="m6 9 6 6 6-6" />
2345
+ </svg>
2346
+ `, isInline: true, styles: [":host .b-select-triggered{transform:rotate(180deg)}\n"] });
2216
2347
  }
2217
2348
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: SelectTrigger, decorators: [{
2218
2349
  type: Component,
2219
- args: [{
2220
- selector: 'button[b-select-trigger]',
2221
- imports: [NgIcon],
2222
- template: `
2350
+ args: [{ selector: 'button[b-select-trigger]', template: `
2223
2351
  <ng-content />
2224
- <ng-icon [name]="triggered() ? 'lucide-a-arrow-up' : 'lucide-a-arrow-down'" />
2225
- `,
2226
- host: {
2227
- '(keydown)': 'handleKeydown($event)',
2228
- '(click)': 'handleClick()',
2352
+ <svg
2353
+ [class.b-select-triggered]="triggered()"
2354
+ xmlns="http://www.w3.org/2000/svg"
2355
+ width="16"
2356
+ height="16"
2357
+ viewBox="0 0 24 24"
2358
+ fill="none"
2359
+ stroke="currentColor"
2360
+ stroke-width="2"
2361
+ stroke-linecap="round"
2362
+ stroke-linejoin="round"
2363
+ class="lucide lucide-chevron-down-icon lucide-chevron-down"
2364
+ >
2365
+ <path d="m6 9 6 6 6-6" />
2366
+ </svg>
2367
+ `, host: {
2368
+ '(keydown.arrowUp)': 'buttonClicked.emit()',
2369
+ '(keydown.arrowDown)': 'buttonClicked.emit()',
2370
+ '(click)': 'buttonClicked.emit()',
2229
2371
  '[disabled]': 'disabled()',
2230
- },
2231
- providers: [provideIcons({ lucideAArrowDown, lucideAArrowUp })],
2232
- }]
2372
+ }, styles: [":host .b-select-triggered{transform:rotate(180deg)}\n"] }]
2233
2373
  }], propDecorators: { buttonClicked: [{ type: i0.Output, args: ["buttonClicked"] }] } });
2234
2374
 
2235
2375
  /**
@@ -2325,6 +2465,7 @@ class Select {
2325
2465
  */
2326
2466
  handleOverlayAttached() {
2327
2467
  this.overlay()?.attachEmitter.subscribe(() => {
2468
+ this.selectTrigger()?.triggered.set(true);
2328
2469
  if (this.value().length === 0) {
2329
2470
  this.selectContent()?.el.nativeElement.focus();
2330
2471
  return;
@@ -2345,6 +2486,7 @@ class Select {
2345
2486
  handleOverlayDetached() {
2346
2487
  this.overlay()?.detachEmitter.subscribe(() => {
2347
2488
  this.overlay()?.closeOverlay();
2489
+ this.selectTrigger()?.triggered.set(false);
2348
2490
  });
2349
2491
  }
2350
2492
  /**