• Joined on 2026-02-19

@phoenix-tekhne/angular-dynamis (2.5.1)

Published 2026-05-28 07:22:39 -07:00 by dmonphx

Installation

@phoenix-tekhne:registry=https://gitrepo.cstudiosinc.com/api/packages/dmonphx/npm/
npm install @phoenix-tekhne/angular-dynamis@2.5.1
"@phoenix-tekhne/angular-dynamis": "2.5.1"

About this package

@phoenix-tekhne/angular-dynamis

Angular services for headless UI interactions using RxJS. Part of the Phoenix Tekhne Design System.

Installation

npm install @phoenix-tekhne/angular-dynamis @phoenix-tekhne/dynamis-core

Peer Dependencies:

  • @angular/core@^17.0.0
  • rxjs@^7.8.0

Quick Start

import { Component } from '@angular/core';
import { PressService } from '@phoenix-tekhne/angular-dynamis';

@Component({
  selector: 'app-button',
  template: `
    <button
      [attr.data-pressed]="(pressService.state$ | async)?.isPressed"
      (pointerdown)="pressService.handlePointerDown($event)"
      (pointerup)="pressService.handlePointerUp($event)"
      (keydown)="pressService.handleKeyDown($event)"
      (keyup)="pressService.handleKeyUp($event)"
    >
      <ng-content></ng-content>
    </button>
  `,
})
export class ButtonComponent {
  constructor(public pressService: PressService) {
    pressService.initialize({ isDisabled: false });
  }
}

Services

Service Greek Purpose
PressService προσκρούσις (pressing against) Button press interactions
ToggleService κράσις (mixing) Toggle/checkbox state (binary & tri-state)
TextFieldService λέξις (word) Text field validation and state
SelectService ἔλλειψις (choice) Select/dropdown interactions
ToastService κήρυγμα (proclamation) Toast notifications

API Reference

PressService (Press)

Handles button press interactions with pointer and keyboard support.

import { Component, OnInit } from '@angular/core';
import { PressService } from '@phoenix-tekhne/angular-dynamis';

@Component({
  selector: 'app-button',
  template: `
    <button
      [attr.aria-pressed]="(pressService.state$ | async)?.isPressed"
      [attr.data-pressed]="(pressService.state$ | async)?.isPressed"
      (pointerdown)="pressService.handlePointerDown($event)"
      (pointerup)="pressService.handlePointerUp($event)"
      (pointercancel)="pressService.handlePointerCancel($event)"
      (keydown)="pressService.handleKeyDown($event)"
      (keyup)="pressService.handleKeyUp($event)"
      [disabled]="isDisabled"
    >
      <ng-content></ng-content>
    </button>
  `,
  providers: [PressService],
})
export class ButtonComponent implements OnInit {
  @Input() isDisabled = false;

  constructor(public pressService: PressService) {}

  ngOnInit(): void {
    this.pressService.initialize({
      isDisabled: this.isDisabled,
      onPress: () => this.onPress.emit(),
      onRelease: () => this.onRelease.emit(),
    });
  }

  @Output() onPress = new EventEmitter<void>();
  @Output() onRelease = new EventEmitter<void>();
}

Options:

Option Type Default Description
isDisabled boolean false Disables press handling
onPress () => void - Callback when press activates
onRelease () => void - Callback when press releases

Observable Properties:

Property Type Description
state$ Observable<PressState> Current press state

ToggleService (Toggle/Checkbox)

Handles toggle and checkbox state, supporting both binary and tri-state (indeterminate).

import { Component, OnInit } from '@angular/core';
import { ToggleService, KrasisStateValue } from '@phoenix-tekhne/angular-dynamis';

@Component({
  selector: 'app-checkbox',
  template: `
    <div
      role="checkbox"
      [attr.aria-checked]="getAriaChecked()"
      [attr.data-checked]="(toggleService.state$ | async)?.value === true"
      tabindex="0"
      (click)="toggleService.toggle()"
      (keydown)="handleKeyDown($event)"
    >
      <span class="checkmark">
        {{ (toggleService.state$ | async)?.value === true ? '✓' : 
           (toggleService.state$ | async)?.value === 'indeterminate' ? '−' : '' }}
      </span>
      <ng-content></ng-content>
    </div>
  `,
  providers: [ToggleService],
})
export class CheckboxComponent implements OnInit {
  @Input() isDisabled = false;
  @Input() initialValue: KrasisStateValue = false;
  @Input() allowIndeterminate = false;

  constructor(public toggleService: ToggleService) {}

  ngOnInit(): void {
    this.toggleService.initialize({
      isDisabled: this.isDisabled,
      initialValue: this.initialValue,
      allowIndeterminate: this.allowIndeterminate,
      onChange: (value) => this.onChange.emit(value),
    });
  }

  @Output() onChange = new EventEmitter<KrasisStateValue>();

  getAriaChecked(): string {
    const value = this.toggleService.value;
    if (value === 'indeterminate') return 'mixed';
    return String(value);
  }

  handleKeyDown(event: KeyboardEvent): void {
    if (event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      this.toggleService.toggle();
    }
  }
}

Options:

Option Type Default Description
isDisabled boolean false Disables toggle
initialValue boolean | 'indeterminate' false Initial checked state
allowIndeterminate boolean false Enable tri-state mode
onChange (value) => void - Callback on value change

TextFieldService (Text Field)

Handles text field state, validation, and formatting.

import { Component, OnInit } from '@angular/core';
import { TextFieldService } from '@phoenix-tekhne/angular-dynamis';

@Component({
  selector: 'app-text-field',
  template: `
    <div>
      <input
        [value]="textFieldService.value"
        (input)="textFieldService.handleChange($event.target.value)"
        (focus)="textFieldService.handleFocus()"
        (blur)="textFieldService.handleBlur()"
        [disabled]="isDisabled"
        [required]="isRequired"
        [attr.aria-invalid]="!(textFieldService.state$ | async)?.isValid"
        [attr.data-dirty]="(textFieldService.state$ | async)?.isDirty"
        [attr.data-visited]="(textFieldService.state$ | async)?.isVisited"
      />
      <span *ngIf="!(textFieldService.state$ | async)?.isValid && (textFieldService.state$ | async)?.isVisited">
        {{ textFieldService.getValidationMessage() }}
      </span>
    </div>
  `,
  providers: [TextFieldService],
})
export class TextFieldComponent implements OnInit {
  @Input() isDisabled = false;
  @Input() isRequired = false;
  @Input() minLength?: number;
  @Input() maxLength?: number;
  @Input() pattern?: string;
  @Input() validate?: (value: string) => boolean;

  constructor(public textFieldService: TextFieldService) {}

  ngOnInit(): void {
    this.textFieldService.initialize({
      isDisabled: this.isDisabled,
      isRequired: this.isRequired,
      minLength: this.minLength,
      maxLength: this.maxLength,
      pattern: this.pattern,
      validate: this.validate,
      onChange: (value) => this.onChange.emit(value),
      onBlur: (value) => this.onBlur.emit(value),
    });
  }

  @Output() onChange = new EventEmitter<string>();
  @Output() onBlur = new EventEmitter<string>();
}

SelectService (Select/Dropdown)

Handles select and dropdown interactions with single and multi-select support.

import { Component, OnInit } from '@angular/core';
import { SelectService, SelectItem } from '@phoenix-tekhne/angular-dynamis';

@Component({
  selector: 'app-select',
  template: `
    <div>
      <button
        [attr.aria-expanded]="(selectService.state$ | async)?.isOpen"
        (click)="selectService.handleTriggerClick()"
        (keydown)="selectService.handleTriggerKeyDown($event)"
      >
        {{ selectService.displayLabel || 'Select...' }}
      </button>
      
      <ul
        *ngIf="(selectService.state$ | async)?.isOpen"
        role="listbox"
        [attr.data-open]="(selectService.state$ | async)?.isOpen"
      >
        <li
          *ngFor="let item of items; let i = index"
          role="option"
          [attr.data-selected]="selectService.selectedValues.includes(item.id)"
          (click)="selectService.selectItem(item.id)"
        >
          {{ item.label }}
        </li>
      </ul>
    </div>
  `,
  providers: [SelectService],
})
export class SelectComponent implements OnInit {
  @Input() items: SelectItem[] = [];
  @Input() isMulti = false;

  constructor(public selectService: SelectService) {}

  ngOnInit(): void {
    this.selectService.initialize({
      isMulti: this.isMulti,
      onChange: (values) => this.onChange.emit(values),
    });
    this.selectService.setItems(this.items);
  }

  @Output() onChange = new EventEmitter<string[]>();
}

ToastService (Toast Notifications)

Manages toast notification lifecycle including queuing and dismissal.

import { Component, OnInit } from '@angular/core';
import { ToastService } from '@phoenix-tekhne/angular-dynamis';

@Component({
  selector: 'app-toast-container',
  template: `
    <div [attr.data-position]="(toastService.state$ | async)?.position" class="toast-container">
      <div
        *ngFor="let toast of toastService.getVisibleToasts()"
        class="toast"
        [attr.data-variant]="toast.variant"
        (mouseenter)="toastService.pause(toast.id)"
        (mouseleave)="toastService.resume(toast.id)"
      >
        <span *ngIf="toast.title" class="toast-title">{{ toast.title }}</span>
        <span class="toast-message">{{ toast.message }}</span>
        <button *ngIf="toast.dismissible" (click)="toastService.dismiss(toast.id)">×</button>
      </div>
    </div>
  `,
  providers: [ToastService],
})
export class ToastContainerComponent implements OnInit {
  constructor(public toastService: ToastService) {}

  ngOnInit(): void {
    this.toastService.initialize({
      position: 'bottom-right',
      maxVisible: 5,
      duration: 5000,
    });
  }
}

// Usage in a service:
@Injectable({ providedIn: 'root' })
export class NotificationService {
  constructor(private toastService: ToastService) {
    this.toastService.initialize({ position: 'bottom-right' });
  }

  showSuccess(message: string, title?: string): string {
    return this.toastService.success(message, title);
  }

  showError(message: string, title?: string): string {
    return this.toastService.error(message, title);
  }
}

Options:

Option Type Default Description
maxVisible number 5 Maximum visible toasts
position ToastPosition 'bottom-right' Toast position
duration number 5000 Default duration in ms
pauseOnHover boolean true Pause on hover
dismissible boolean true Allow dismissal
showProgress boolean false Show progress bar

RxJS Patterns

All services follow consistent RxJS patterns:

BehaviorSubject for State

@Injectable({ providedIn: 'root' })
export class MyService {
  private stateSubject = new BehaviorSubject<State>(initialState);
  state$ = this.stateSubject.asObservable();
}

Observable Pattern

// Subscribe to state changes
this.pressService.state$.subscribe(state => {
  console.log('Is pressed:', state.isPressed);
});

// Use with async pipe in templates
<div>{{ (pressService.state$ | async)?.isPressed }}</div>

Greek Terminology

Greek Transliteration English Used For
δύναμις dynamis Power, Ability Core state machine package
προσκρούσις proskrousis Pressing against, striking Button press interactions
κράσις krasis Mixing Toggle/checkbox state
λέξις lexis Word Text field validation
ἔλλειψις epilipsis Choice, selection Select/dropdown
κήρυγμα kerygma Proclamation Toast notifications

Related Packages

  • @phoenix-tekhne/dynamis-core - Pure TypeScript state machines
  • @phoenix-tekhne/react-dynamis - React hooks
  • @phoenix-tekhne/vue-dynamis - Vue 3 composables
  • @phoenix-tekhne/svelte-dynamis - Svelte stores
  • @phoenix-tekhne/web-dynamis - Web Components

License

MIT

Dependencies

Dependencies

ID Version
@phoenix-tekhne/dynamis-core 2.5.1

Development Dependencies

ID Version
@angular/core ^19.0.0
rxjs ^7.8.0
typescript ^5.7.0
vite ^6.0.0
vitest ^2.0.0

Peer Dependencies

ID Version
@angular/core ^19.0.0
rxjs ^7.5.0

Keywords

angular angular2 headless ui components services rxjs design-system phoenix-tekhne
Details
npm
2026-05-28 07:22:39 -07:00
4
Phoenix Tekhne Team
MIT
latest
16 KiB
Assets (1)
Versions (1) View all
2.5.1 2026-05-28