@origostudio/origo-design 0.0.55 → 0.0.56

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +4 -4
  2. package/dist/packages/angular-renderer/CHANGELOG.md +11 -0
  3. package/dist/packages/angular-renderer/package.json +3 -3
  4. package/dist/packages/angular-renderer/src/components/primitives/dialog/dialog.component.d.ts +153 -0
  5. package/dist/packages/angular-renderer/src/components/primitives/dialog/dialog.component.js +346 -0
  6. package/dist/packages/angular-renderer/src/components/primitives/dialog/dialog.component.js.map +1 -0
  7. package/dist/packages/angular-renderer/src/components/primitives/drawer/drawer.component.d.ts +93 -0
  8. package/dist/packages/angular-renderer/src/components/primitives/drawer/drawer.component.js +205 -0
  9. package/dist/packages/angular-renderer/src/components/primitives/drawer/drawer.component.js.map +1 -0
  10. package/dist/packages/angular-renderer/src/components/primitives/modal/modal.component.d.ts +106 -0
  11. package/dist/packages/angular-renderer/src/components/primitives/modal/modal.component.js +61 -0
  12. package/dist/packages/angular-renderer/src/components/primitives/modal/modal.component.js.map +1 -0
  13. package/dist/packages/angular-renderer/src/components/primitives/popover/popover.component.d.ts +82 -0
  14. package/dist/packages/angular-renderer/src/components/primitives/popover/popover.component.js +280 -0
  15. package/dist/packages/angular-renderer/src/components/primitives/popover/popover.component.js.map +1 -0
  16. package/dist/packages/angular-renderer/src/components/primitives/tooltip/tooltip.component.d.ts +86 -0
  17. package/dist/packages/angular-renderer/src/components/primitives/tooltip/tooltip.component.js +272 -0
  18. package/dist/packages/angular-renderer/src/components/primitives/tooltip/tooltip.component.js.map +1 -0
  19. package/dist/packages/angular-renderer/src/index.d.ts +5 -0
  20. package/dist/packages/angular-renderer/src/index.js +5 -0
  21. package/dist/packages/angular-renderer/src/index.js.map +1 -1
  22. package/dist/packages/angular-renderer/src/lib/overlay/overlay-ref.d.ts +13 -0
  23. package/dist/packages/angular-renderer/src/lib/overlay/overlay-ref.js +18 -0
  24. package/dist/packages/angular-renderer/src/lib/overlay/overlay-ref.js.map +1 -1
  25. package/dist/packages/angular-renderer/src/lib/overlay/overlay.models.d.ts +10 -0
  26. package/dist/packages/angular-renderer/src/lib/primitives.provider.js +11 -0
  27. package/dist/packages/angular-renderer/src/lib/primitives.provider.js.map +1 -1
  28. package/dist/packages/cli/CHANGELOG.md +4 -0
  29. package/dist/packages/cli/package.json +2 -2
  30. package/dist/packages/cli/package.json.bak +2 -2
  31. package/dist/packages/core/CHANGELOG.md +4 -0
  32. package/dist/packages/core/package.json +1 -1
  33. package/dist/packages/core/package.json.bak +1 -1
  34. package/dist/packages/design-tokens/CHANGELOG.md +4 -0
  35. package/dist/packages/design-tokens/package.json +1 -1
  36. package/dist/packages/playground/browser/{chunk-COdU4TAE.js → chunk-1EpKMVqg.js} +1 -1
  37. package/dist/packages/playground/browser/{chunk-FZuwRWVL.js → chunk-99Z1ZSho.js} +1 -1
  38. package/dist/packages/playground/browser/{chunk-ChIFPhYz.js → chunk-B6I7DcL1.js} +1 -1
  39. package/dist/packages/playground/browser/{chunk-xkhVqkfX.js → chunk-BJUlPnGj.js} +1 -1
  40. package/dist/packages/playground/browser/{chunk-BtTuZVyy.js → chunk-BQzQEZtb.js} +1 -1
  41. package/dist/packages/playground/browser/{chunk-CkxX-6B-.js → chunk-BhCIuVBX.js} +1 -1
  42. package/dist/packages/playground/browser/{chunk-DYVZHQvW.js → chunk-CKodZfQB.js} +1 -1
  43. package/dist/packages/playground/browser/{chunk-DhQakfe5.js → chunk-CVI627RV.js} +1 -1
  44. package/dist/packages/playground/browser/{chunk-DT7XZi8x.js → chunk-ClkrBRsl.js} +1 -1
  45. package/dist/packages/playground/browser/{chunk-x-oQTX69.js → chunk-D9WDO3Mq.js} +1 -1
  46. package/dist/packages/playground/browser/{chunk-CsaBv_7w.js → chunk-DFbzJcTS.js} +1 -1
  47. package/dist/packages/playground/browser/{chunk-DYyPC2oE.js → chunk-DiYrE5js.js} +1 -1
  48. package/dist/packages/playground/browser/{chunk-DVT-phDq.js → chunk-Dq1v5PE5.js} +1 -1
  49. package/dist/packages/playground/browser/{chunk-BrNyIaRu.js → chunk-DyacuCzl.js} +1 -1
  50. package/dist/packages/playground/browser/{chunk-1qL4kcgA.js → chunk-YNHZb2Tp.js} +1 -1
  51. package/dist/packages/playground/browser/index.html +1 -1
  52. package/dist/packages/playground/browser/main-LRA6JC6Y.js +894 -0
  53. package/package.json +1 -1
  54. package/packages/angular-renderer/CHANGELOG.md +11 -0
  55. package/packages/angular-renderer/package.json +3 -3
  56. package/packages/angular-renderer/src/components/primitives/dialog/dialog.component.html +49 -0
  57. package/packages/angular-renderer/src/components/primitives/dialog/dialog.component.scss +102 -0
  58. package/packages/angular-renderer/src/components/primitives/dialog/dialog.component.ts +415 -0
  59. package/packages/angular-renderer/src/components/primitives/drawer/drawer.component.html +38 -0
  60. package/packages/angular-renderer/src/components/primitives/drawer/drawer.component.scss +76 -0
  61. package/packages/angular-renderer/src/components/primitives/drawer/drawer.component.ts +248 -0
  62. package/packages/angular-renderer/src/components/primitives/modal/modal.component.html +13 -0
  63. package/packages/angular-renderer/src/components/primitives/modal/modal.component.scss +3 -0
  64. package/packages/angular-renderer/src/components/primitives/modal/modal.component.ts +56 -0
  65. package/packages/angular-renderer/src/components/primitives/popover/popover.component.html +41 -0
  66. package/packages/angular-renderer/src/components/primitives/popover/popover.component.scss +77 -0
  67. package/packages/angular-renderer/src/components/primitives/popover/popover.component.ts +345 -0
  68. package/packages/angular-renderer/src/components/primitives/tooltip/tooltip.component.html +27 -0
  69. package/packages/angular-renderer/src/components/primitives/tooltip/tooltip.component.scss +30 -0
  70. package/packages/angular-renderer/src/components/primitives/tooltip/tooltip.component.ts +330 -0
  71. package/packages/angular-renderer/src/index.ts +5 -0
  72. package/packages/angular-renderer/src/lib/overlay/overlay-ref.ts +31 -0
  73. package/packages/angular-renderer/src/lib/overlay/overlay.models.ts +10 -0
  74. package/packages/angular-renderer/src/lib/primitives.provider.ts +11 -0
  75. package/packages/angular-renderer/src/styles/overlay.scss +70 -26
  76. package/packages/cli/CHANGELOG.md +4 -0
  77. package/packages/cli/package.json +2 -2
  78. package/packages/core/CHANGELOG.md +4 -0
  79. package/packages/core/package.json +1 -1
  80. package/packages/design-tokens/CHANGELOG.md +4 -0
  81. package/packages/design-tokens/package.json +1 -1
  82. package/packages/devtools/CHANGELOG.md +4 -0
  83. package/packages/devtools/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  84. package/packages/devtools/package.json +3 -3
  85. package/packages/playground/CHANGELOG.md +4 -0
  86. package/packages/playground/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  87. package/packages/playground/package.json +3 -3
  88. package/dist/packages/playground/browser/main-UNDFLKYI.js +0 -889
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@origostudio/origo-design",
3
- "version": "0.0.55",
3
+ "version": "0.0.56",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,14 @@
1
+ ## 0.0.56 (2026-10-13)
2
+
3
+ ### 🚀 Features
4
+
5
+ - implement modal and overlay primitives including dialog, drawer, popover, and tooltip components ([ca3030d](https://github.com/OrigoStudio/origo-design/commit/ca3030d))
6
+ - implement modal and overlay primitives with shadow DOM support and focus trapping ([0c8b1b8](https://github.com/OrigoStudio/origo-design/commit/0c8b1b8))
7
+
8
+ ### ❤️ Thank You
9
+
10
+ - Hardik Patel @hardikpatelse
11
+
1
12
  ## 0.0.55 (2026-10-13)
2
13
 
3
14
  ### 🚀 Features
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@origostudio/angular-renderer",
3
- "version": "0.0.55",
3
+ "version": "0.0.56",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "dependencies": {
9
- "@origostudio/core": "0.0.55",
10
- "@origostudio/design-tokens": "0.0.55",
9
+ "@origostudio/core": "0.0.56",
10
+ "@origostudio/design-tokens": "0.0.56",
11
11
  "tslib": "^2.3.0",
12
12
  "@analogjs/vite-plugin-angular": "^2.7.1",
13
13
  "zone.js": "^0.16.3",
@@ -0,0 +1,49 @@
1
+ <ng-template #dialogTemplate>
2
+ <div
3
+ #dialogElement
4
+ role="dialog"
5
+ [attr.aria-modal]="computedModal() ? 'true' : null"
6
+ class="origo-dialog"
7
+ [class.origo-dialog--maximized]="isMaximized()"
8
+ [class.origo-dialog--draggable]="computedDraggable() && !isMaximized()"
9
+ [class.origo-dialog--resizable]="computedResizable() && !isMaximized()"
10
+ [style.--origo-dialog-translate-x.px]="dragPosition().x"
11
+ [style.--origo-dialog-translate-y.px]="dragPosition().y"
12
+ [attr.aria-labelledby]="computedHeader() ? headerId : null"
13
+ [attr.aria-label]="!computedHeader() ? computedAriaLabel() || 'Dialog' : null"
14
+ [attr.aria-describedby]="computedAriaDescribedBy() || contentId"
15
+ [attr.data-testid]="contract().id"
16
+ >
17
+ <div class="origo-dialog__header" (pointerdown)="onHeaderPointerDown($event)">
18
+ @if (computedHeader()) {
19
+ <h2 [id]="headerId" class="origo-dialog__title">{{ computedHeader() }}</h2>
20
+ }
21
+ <div class="origo-dialog__header-actions">
22
+ @if (computedMaximizable()) {
23
+ <button
24
+ type="button"
25
+ class="origo-dialog__action origo-dialog__action--maximize"
26
+ [attr.aria-label]="isMaximized() ? 'Restore dialog' : 'Maximize dialog'"
27
+ (click)="toggleMaximize()"
28
+ >
29
+ <span class="origo-dialog__action-icon">{{ isMaximized() ? '🗗' : '🗖' }}</span>
30
+ </button>
31
+ }
32
+ @if (computedClosable()) {
33
+ <button
34
+ type="button"
35
+ class="origo-dialog__action origo-dialog__action--close"
36
+ aria-label="Close dialog"
37
+ (click)="onClose()"
38
+ >
39
+ <span class="origo-dialog__action-icon">✕</span>
40
+ </button>
41
+ }
42
+ </div>
43
+ </div>
44
+ <div [id]="contentId" class="origo-dialog__content">
45
+ <ng-container #vc></ng-container>
46
+ <ng-content></ng-content>
47
+ </div>
48
+ </div>
49
+ </ng-template>
@@ -0,0 +1,102 @@
1
+ :host {
2
+ display: contents;
3
+ }
4
+
5
+ .origo-dialog {
6
+ display: flex;
7
+ flex-direction: column;
8
+ box-sizing: border-box;
9
+ inline-size: 100%;
10
+ block-size: 100%;
11
+ background-color: var(--origo-color-surface-overlay, var(--origo-color-surface-primary));
12
+ color: var(--origo-color-text-primary);
13
+ border-radius: var(--origo-radius-md);
14
+ box-shadow: var(--origo-shadow-lg);
15
+ border: var(--origo-border-width-default) solid var(--origo-color-border-subtle);
16
+ overflow: hidden;
17
+ outline: none;
18
+ transform: translate(var(--origo-dialog-translate-x, 0), var(--origo-dialog-translate-y, 0));
19
+
20
+ &--draggable {
21
+ user-select: none;
22
+ }
23
+
24
+ &--resizable {
25
+ resize: both;
26
+ overflow: auto;
27
+ }
28
+
29
+ &--maximized {
30
+ inline-size: 100vw !important;
31
+ block-size: 100vh !important;
32
+ max-inline-size: 100vw !important;
33
+ max-block-size: 100vh !important;
34
+ border-radius: 0 !important;
35
+ inset-block-start: 0 !important;
36
+ inset-inline-start: 0 !important;
37
+ transform: none !important;
38
+ }
39
+
40
+ &__header {
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: space-between;
44
+ padding-inline: var(--origo-space-md);
45
+ padding-block: var(--origo-space-md);
46
+ border-block-end: var(--origo-border-width-default) solid var(--origo-color-border-subtle);
47
+ background-color: var(--origo-color-surface-secondary);
48
+ gap: var(--origo-space-sm);
49
+
50
+ .origo-dialog--draggable & {
51
+ cursor: move;
52
+ }
53
+ }
54
+
55
+ &__title {
56
+ margin: 0;
57
+ font-size: var(--origo-font-size-lg);
58
+ font-weight: var(--origo-font-weight-semibold);
59
+ color: var(--origo-color-text-primary);
60
+ line-height: var(--origo-line-height-tight);
61
+ }
62
+
63
+ &__header-actions {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--origo-space-xs);
67
+ margin-inline-start: auto;
68
+ }
69
+
70
+ &__action {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ border: none;
75
+ background: transparent;
76
+ color: var(--origo-color-text-secondary);
77
+ border-radius: var(--origo-radius-sm);
78
+ padding-inline: var(--origo-space-xs);
79
+ padding-block: var(--origo-space-xs);
80
+ cursor: pointer;
81
+ font-size: var(--origo-font-size-md);
82
+ line-height: 1;
83
+
84
+ &:hover {
85
+ background-color: var(--origo-color-action-hover);
86
+ color: var(--origo-color-text-primary);
87
+ }
88
+
89
+ &:focus-visible {
90
+ outline: var(--origo-border-width-focus) solid var(--origo-color-focus-ring);
91
+ outline-offset: var(--origo-space-xxs);
92
+ }
93
+ }
94
+
95
+ &__content {
96
+ padding-inline: var(--origo-space-md);
97
+ padding-block: var(--origo-space-md);
98
+ flex: 1 1 auto;
99
+ overflow-y: auto;
100
+ color: var(--origo-color-text-primary);
101
+ }
102
+ }
@@ -0,0 +1,415 @@
1
+ import {
2
+ Component,
3
+ input,
4
+ output,
5
+ ChangeDetectionStrategy,
6
+ ViewEncapsulation,
7
+ inject,
8
+ viewChild,
9
+ ViewContainerRef,
10
+ TemplateRef,
11
+ ElementRef,
12
+ signal,
13
+ computed,
14
+ effect,
15
+ DestroyRef,
16
+ PLATFORM_ID,
17
+ } from '@angular/core';
18
+ import { isPlatformBrowser, DOCUMENT } from '@angular/common';
19
+ import { InteractionContract } from '@origostudio/core';
20
+ import {
21
+ OrigoAdapter,
22
+ ContainerComponent,
23
+ coerceContractProps,
24
+ } from '../../../adapters/web/adapter';
25
+ import { WebExperienceAdapterService } from '../../../adapters/web/experience-adapter.service';
26
+ import { OrigoOverlayService } from '../../../lib/overlay/overlay.service';
27
+ import { OrigoOverlayConfig, OrigoOverlayRef } from '../../../lib/overlay/overlay.models';
28
+
29
+ let dialogCounter = 0;
30
+
31
+ export interface DialogProps {
32
+ visible?: boolean;
33
+ header?: string;
34
+ modal?: boolean;
35
+ closable?: boolean;
36
+ closeOnEscape?: boolean;
37
+ dismissableMask?: boolean;
38
+ draggable?: boolean;
39
+ resizable?: boolean;
40
+ maximizable?: boolean;
41
+ maximized?: boolean;
42
+ width?: string;
43
+ height?: string;
44
+ minWidth?: string;
45
+ minHeight?: string;
46
+ maxWidth?: string;
47
+ maxHeight?: string;
48
+ position?: 'center' | 'top' | 'bottom' | 'start' | 'end';
49
+ blockScroll?: boolean;
50
+ data?: unknown;
51
+ permissions?: Record<string, string>;
52
+ rules?: Record<string, unknown>;
53
+ metadata?: Record<string, unknown>;
54
+ 'aria-label'?: string;
55
+ 'aria-describedby'?: string;
56
+ }
57
+
58
+ export const DialogContractSchema = {
59
+ permissions: 'object',
60
+ rules: 'object',
61
+ metadata: 'object',
62
+ 'aria-label': 'string',
63
+ 'aria-describedby': 'string',
64
+ visible: 'boolean',
65
+ header: 'string',
66
+ modal: 'boolean',
67
+ closable: 'boolean',
68
+ closeOnEscape: 'boolean',
69
+ dismissableMask: 'boolean',
70
+ draggable: 'boolean',
71
+ resizable: 'boolean',
72
+ maximizable: 'boolean',
73
+ maximized: 'boolean',
74
+ width: 'string',
75
+ height: 'string',
76
+ minWidth: 'string',
77
+ minHeight: 'string',
78
+ maxWidth: 'string',
79
+ maxHeight: 'string',
80
+ position: 'string',
81
+ blockScroll: 'boolean',
82
+ data: 'object',
83
+ } as const;
84
+
85
+ @Component({
86
+ selector: 'origo-dialog',
87
+ standalone: true,
88
+ templateUrl: './dialog.component.html',
89
+ styleUrls: ['./dialog.component.scss'],
90
+ changeDetection: ChangeDetectionStrategy.OnPush,
91
+ encapsulation: ViewEncapsulation.ShadowDom,
92
+ host: {
93
+ '[class.origo-dialog-host]': 'true',
94
+ '[attr.data-testid]': 'contract().id',
95
+ },
96
+ })
97
+ export class DialogComponent implements OrigoAdapter<DialogProps>, ContainerComponent {
98
+ static readonly contractSchema = DialogContractSchema;
99
+ static readonly strictContract = false;
100
+
101
+ contract = input.required<InteractionContract<DialogProps>>();
102
+ vc = viewChild('vc', { read: ViewContainerRef });
103
+
104
+ dialogTemplate = viewChild('dialogTemplate', { read: TemplateRef });
105
+ dialogElement = viewChild<ElementRef<HTMLElement>>('dialogElement');
106
+
107
+ visibleChange = output<boolean>();
108
+ open = output<void>();
109
+ close = output<void>();
110
+ maximize = output<boolean>();
111
+ restore = output<void>();
112
+ dragEnd = output<{ x: number; y: number }>();
113
+ resizeEnd = output<{ width: number; height: number }>();
114
+
115
+ private overlayService = inject(OrigoOverlayService);
116
+ private destroyRef = inject(DestroyRef);
117
+ private vcr = inject(ViewContainerRef);
118
+ private experienceAdapter = inject(WebExperienceAdapterService);
119
+ private platformId = inject(PLATFORM_ID);
120
+ private document = inject(DOCUMENT);
121
+
122
+ private readonly id = ++dialogCounter;
123
+ readonly headerId = `origo-dialog-header-${this.id}`;
124
+ readonly contentId = `origo-dialog-content-${this.id}`;
125
+
126
+ private overlayRef: OrigoOverlayRef | null = null;
127
+ private isOpen = false;
128
+ private resizeObserver: ResizeObserver | null = null;
129
+ private resizeTimeout: ReturnType<typeof setTimeout> | null = null;
130
+
131
+ isMaximized = signal(false);
132
+ dragPosition = signal<{ x: number; y: number }>({ x: 0, y: 0 });
133
+
134
+ private isDragging = false;
135
+ private dragStartCoords = { x: 0, y: 0 };
136
+ private initialDragPos = { x: 0, y: 0 };
137
+ private pointerMoveListener: ((e: PointerEvent) => void) | null = null;
138
+ private pointerUpListener: ((e: PointerEvent) => void) | null = null;
139
+
140
+ coercedProps = computed(() =>
141
+ coerceContractProps<DialogProps>(this.contract()?.props ?? {}, DialogContractSchema)
142
+ );
143
+
144
+ computedVisible = computed(() => !!this.coercedProps().visible);
145
+ computedHeader = computed(() => this.coercedProps().header);
146
+ computedModal = computed(() => this.coercedProps().modal !== false);
147
+ computedClosable = computed(() => this.coercedProps().closable !== false);
148
+ computedCloseOnEscape = computed(() => this.coercedProps().closeOnEscape !== false);
149
+ computedDismissableMask = computed(() => !!this.coercedProps().dismissableMask);
150
+ computedDraggable = computed(() => !!this.coercedProps().draggable);
151
+ computedResizable = computed(() => !!this.coercedProps().resizable);
152
+ computedMaximizable = computed(() => !!this.coercedProps().maximizable);
153
+
154
+ computedWidth = computed(() => this.coercedProps().width ?? '560px');
155
+ computedHeight = computed(() => this.coercedProps().height ?? 'auto');
156
+ computedMinWidth = computed(() => this.coercedProps().minWidth);
157
+ computedMinHeight = computed(() => this.coercedProps().minHeight);
158
+ computedMaxWidth = computed(() => this.coercedProps().maxWidth ?? '90vw');
159
+ computedMaxHeight = computed(() => this.coercedProps().maxHeight ?? '90vh');
160
+ computedPosition = computed(
161
+ () =>
162
+ (this.coercedProps().position as 'center' | 'top' | 'bottom' | 'start' | 'end') ?? 'center'
163
+ );
164
+
165
+ computedAriaLabel = computed(() => {
166
+ const label = this.contract()?.props?.['aria-label'];
167
+ return label !== undefined && label !== null ? String(label) : undefined;
168
+ });
169
+
170
+ computedAriaDescribedBy = computed(() => {
171
+ const desc = this.contract()?.props?.['aria-describedby'];
172
+ return desc !== undefined && desc !== null ? String(desc) : undefined;
173
+ });
174
+
175
+ constructor() {
176
+ effect(() => {
177
+ const visible = this.computedVisible();
178
+ if (visible && !this.isOpen) {
179
+ this.openDialog();
180
+ } else if (!visible && this.isOpen) {
181
+ this.closeDialog(false);
182
+ }
183
+ });
184
+
185
+ effect(() => {
186
+ const maxProp = this.coercedProps().maximized;
187
+ if (maxProp !== undefined) {
188
+ this.isMaximized.set(!!maxProp);
189
+ }
190
+ });
191
+
192
+ this.destroyRef.onDestroy(() => {
193
+ this.cleanup();
194
+ });
195
+ }
196
+
197
+ private openDialog(): void {
198
+ if (this.isOpen) return;
199
+
200
+ const tpl = this.dialogTemplate();
201
+ if (!tpl) return;
202
+
203
+ const config: OrigoOverlayConfig = {
204
+ position: {
205
+ type: 'global',
206
+ position: this.computedPosition(),
207
+ width: this.isMaximized() ? '100vw' : this.computedWidth(),
208
+ height: this.isMaximized() ? '100vh' : this.computedHeight(),
209
+ minWidth: this.isMaximized() ? '100vw' : this.computedMinWidth(),
210
+ minHeight: this.isMaximized() ? '100vh' : this.computedMinHeight(),
211
+ maxWidth: this.isMaximized() ? '100vw' : this.computedMaxWidth(),
212
+ maxHeight: this.isMaximized() ? '100vh' : this.computedMaxHeight(),
213
+ },
214
+ hasBackdrop: this.computedModal(),
215
+ backdropClass: 'origo-dialog-backdrop',
216
+ panelClass: [
217
+ 'origo-overlay-pane--dialog',
218
+ 'origo-overlay-pane--modal',
219
+ ...(this.isMaximized() ? ['origo-overlay-pane--maximized'] : []),
220
+ ],
221
+ closeOnBackdropClick: this.computedDismissableMask(),
222
+ closeOnOutsideClick: this.computedDismissableMask(),
223
+ closeOnEscape: this.computedCloseOnEscape(),
224
+ trapFocus: this.computedModal(),
225
+ viewContainerRef: this.vcr,
226
+ };
227
+
228
+ this.overlayRef = this.overlayService.create(config, this.destroyRef);
229
+ this.overlayRef.open(tpl, this.vcr);
230
+ this.isOpen = true;
231
+
232
+ this.open.emit();
233
+ this.experienceAdapter.updateState(this.contract().id, 'visible', true);
234
+ this.experienceAdapter.dispatchCapability(this.contract().id, 'open', undefined);
235
+
236
+ this.setupResizeObserver();
237
+
238
+ this.overlayRef.afterClosed().then(result => {
239
+ if (this.isOpen) {
240
+ this.isOpen = false;
241
+ this.cleanupDrag();
242
+ this.visibleChange.emit(false);
243
+ this.close.emit();
244
+ this.experienceAdapter.updateState(this.contract().id, 'visible', false);
245
+ this.experienceAdapter.dispatchCapability(this.contract().id, 'close', result);
246
+ }
247
+ });
248
+ }
249
+
250
+ private closeDialog(notify = true): void {
251
+ if (!this.isOpen) return;
252
+ this.isOpen = false;
253
+ this.cleanupDrag();
254
+
255
+ if (this.overlayRef?.isOpen) {
256
+ this.overlayRef.close();
257
+ }
258
+
259
+ if (notify) {
260
+ this.visibleChange.emit(false);
261
+ this.close.emit();
262
+ this.experienceAdapter.updateState(this.contract().id, 'visible', false);
263
+ this.experienceAdapter.dispatchCapability(this.contract().id, 'close', undefined);
264
+ }
265
+ }
266
+
267
+ onClose(): void {
268
+ this.closeDialog(true);
269
+ }
270
+
271
+ toggleMaximize(): void {
272
+ const next = !this.isMaximized();
273
+ this.isMaximized.set(next);
274
+
275
+ if (this.overlayRef?.isOpen) {
276
+ if (next) {
277
+ this.overlayRef.addPanelClass?.('origo-overlay-pane--maximized');
278
+ this.overlayRef.updateSize?.({
279
+ width: '100vw',
280
+ height: '100vh',
281
+ maxWidth: '100vw',
282
+ maxHeight: '100vh',
283
+ });
284
+ } else {
285
+ this.overlayRef.removePanelClass?.('origo-overlay-pane--maximized');
286
+ this.overlayRef.updateSize?.({
287
+ width: this.computedWidth(),
288
+ height: this.computedHeight(),
289
+ maxWidth: this.computedMaxWidth(),
290
+ maxHeight: this.computedMaxHeight(),
291
+ });
292
+ }
293
+ this.overlayRef.updatePosition();
294
+ }
295
+
296
+ if (next) {
297
+ this.maximize.emit(true);
298
+ this.experienceAdapter.dispatchCapability(this.contract().id, 'maximize', true);
299
+ } else {
300
+ this.restore.emit();
301
+ this.experienceAdapter.dispatchCapability(this.contract().id, 'restore', undefined);
302
+ }
303
+ }
304
+
305
+ onHeaderPointerDown(event: PointerEvent): void {
306
+ if (!this.computedDraggable() || this.isMaximized() || event.button !== 0) {
307
+ return;
308
+ }
309
+
310
+ // Do not initiate dragging if clicking close or maximize buttons
311
+ const target = event.target as HTMLElement | null;
312
+ if (target?.closest('button')) {
313
+ return;
314
+ }
315
+
316
+ if (!isPlatformBrowser(this.platformId) || !this.document) {
317
+ return;
318
+ }
319
+
320
+ this.cleanupDragListeners();
321
+ this.isDragging = true;
322
+ this.dragStartCoords = { x: event.clientX, y: event.clientY };
323
+ this.initialDragPos = { ...this.dragPosition() };
324
+
325
+ this.pointerMoveListener = (e: PointerEvent) => {
326
+ if (!this.isDragging) return;
327
+ const dx = e.clientX - this.dragStartCoords.x;
328
+ const dy = e.clientY - this.dragStartCoords.y;
329
+ this.dragPosition.set({
330
+ x: this.initialDragPos.x + dx,
331
+ y: this.initialDragPos.y + dy,
332
+ });
333
+ };
334
+
335
+ this.pointerUpListener = () => {
336
+ if (!this.isDragging) return;
337
+ this.isDragging = false;
338
+ const finalPos = this.dragPosition();
339
+ this.dragEnd.emit(finalPos);
340
+ this.experienceAdapter.dispatchCapability(this.contract().id, 'dragEnd', finalPos);
341
+ this.cleanupDragListeners();
342
+ };
343
+
344
+ this.document.addEventListener('pointermove', this.pointerMoveListener);
345
+ this.document.addEventListener('pointerup', this.pointerUpListener);
346
+ }
347
+
348
+ private setupResizeObserver(): void {
349
+ if (!isPlatformBrowser(this.platformId) || typeof ResizeObserver === 'undefined') {
350
+ return;
351
+ }
352
+
353
+ if (this.resizeTimeout) {
354
+ clearTimeout(this.resizeTimeout);
355
+ }
356
+
357
+ this.resizeTimeout = setTimeout(() => {
358
+ if (!this.isOpen) return;
359
+ const el = this.dialogElement()?.nativeElement;
360
+ if (!el) return;
361
+
362
+ let initial = true;
363
+ this.resizeObserver = new ResizeObserver(entries => {
364
+ if (initial) {
365
+ initial = false;
366
+ return;
367
+ }
368
+ if (this.isMaximized()) return;
369
+ for (const entry of entries) {
370
+ const dims = {
371
+ width: Math.round(entry.contentRect.width),
372
+ height: Math.round(entry.contentRect.height),
373
+ };
374
+ this.resizeEnd.emit(dims);
375
+ this.experienceAdapter.dispatchCapability(this.contract().id, 'resizeEnd', dims);
376
+ }
377
+ });
378
+
379
+ this.resizeObserver.observe(el);
380
+ }, 0);
381
+ }
382
+
383
+ private cleanupDrag(): void {
384
+ this.isDragging = false;
385
+ this.cleanupDragListeners();
386
+ }
387
+
388
+ private cleanupDragListeners(): void {
389
+ if (this.pointerMoveListener && this.document) {
390
+ this.document.removeEventListener('pointermove', this.pointerMoveListener);
391
+ this.pointerMoveListener = null;
392
+ }
393
+ if (this.pointerUpListener && this.document) {
394
+ this.document.removeEventListener('pointerup', this.pointerUpListener);
395
+ this.pointerUpListener = null;
396
+ }
397
+ }
398
+
399
+ private cleanup(): void {
400
+ this.cleanupDrag();
401
+ if (this.resizeTimeout) {
402
+ clearTimeout(this.resizeTimeout);
403
+ this.resizeTimeout = null;
404
+ }
405
+ if (this.resizeObserver) {
406
+ this.resizeObserver.disconnect();
407
+ this.resizeObserver = null;
408
+ }
409
+ if (this.overlayRef) {
410
+ this.overlayRef.dispose();
411
+ this.overlayRef = null;
412
+ }
413
+ this.isOpen = false;
414
+ }
415
+ }
@@ -0,0 +1,38 @@
1
+ <ng-template #drawerTemplate>
2
+ <div
3
+ role="dialog"
4
+ [attr.aria-modal]="computedModal() ? 'true' : null"
5
+ class="origo-drawer"
6
+ [class.origo-drawer--left]="computedPosition() === 'left'"
7
+ [class.origo-drawer--right]="computedPosition() === 'right'"
8
+ [class.origo-drawer--top]="computedPosition() === 'top'"
9
+ [class.origo-drawer--bottom]="computedPosition() === 'bottom'"
10
+ [attr.aria-labelledby]="computedHeader() ? headerId : null"
11
+ [attr.aria-label]="!computedHeader() ? computedAriaLabel() : null"
12
+ [attr.aria-describedby]="computedAriaDescribedBy()"
13
+ [attr.data-testid]="contract().id"
14
+ >
15
+ <div class="origo-drawer__header">
16
+ @if (computedHeader()) {
17
+ <h2 [id]="headerId" class="origo-drawer__title">{{ computedHeader() }}</h2>
18
+ }
19
+ @if (computedClosable()) {
20
+ <button
21
+ type="button"
22
+ class="origo-drawer__close"
23
+ aria-label="Close drawer"
24
+ (click)="onClose()"
25
+ >
26
+ <span class="origo-drawer__close-icon">✕</span>
27
+ </button>
28
+ }
29
+ </div>
30
+ <div class="origo-drawer__content">
31
+ <ng-container #vc></ng-container>
32
+ <ng-content></ng-content>
33
+ </div>
34
+ @if (computedFooter()) {
35
+ <div class="origo-drawer__footer">{{ computedFooter() }}</div>
36
+ }
37
+ </div>
38
+ </ng-template>