@retalia/pos-components 0.0.10 → 0.0.11
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/README.md +442 -400
- package/fesm2022/retalia-pos-components.mjs +1751 -32
- package/fesm2022/retalia-pos-components.mjs.map +1 -1
- package/package.json +1 -1
- package/types/retalia-pos-components.d.ts +253 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, output, signal, computed, ChangeDetectionStrategy, Component, viewChild, afterNextRender } from '@angular/core';
|
|
2
|
+
import { input, output, signal, computed, ChangeDetectionStrategy, Component, viewChild, afterNextRender, InjectionToken, inject, booleanAttribute, ElementRef, DestroyRef, contentChildren, linkedSignal, effect, untracked, forwardRef } from '@angular/core';
|
|
3
3
|
|
|
4
4
|
const DEFAULT_SESSION = { status: 'signedOut' };
|
|
5
5
|
const DIGIT_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
@@ -244,47 +244,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
244
244
|
}, template: "<div class=\"pos-header__identity\">\r\n @if (showBrandImage() || brandLabel()) {\r\n <div class=\"pos-header__brand\">\r\n @if (showBrandImage()) {\r\n @if (brandImage()) {\r\n <img\r\n class=\"pos-header__brand-image\"\r\n [src]=\"brandImage()\"\r\n [alt]=\"brandLabel() ? '' : 'Brand'\"\r\n />\r\n } @else {\r\n <svg\r\n class=\"pos-header__brand-image\"\r\n viewBox=\"492.40 241.70 51.34 54.94\"\r\n [attr.aria-hidden]=\"brandLabel() ? true : null\"\r\n [attr.aria-label]=\"brandLabel() ? null : 'Retalia'\"\r\n >\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M 522.628906 291.910156 C 522.636719 293.0625 523.441406 293.511719 524.425781 292.914062 L 540.464844 283.207031 C 541.449219 282.613281 542.246094 281.195312 542.246094 280.046875 L 542.246094 261.132812 C 542.246094 259.980469 541.441406 259.523438 540.453125 260.113281 L 524.292969 269.691406 C 523.300781 270.277344 522.503906 271.683594 522.511719 272.835938 Z M 522.628906 291.910156\"\r\n />\r\n <path\r\n fill=\"currentColor\"\r\n d=\"M 539.097656 253.351562 L 517.101562 243.664062 C 516.066406 243.203125 514.417969 243.265625 513.421875 243.804688 L 495.835938 253.308594 C 494.839844 253.847656 494.027344 255.214844 494.023438 256.367188 L 493.910156 280.140625 C 493.902344 281.292969 494.699219 282.679688 495.683594 283.234375 L 501.734375 286.640625 C 502.71875 287.199219 503.503906 286.71875 503.480469 285.566406 L 503.214844 271.488281 C 503.207031 271.082031 503.492188 270.894531 503.855469 271.066406 L 506.128906 272.148438 C 507.152344 272.636719 507.980469 273.960938 507.980469 275.113281 L 507.980469 287.839844 C 507.980469 288.992188 508.773438 290.394531 509.746094 290.972656 L 515.777344 294.5625 C 516.753906 295.140625 517.554688 294.679688 517.566406 293.527344 L 517.800781 273.300781 C 517.808594 272.488281 517.539062 271.238281 517.195312 270.503906 L 515.460938 266.789062 C 515.292969 266.421875 515.476562 266.109375 515.871094 266.089844 L 518.925781 265.941406 C 520.050781 265.886719 521.761719 265.378906 522.738281 264.808594 L 539.195312 255.21875 C 540.175781 254.648438 540.132812 253.8125 539.097656 253.351562 M 520.609375 255.050781 L 518.019531 256.585938 C 516.914062 257.125 515.734375 257.289062 514.710938 256.804688 L 511.078125 255.101562 C 510.054688 254.621094 510.023438 253.773438 511.003906 253.210938 L 513.835938 251.597656 C 514.53125 251.203125 515.691406 251.132812 516.425781 251.445312 L 520.449219 253.128906 C 521.488281 253.570312 521.5625 254.429688 520.609375 255.050781\"\r\n />\r\n </svg>\r\n }\r\n }\r\n @if (brandLabel()) {\r\n <span class=\"pos-header__brand-label\">{{ brandLabel() }}</span>\r\n }\r\n </div>\r\n }\r\n @if (title()) {\r\n <h1 class=\"pos-header__title\">{{ title() }}</h1>\r\n }\r\n @if (context()) {\r\n <p class=\"pos-header__context\">{{ context() }}</p>\r\n }\r\n</div>\r\n\r\n<div class=\"pos-header__session\">\r\n @if (clerkName()) {\r\n <div class=\"pos-header__clerk\">\r\n @if (canSignOut()) {\r\n <div class=\"pos-header__menu-anchor\">\r\n <button\r\n type=\"button\"\r\n class=\"pos-header__menu-button\"\r\n [attr.aria-label]=\"menuLabel()\"\r\n [attr.aria-expanded]=\"menuOpen()\"\r\n aria-haspopup=\"menu\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"toggleMenu()\"\r\n >\r\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\r\n <circle cx=\"5\" cy=\"12\" r=\"1.6\" />\r\n <circle cx=\"12\" cy=\"12\" r=\"1.6\" />\r\n <circle cx=\"19\" cy=\"12\" r=\"1.6\" />\r\n </svg>\r\n </button>\r\n </div>\r\n }\r\n <span class=\"pos-header__avatar\" aria-hidden=\"true\">{{ initial() }}</span>\r\n <span class=\"pos-header__clerk-text\">\r\n <span class=\"pos-header__clerk-name\">{{ clerkName() }}</span>\r\n @if (clerkRole()) {\r\n <span class=\"pos-header__clerk-role\">{{ clerkRole() }}</span>\r\n }\r\n </span>\r\n </div>\r\n }\r\n @if (menuOpen()) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-header__backdrop\"\r\n aria-label=\"Close menu\"\r\n (click)=\"closeMenu()\"\r\n ></button>\r\n <div class=\"pos-header__menu\" role=\"menu\">\r\n <button\r\n type=\"button\"\r\n class=\"pos-header__logout\"\r\n role=\"menuitem\"\r\n (click)=\"onSignOut()\"\r\n >\r\n {{ signOutLabel() }}\r\n </button>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--pos-space-sm) var(--pos-space-md);box-sizing:border-box;width:100%;min-width:0;color:var(--pos-color-text);font-family:var(--pos-font-family);font-size:var(--pos-font-size-md)}.pos-header__identity,.pos-header__session{display:flex;flex-wrap:wrap;align-items:center;gap:var(--pos-space-sm) var(--pos-space-md);min-width:0}.pos-header__session{position:relative;margin-left:auto}.pos-header__brand{display:flex;align-items:center;gap:var(--pos-space-sm);min-width:0}.pos-header__brand-image{flex:0 0 auto;width:auto;height:var(--pos-space-xl);color:var(--pos-color-ink)}.pos-header__brand-label{font-family:var(--pos-font-family-display);font-weight:var(--pos-font-weight-bold);letter-spacing:var(--pos-letter-spacing-brand)}.pos-header__title{margin:0;font-size:var(--pos-font-size-xl);font-weight:var(--pos-font-weight-bold)}.pos-header__context{margin:0;color:var(--pos-color-text-muted);font-size:var(--pos-font-size-sm)}.pos-header__clerk{display:flex;align-items:center;gap:var(--pos-space-sm);min-width:0}.pos-header__avatar{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;width:var(--pos-button-min-height);height:var(--pos-button-min-height);border-radius:var(--pos-radius-pill);background:var(--pos-color-primary);color:var(--pos-color-on-primary);font-size:var(--pos-font-size-lg);font-weight:var(--pos-font-weight-bold)}.pos-header__clerk-text{display:flex;flex-direction:column;min-width:0}.pos-header__clerk-name{font-weight:var(--pos-font-weight-bold);line-height:1.2}.pos-header__clerk-role{color:var(--pos-color-text-muted);font-size:var(--pos-font-size-sm);line-height:1.2;text-transform:lowercase}.pos-header__menu-anchor{position:relative;flex:0 0 auto}.pos-header__menu-button{display:inline-flex;align-items:center;justify-content:center;width:var(--pos-button-min-height);height:var(--pos-button-min-height);padding:0;border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-pill);background:var(--pos-color-surface);color:var(--pos-color-text-muted);cursor:pointer}.pos-header__menu-button svg{width:1.25rem;height:1.25rem;fill:currentColor}.pos-header__menu-button:disabled{background:var(--pos-color-disabled);color:var(--pos-color-on-disabled);cursor:default}.pos-header__backdrop{position:fixed;inset:0;z-index:1;border:0;background:transparent;cursor:default}.pos-header__menu{position:absolute;top:calc(100% + var(--pos-space-xs));right:0;z-index:2;box-sizing:border-box;width:16rem;padding:var(--pos-space-sm);border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-lg);background:var(--pos-color-surface);box-shadow:var(--pos-shadow-overlay)}.pos-header__logout{display:flex;align-items:center;width:100%;min-height:var(--pos-button-min-height);padding:0 var(--pos-space-md);border:0;border-radius:var(--pos-radius-md);background:transparent;color:var(--pos-color-danger);font:inherit;font-weight:var(--pos-font-weight-semibold);text-align:left;cursor:pointer}.pos-header__logout:hover{background:color-mix(in srgb,var(--pos-color-danger) 12%,var(--pos-color-surface))}\n"] }]
|
|
245
245
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], showBrandImage: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBrandImage", required: false }] }], brandImage: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandImage", required: false }] }], brandLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLabel", required: false }] }], clerkName: [{ type: i0.Input, args: [{ isSignal: true, alias: "clerkName", required: false }] }], clerkRole: [{ type: i0.Input, args: [{ isSignal: true, alias: "clerkRole", required: false }] }], signOutLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "signOutLabel", required: false }] }], menuLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuLabel", required: false }] }], canSignOut: [{ type: i0.Input, args: [{ isSignal: true, alias: "canSignOut", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
|
|
246
246
|
|
|
247
|
-
/** Bar buttons, in display order. Other envelope actions belong to other components. */
|
|
248
|
-
const BUTTONS = [
|
|
249
|
-
{
|
|
250
|
-
id: 'clearBasket',
|
|
251
|
-
label: 'Cancel',
|
|
252
|
-
className: 'pos-transaction-action-bar__button--cancel',
|
|
253
|
-
intent: { type: 'actionBar.clearBasket' },
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
id: 'tender',
|
|
257
|
-
label: 'Cash',
|
|
258
|
-
className: 'pos-transaction-action-bar__button--cash',
|
|
259
|
-
intent: { type: 'actionBar.tender' },
|
|
260
|
-
},
|
|
261
|
-
];
|
|
262
247
|
class PosTransactionActionBar {
|
|
263
|
-
/**
|
|
248
|
+
/** Envelope action names. `clearBasket` shows Cancel. `tender` shows Cash. */
|
|
264
249
|
availableActions = input([], ...(ngDevMode ? [{ debugName: "availableActions" }] : /* istanbul ignore next */ []));
|
|
265
|
-
/** Locks the shown buttons while the host is busy. Does not choose which actions exist. */
|
|
266
250
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
267
251
|
action = output();
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (this.disabled()) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
this.action.emit(button.intent);
|
|
252
|
+
onClear() {
|
|
253
|
+
this.action.emit({ type: 'actionBar.clearBasket' });
|
|
254
|
+
}
|
|
255
|
+
onCash() {
|
|
256
|
+
this.action.emit({ type: 'actionBar.tender' });
|
|
277
257
|
}
|
|
278
258
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosTransactionActionBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
279
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: PosTransactionActionBar, isStandalone: true, selector: "pos-transaction-action-bar", inputs: { availableActions: { classPropertyName: "availableActions", publicName: "availableActions", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, host: {
|
|
259
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: PosTransactionActionBar, isStandalone: true, selector: "pos-transaction-action-bar", inputs: { availableActions: { classPropertyName: "availableActions", publicName: "availableActions", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, host: { classAttribute: "pos-transaction-action-bar" }, ngImport: i0, template: "@if (availableActions().includes('clearBasket')) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-transaction-action-bar__button pos-transaction-action-bar__button--cancel\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"onClear()\"\r\n >\r\n Cancel\r\n </button>\r\n}\r\n@if (availableActions().includes('tender')) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-transaction-action-bar__button pos-transaction-action-bar__button--cash\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"onCash()\"\r\n >\r\n Cash\r\n </button>\r\n}\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-wrap:wrap;align-items:stretch;gap:var(--pos-space-sm);width:100%;box-sizing:border-box;padding-top:var(--pos-space-md);border-top:1px solid var(--pos-color-border)}.pos-transaction-action-bar__button{flex:1 1 8rem;min-height:var(--pos-button-min-height);border:1px solid transparent;border-radius:var(--pos-radius-md);font:inherit;font-size:var(--pos-font-size-md);font-weight:var(--pos-font-weight-semibold);cursor:pointer}.pos-transaction-action-bar__button--cancel{background:var(--pos-color-danger);color:var(--pos-color-on-danger)}.pos-transaction-action-bar__button--cash{background:var(--pos-color-success);color:var(--pos-color-on-success)}.pos-transaction-action-bar__button:disabled{background:var(--pos-color-disabled);color:var(--pos-color-on-disabled);cursor:default}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
280
260
|
}
|
|
281
261
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosTransactionActionBar, decorators: [{
|
|
282
262
|
type: Component,
|
|
283
263
|
args: [{ selector: 'pos-transaction-action-bar', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
284
264
|
class: 'pos-transaction-action-bar',
|
|
285
|
-
|
|
286
|
-
'aria-label': 'Sale actions',
|
|
287
|
-
}, template: "@for (button of buttons(); track button.id) {\r\n <button\r\n type=\"button\"\r\n [class]=\"'pos-transaction-action-bar__button ' + button.className\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"onPress(button)\"\r\n >\r\n {{ button.label }}\r\n </button>\r\n}\r\n", styles: [":host{display:flex;flex-wrap:wrap;align-items:stretch;gap:var(--pos-space-md);width:100%;box-sizing:border-box;padding-top:var(--pos-space-md);border-top:1px solid var(--pos-color-border);color:var(--pos-color-text);font-family:var(--pos-font-family);font-size:var(--pos-font-size-md)}:host:empty{display:none}.pos-transaction-action-bar__button{flex:1 1 8rem;min-height:var(--pos-button-min-height);border:1px solid transparent;border-radius:var(--pos-radius-md);font:inherit;font-weight:var(--pos-font-weight-semibold);cursor:pointer}.pos-transaction-action-bar__button--cancel{background:var(--pos-color-danger);color:var(--pos-color-on-danger)}.pos-transaction-action-bar__button--cash{background:var(--pos-color-success);color:var(--pos-color-on-success)}.pos-transaction-action-bar__button:disabled{background:var(--pos-color-disabled);color:var(--pos-color-on-disabled);cursor:default}\n"] }]
|
|
265
|
+
}, template: "@if (availableActions().includes('clearBasket')) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-transaction-action-bar__button pos-transaction-action-bar__button--cancel\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"onClear()\"\r\n >\r\n Cancel\r\n </button>\r\n}\r\n@if (availableActions().includes('tender')) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-transaction-action-bar__button pos-transaction-action-bar__button--cash\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"onCash()\"\r\n >\r\n Cash\r\n </button>\r\n}\r\n<ng-content />\r\n", styles: [":host{display:flex;flex-wrap:wrap;align-items:stretch;gap:var(--pos-space-sm);width:100%;box-sizing:border-box;padding-top:var(--pos-space-md);border-top:1px solid var(--pos-color-border)}.pos-transaction-action-bar__button{flex:1 1 8rem;min-height:var(--pos-button-min-height);border:1px solid transparent;border-radius:var(--pos-radius-md);font:inherit;font-size:var(--pos-font-size-md);font-weight:var(--pos-font-weight-semibold);cursor:pointer}.pos-transaction-action-bar__button--cancel{background:var(--pos-color-danger);color:var(--pos-color-on-danger)}.pos-transaction-action-bar__button--cash{background:var(--pos-color-success);color:var(--pos-color-on-success)}.pos-transaction-action-bar__button:disabled{background:var(--pos-color-disabled);color:var(--pos-color-on-disabled);cursor:default}\n"] }]
|
|
288
266
|
}], propDecorators: { availableActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableActions", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
|
|
289
267
|
|
|
290
268
|
class PosItemEntry {
|
|
@@ -354,6 +332,1747 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
354
332
|
}, template: "<section\r\n class=\"pos-item-entry__panel\"\r\n [class.pos-item-entry__panel--bordered]=\"bordered()\"\r\n [attr.aria-busy]=\"disabled() ? true : null\"\r\n>\r\n <form class=\"pos-item-entry__form\" (submit)=\"onSubmit($event)\">\r\n <div\r\n class=\"pos-item-entry__omnibox\"\r\n [class.pos-item-entry__omnibox--disabled]=\"disabled()\"\r\n >\r\n <label class=\"pos-item-entry__field\">\r\n <span class=\"pos-item-entry__label\">{{ label() }}</span>\r\n <svg\r\n class=\"pos-item-entry__search\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n aria-hidden=\"true\"\r\n >\r\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\r\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\" />\r\n </svg>\r\n <input\r\n #itemInput\r\n class=\"pos-item-entry__input\"\r\n type=\"text\"\r\n name=\"itemReference\"\r\n autocomplete=\"off\"\r\n spellcheck=\"false\"\r\n [attr.autocorrect]=\"'off'\"\r\n [attr.autocapitalize]=\"'none'\"\r\n enterkeyhint=\"done\"\r\n [placeholder]=\"placeholder()\"\r\n [value]=\"reference()\"\r\n [disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorMessage() ? true : null\"\r\n [attr.aria-keyshortcuts]=\"focusHotkey() || null\"\r\n (input)=\"onInput($event)\"\r\n />\r\n </label>\r\n\r\n <span class=\"pos-item-entry__scanner\">\r\n <svg\r\n class=\"pos-item-entry__scan\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n aria-hidden=\"true\"\r\n >\r\n <line x1=\"4\" y1=\"6\" x2=\"4\" y2=\"18\" />\r\n <line x1=\"7\" y1=\"6\" x2=\"7\" y2=\"18\" />\r\n <line x1=\"11\" y1=\"6\" x2=\"11\" y2=\"18\" />\r\n <line x1=\"14\" y1=\"6\" x2=\"14\" y2=\"18\" />\r\n <line x1=\"18\" y1=\"6\" x2=\"18\" y2=\"18\" />\r\n <line x1=\"21\" y1=\"6\" x2=\"21\" y2=\"18\" />\r\n </svg>\r\n {{ scannerStatus() }}\r\n </span>\r\n\r\n @if (focusHotkey()) {\r\n <kbd class=\"pos-item-entry__hotkey\">{{ focusHotkey() }}</kbd>\r\n }\r\n </div>\r\n </form>\r\n\r\n <div class=\"pos-item-entry__status\" aria-live=\"polite\">\r\n @if (errorMessage()) {\r\n <p class=\"pos-item-entry__error\">{{ errorMessage() }}</p>\r\n }\r\n </div>\r\n</section>\r\n", styles: [":host{display:block;width:100%;color:var(--pos-color-text);font-family:var(--pos-font-family);font-size:var(--pos-font-size-md)}.pos-item-entry__panel{display:flex;flex-direction:column;gap:var(--pos-space-sm)}.pos-item-entry__panel--bordered{padding:var(--pos-space-lg);border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-lg);background:var(--pos-color-panel)}.pos-item-entry__form{min-width:0}.pos-item-entry__omnibox{display:flex;align-items:center;gap:var(--pos-space-sm);box-sizing:border-box;min-height:var(--pos-button-min-height);padding:0 var(--pos-space-md) 0 var(--pos-space-lg);border:1px solid transparent;border-radius:var(--pos-radius-pill);background:var(--pos-color-surface-muted);transition:border-color .12s ease,box-shadow .12s ease,background-color .12s ease}.pos-item-entry__omnibox:focus-within{border-color:var(--pos-color-primary);background:var(--pos-color-surface);box-shadow:0 0 0 3px color-mix(in srgb,var(--pos-color-primary) 35%,transparent)}.pos-item-entry__omnibox:has(.pos-item-entry__input[aria-invalid=true]){border-color:var(--pos-color-danger)}.pos-item-entry__omnibox--disabled{cursor:not-allowed;background:var(--pos-color-disabled)}.pos-item-entry__field{display:flex;flex:1 1 auto;align-items:center;gap:var(--pos-space-sm);min-width:0}.pos-item-entry__label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pos-item-entry__search,.pos-item-entry__scan{flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.pos-item-entry__search{width:15px;height:15px;color:var(--pos-color-text-muted)}.pos-item-entry__input{box-sizing:border-box;flex:1 1 auto;width:100%;min-width:0;min-height:calc(var(--pos-button-min-height) - 8px);margin:0;padding:0;border:0;background:transparent;color:var(--pos-color-text);font:inherit;outline:none}.pos-item-entry__input::placeholder{color:var(--pos-color-text-muted);opacity:1}.pos-item-entry__input:disabled{cursor:not-allowed;color:var(--pos-color-on-disabled)}.pos-item-entry__scanner{display:inline-flex;flex:0 0 auto;align-items:center;gap:var(--pos-space-xs);padding:2px var(--pos-space-sm);border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-sm);background:var(--pos-color-surface);color:var(--pos-color-text-muted);font-size:.72em;line-height:1.2;white-space:nowrap}.pos-item-entry__omnibox--disabled .pos-item-entry__scanner{color:var(--pos-color-on-disabled)}.pos-item-entry__scan{width:11px;height:11px}.pos-item-entry__hotkey{flex:0 0 auto;margin:0;padding:0;border:0;background:transparent;color:var(--pos-color-text-muted);font-family:inherit;font-size:var(--pos-font-size-sm);font-weight:var(--pos-font-weight-semibold);line-height:1}.pos-item-entry__status{min-height:0}.pos-item-entry__error{margin:0;padding:var(--pos-space-sm) var(--pos-space-md);border-left:3px solid var(--pos-color-danger);background:color-mix(in srgb,var(--pos-color-danger) 12%,transparent);color:var(--pos-color-danger);font-size:var(--pos-font-size-sm);font-weight:var(--pos-font-weight-semibold)}@media(max-width:479px){.pos-item-entry__hotkey{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}}@media(min-width:768px){.pos-item-entry__panel--bordered{padding:var(--pos-space-xl)}}@media(prefers-reduced-motion:reduce){.pos-item-entry__omnibox{transition:none}}\n"] }]
|
|
355
333
|
}], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], scannerStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "scannerStatus", required: false }] }], focusHotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusHotkey", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], itemInput: [{ type: i0.ViewChild, args: ['itemInput', { isSignal: true }] }] } });
|
|
356
334
|
|
|
335
|
+
const POS_WORKSPACE = new InjectionToken('POS_WORKSPACE');
|
|
336
|
+
|
|
337
|
+
const POS_WORKSPACE_PANE_IDS = ['header', 'itemEntry', 'basket', 'actionBar'];
|
|
338
|
+
const EMPTY_PANE_ID = /^empty-(\d+)$/;
|
|
339
|
+
/** JSON the consuming POS passes back in as the `layout` input. */
|
|
340
|
+
function workspaceLayoutJson(layout) {
|
|
341
|
+
const document = {
|
|
342
|
+
startColumnRatio: layout.startColumnRatio,
|
|
343
|
+
panes: layout.panes.map((pane) => {
|
|
344
|
+
const placement = {
|
|
345
|
+
id: pane.id,
|
|
346
|
+
column: pane.column,
|
|
347
|
+
order: pane.order,
|
|
348
|
+
weight: pane.weight,
|
|
349
|
+
};
|
|
350
|
+
let next = placement;
|
|
351
|
+
if (pane.span === 'bottom') {
|
|
352
|
+
next = { ...placement, span: 'bottom' };
|
|
353
|
+
}
|
|
354
|
+
else if (pane.span === 'top') {
|
|
355
|
+
next = { ...placement, fullWidth: true, span: 'top' };
|
|
356
|
+
}
|
|
357
|
+
else if (pane.fullWidth) {
|
|
358
|
+
next = { ...placement, fullWidth: true };
|
|
359
|
+
}
|
|
360
|
+
if (pane.visible === false) {
|
|
361
|
+
next = { ...next, visible: false };
|
|
362
|
+
}
|
|
363
|
+
return next;
|
|
364
|
+
}),
|
|
365
|
+
};
|
|
366
|
+
return `${JSON.stringify(document, null, 2)}\n`;
|
|
367
|
+
}
|
|
368
|
+
/** Reads a file written by `workspaceLayoutJson`. */
|
|
369
|
+
function parseWorkspaceLayout(json) {
|
|
370
|
+
let value;
|
|
371
|
+
try {
|
|
372
|
+
value = JSON.parse(json);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
throw new Error('Layout file is not JSON.');
|
|
376
|
+
}
|
|
377
|
+
if (!isRecord(value) || typeof value['startColumnRatio'] !== 'number') {
|
|
378
|
+
throw new Error('Layout file is missing a column ratio.');
|
|
379
|
+
}
|
|
380
|
+
if (!Number.isFinite(value['startColumnRatio']) || !Array.isArray(value['panes'])) {
|
|
381
|
+
throw new Error('Layout file is missing a pane list.');
|
|
382
|
+
}
|
|
383
|
+
const seen = new Set();
|
|
384
|
+
return {
|
|
385
|
+
startColumnRatio: value['startColumnRatio'],
|
|
386
|
+
panes: value['panes'].map((pane) => readPlacement(pane, seen)),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function readPlacement(value, seen) {
|
|
390
|
+
if (!isRecord(value) || typeof value['id'] !== 'string' || !isPaneId(value['id'])) {
|
|
391
|
+
throw new Error('Layout file has a pane id this workspace does not know.');
|
|
392
|
+
}
|
|
393
|
+
if (seen.has(value['id'])) {
|
|
394
|
+
throw new Error('Layout file lists the same pane twice.');
|
|
395
|
+
}
|
|
396
|
+
seen.add(value['id']);
|
|
397
|
+
const column = value['column'];
|
|
398
|
+
const order = value['order'];
|
|
399
|
+
const weight = value['weight'];
|
|
400
|
+
if (column !== 0 && column !== 1) {
|
|
401
|
+
throw new Error('Layout file has a pane outside the two columns.');
|
|
402
|
+
}
|
|
403
|
+
if (typeof order !== 'number' || typeof weight !== 'number' || !Number.isFinite(order) || !Number.isFinite(weight)) {
|
|
404
|
+
throw new Error('Layout file has a pane without an order or weight.');
|
|
405
|
+
}
|
|
406
|
+
const placement = {
|
|
407
|
+
id: value['id'],
|
|
408
|
+
column,
|
|
409
|
+
order,
|
|
410
|
+
weight,
|
|
411
|
+
};
|
|
412
|
+
let next = placement;
|
|
413
|
+
if (value['span'] === 'bottom') {
|
|
414
|
+
next = { ...placement, span: 'bottom' };
|
|
415
|
+
}
|
|
416
|
+
else if (value['span'] === 'top') {
|
|
417
|
+
next = { ...placement, fullWidth: true, span: 'top' };
|
|
418
|
+
}
|
|
419
|
+
else if (value['fullWidth'] === true) {
|
|
420
|
+
next = { ...placement, fullWidth: true };
|
|
421
|
+
}
|
|
422
|
+
if (value['visible'] === false) {
|
|
423
|
+
return { ...next, visible: false };
|
|
424
|
+
}
|
|
425
|
+
if (value['visible'] !== undefined && value['visible'] !== true) {
|
|
426
|
+
throw new Error('Layout file has a pane visibility that is not true or false.');
|
|
427
|
+
}
|
|
428
|
+
return next;
|
|
429
|
+
}
|
|
430
|
+
function isPaneId(id) {
|
|
431
|
+
return isEmptyPaneId(id) || POS_WORKSPACE_PANE_IDS.some((paneId) => paneId === id);
|
|
432
|
+
}
|
|
433
|
+
function isRecord(value) {
|
|
434
|
+
return typeof value === 'object' && value !== null;
|
|
435
|
+
}
|
|
436
|
+
const POS_WORKSPACE_PANE_LABELS = {
|
|
437
|
+
header: 'Header',
|
|
438
|
+
itemEntry: 'Item lookup',
|
|
439
|
+
basket: 'Basket',
|
|
440
|
+
actionBar: 'Action bar',
|
|
441
|
+
};
|
|
442
|
+
function isEmptyPaneId(id) {
|
|
443
|
+
return EMPTY_PANE_ID.test(id);
|
|
444
|
+
}
|
|
445
|
+
function workspacePaneLabel(id) {
|
|
446
|
+
if (isEmptyPaneId(id)) {
|
|
447
|
+
const number = EMPTY_PANE_ID.exec(id)?.[1];
|
|
448
|
+
return `Custom container ${number}`;
|
|
449
|
+
}
|
|
450
|
+
return POS_WORKSPACE_PANE_LABELS[id];
|
|
451
|
+
}
|
|
452
|
+
function nextEmptyId(layout) {
|
|
453
|
+
let highest = 0;
|
|
454
|
+
for (const pane of layout.panes) {
|
|
455
|
+
const number = EMPTY_PANE_ID.exec(pane.id)?.[1];
|
|
456
|
+
if (number) {
|
|
457
|
+
highest = Math.max(highest, Number(number));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return `empty-${highest + 1}`;
|
|
461
|
+
}
|
|
462
|
+
/** Same stack the sale screen uses today: header, item lookup, flexible basket, action bar. */
|
|
463
|
+
const POS_WORKSPACE_DEFAULT_LAYOUT = {
|
|
464
|
+
startColumnRatio: 0.58,
|
|
465
|
+
panes: [
|
|
466
|
+
{ id: 'header', column: 0, order: 0, weight: 0 },
|
|
467
|
+
{ id: 'itemEntry', column: 0, order: 1, weight: 0 },
|
|
468
|
+
{ id: 'basket', column: 0, order: 2, weight: 1 },
|
|
469
|
+
{ id: 'actionBar', column: 0, order: 3, weight: 0 },
|
|
470
|
+
],
|
|
471
|
+
};
|
|
472
|
+
/** Basket docked on the right at full height. Item lookup is marked flexible so it fills above the action bar. */
|
|
473
|
+
const POS_WORKSPACE_BASKET_RIGHT_LAYOUT = {
|
|
474
|
+
startColumnRatio: 0.58,
|
|
475
|
+
panes: [
|
|
476
|
+
{ id: 'header', column: 0, order: 0, weight: 0 },
|
|
477
|
+
{ id: 'itemEntry', column: 0, order: 1, weight: 1 },
|
|
478
|
+
{ id: 'actionBar', column: 0, order: 2, weight: 0 },
|
|
479
|
+
{ id: 'basket', column: 1, order: 0, weight: 1 },
|
|
480
|
+
],
|
|
481
|
+
};
|
|
482
|
+
/** Nothing placed yet. Edit mode lists the library components so they can be dragged on. */
|
|
483
|
+
const POS_WORKSPACE_EMPTY_LAYOUT = {
|
|
484
|
+
startColumnRatio: 0.58,
|
|
485
|
+
panes: [],
|
|
486
|
+
};
|
|
487
|
+
/** Header spans both columns, with the basket docked on the right. */
|
|
488
|
+
const POS_WORKSPACE_HEADER_SPAN_LAYOUT = {
|
|
489
|
+
startColumnRatio: 0.58,
|
|
490
|
+
panes: [
|
|
491
|
+
{ id: 'header', column: 0, order: 0, weight: 0, fullWidth: true, span: 'top' },
|
|
492
|
+
{ id: 'itemEntry', column: 0, order: 0, weight: 1 },
|
|
493
|
+
{ id: 'actionBar', column: 0, order: 1, weight: 0 },
|
|
494
|
+
{ id: 'basket', column: 1, order: 0, weight: 1 },
|
|
495
|
+
],
|
|
496
|
+
};
|
|
497
|
+
/** Legacy edge. `fullWidth` without `span` stays in the stack where the designer placed it. */
|
|
498
|
+
function paneSpan(pane) {
|
|
499
|
+
if (pane.span === 'bottom') {
|
|
500
|
+
return 'bottom';
|
|
501
|
+
}
|
|
502
|
+
if (pane.span === 'top') {
|
|
503
|
+
return 'top';
|
|
504
|
+
}
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
/** True when the pane spans both columns, either in place or on a legacy edge. */
|
|
508
|
+
function paneFillsWidth(pane) {
|
|
509
|
+
return pane.fullWidth === true || pane.span === 'top' || pane.span === 'bottom';
|
|
510
|
+
}
|
|
511
|
+
const MIN_RATIO = 0.28;
|
|
512
|
+
const MAX_RATIO = 0.72;
|
|
513
|
+
const MIN_WEIGHT = 0.35;
|
|
514
|
+
const MAX_WEIGHT = 8;
|
|
515
|
+
function workspaceGrid(layout, present, options) {
|
|
516
|
+
const placed = placedPanes(layout, present);
|
|
517
|
+
const panes = options?.includeHidden
|
|
518
|
+
? placed
|
|
519
|
+
: placed.filter((pane) => pane.visible !== false);
|
|
520
|
+
const top = panes.filter((pane) => paneSpan(pane) === 'top').sort((a, b) => a.order - b.order);
|
|
521
|
+
const bottom = panes
|
|
522
|
+
.filter((pane) => paneSpan(pane) === 'bottom')
|
|
523
|
+
.sort((a, b) => a.order - b.order);
|
|
524
|
+
const body = panes.filter((pane) => paneSpan(pane) === null);
|
|
525
|
+
let grid;
|
|
526
|
+
if (options?.compact === true) {
|
|
527
|
+
const stacked = compactStack(columnList(body, 0), columnList(body, 1));
|
|
528
|
+
grid = singleColumnGrid([...top, ...stacked, ...bottom]);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
const left = columnList(body, 0);
|
|
532
|
+
const rightWide = columnList(body, 1).filter((pane) => pane.fullWidth === true);
|
|
533
|
+
const right = columnList(body, 1).filter((pane) => pane.fullWidth !== true);
|
|
534
|
+
const sequence = [...left, ...rightWide].sort((a, b) => a.order - b.order || a.column - b.column);
|
|
535
|
+
grid = gridFromSequence(sequence, right, layout.startColumnRatio);
|
|
536
|
+
if (bottom.length > 0) {
|
|
537
|
+
grid = withFooter(bottom, grid);
|
|
538
|
+
}
|
|
539
|
+
if (top.length > 0) {
|
|
540
|
+
grid = withBanner(top, grid);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return options?.includeHidden ? markConcealed(grid, panes) : grid;
|
|
544
|
+
}
|
|
545
|
+
function markConcealed(grid, panes) {
|
|
546
|
+
const hidden = new Set(panes.filter((pane) => pane.visible === false).map((pane) => pane.id));
|
|
547
|
+
if (hidden.size === 0) {
|
|
548
|
+
return grid;
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
...grid,
|
|
552
|
+
panes: grid.panes.map((pane) => (hidden.has(pane.id) ? { ...pane, concealed: true } : pane)),
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function gridFromSequence(sequence, right, ratio) {
|
|
556
|
+
if (!sequence.some((pane) => pane.fullWidth === true)) {
|
|
557
|
+
const left = sequence.filter((pane) => pane.column === 0);
|
|
558
|
+
return bodyGridFor(left.length > 0 ? left : sequence, right, ratio);
|
|
559
|
+
}
|
|
560
|
+
const blocks = [];
|
|
561
|
+
let run = [];
|
|
562
|
+
for (const pane of sequence) {
|
|
563
|
+
if (pane.fullWidth === true) {
|
|
564
|
+
if (run.length > 0) {
|
|
565
|
+
blocks.push({ left: run });
|
|
566
|
+
run = [];
|
|
567
|
+
}
|
|
568
|
+
blocks.push({ span: pane, left: [] });
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
run.push(pane);
|
|
572
|
+
}
|
|
573
|
+
if (run.length > 0) {
|
|
574
|
+
blocks.push({ left: run });
|
|
575
|
+
}
|
|
576
|
+
const stacks = blocks.filter((block) => block.span == null);
|
|
577
|
+
const rights = stacks.map(() => []);
|
|
578
|
+
if (stacks.length === 0) {
|
|
579
|
+
if (right.length > 0) {
|
|
580
|
+
blocks.push({ left: [] });
|
|
581
|
+
rights.push([...right]);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else if (stacks.length === 1) {
|
|
585
|
+
rights[0] = [...right];
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
for (const pane of right) {
|
|
589
|
+
let best = 0;
|
|
590
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
591
|
+
stacks.forEach((block, index) => {
|
|
592
|
+
const distance = Math.min(...block.left.map((item) => Math.abs(item.order - pane.order)));
|
|
593
|
+
if (distance < bestDistance) {
|
|
594
|
+
best = index;
|
|
595
|
+
bestDistance = distance;
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
rights[best].push(pane);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
let row = 1;
|
|
602
|
+
let stackIndex = 0;
|
|
603
|
+
const tracks = [];
|
|
604
|
+
const panes = [];
|
|
605
|
+
let split = false;
|
|
606
|
+
let columns = 'minmax(0, 1fr)';
|
|
607
|
+
for (const block of blocks) {
|
|
608
|
+
const sub = block.span != null
|
|
609
|
+
? singleColumnGrid([block.span])
|
|
610
|
+
: bodyGridFor(block.left, rights[stackIndex++] ?? [], ratio);
|
|
611
|
+
if (sub.split) {
|
|
612
|
+
split = true;
|
|
613
|
+
columns = sub.columns;
|
|
614
|
+
}
|
|
615
|
+
for (const track of splitTemplate(sub.rows)) {
|
|
616
|
+
tracks.push(track);
|
|
617
|
+
}
|
|
618
|
+
for (const pane of sub.panes) {
|
|
619
|
+
panes.push({
|
|
620
|
+
...pane,
|
|
621
|
+
rowStart: pane.rowStart + row - 1,
|
|
622
|
+
rowEnd: pane.rowEnd + row - 1,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
row += splitTemplate(sub.rows).length;
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
columns: split ? columns : 'minmax(0, 1fr)',
|
|
629
|
+
rows: tracks.join(' ') || 'minmax(0, 1fr)',
|
|
630
|
+
split,
|
|
631
|
+
panes,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function splitTemplate(template) {
|
|
635
|
+
const tracks = [];
|
|
636
|
+
let depth = 0;
|
|
637
|
+
let current = '';
|
|
638
|
+
for (const char of template.trim()) {
|
|
639
|
+
if (char === '(') {
|
|
640
|
+
depth += 1;
|
|
641
|
+
}
|
|
642
|
+
else if (char === ')') {
|
|
643
|
+
depth = Math.max(0, depth - 1);
|
|
644
|
+
}
|
|
645
|
+
if (depth === 0 && char === ' ') {
|
|
646
|
+
if (current) {
|
|
647
|
+
tracks.push(current);
|
|
648
|
+
}
|
|
649
|
+
current = '';
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
current += char;
|
|
653
|
+
}
|
|
654
|
+
if (current) {
|
|
655
|
+
tracks.push(current);
|
|
656
|
+
}
|
|
657
|
+
return tracks;
|
|
658
|
+
}
|
|
659
|
+
function bodyGridFor(left, right, ratio) {
|
|
660
|
+
if (left.length === 0 && right.length === 0) {
|
|
661
|
+
return {
|
|
662
|
+
columns: 'minmax(0, 1fr)',
|
|
663
|
+
rows: 'minmax(0, 1fr)',
|
|
664
|
+
split: false,
|
|
665
|
+
panes: [],
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
if (left.length === 0) {
|
|
669
|
+
return singleColumnGrid(right);
|
|
670
|
+
}
|
|
671
|
+
if (right.length === 0) {
|
|
672
|
+
return singleColumnGrid(left);
|
|
673
|
+
}
|
|
674
|
+
return splitGrid(left, right, ratio);
|
|
675
|
+
}
|
|
676
|
+
function withBanner(banner, body) {
|
|
677
|
+
const offset = banner.length;
|
|
678
|
+
return {
|
|
679
|
+
columns: body.columns,
|
|
680
|
+
rows: `${banner.map(() => 'auto').join(' ')} ${body.rows}`.trim(),
|
|
681
|
+
split: body.split,
|
|
682
|
+
panes: [
|
|
683
|
+
...banner.map((pane, index) => ({
|
|
684
|
+
id: pane.id,
|
|
685
|
+
column: 1,
|
|
686
|
+
rowStart: index + 1,
|
|
687
|
+
rowEnd: index + 2,
|
|
688
|
+
showResize: false,
|
|
689
|
+
spanColumns: true,
|
|
690
|
+
})),
|
|
691
|
+
...body.panes.map((pane) => ({
|
|
692
|
+
...pane,
|
|
693
|
+
rowStart: pane.rowStart + offset,
|
|
694
|
+
rowEnd: pane.rowEnd + offset,
|
|
695
|
+
})),
|
|
696
|
+
],
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function withFooter(footer, body) {
|
|
700
|
+
const offset = trackCount(body.rows);
|
|
701
|
+
return {
|
|
702
|
+
columns: body.columns,
|
|
703
|
+
rows: `${body.rows} ${footer.map(() => 'auto').join(' ')}`.trim(),
|
|
704
|
+
split: body.split,
|
|
705
|
+
panes: [
|
|
706
|
+
...body.panes,
|
|
707
|
+
...footer.map((pane, index) => ({
|
|
708
|
+
id: pane.id,
|
|
709
|
+
column: 1,
|
|
710
|
+
rowStart: offset + index + 1,
|
|
711
|
+
rowEnd: offset + index + 2,
|
|
712
|
+
showResize: false,
|
|
713
|
+
spanColumns: true,
|
|
714
|
+
})),
|
|
715
|
+
],
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function dropColumn(xRatio, splitThreshold, compact) {
|
|
719
|
+
if (compact) {
|
|
720
|
+
return 0;
|
|
721
|
+
}
|
|
722
|
+
return xRatio >= splitThreshold ? 1 : 0;
|
|
723
|
+
}
|
|
724
|
+
function insertionIndex(pointerY, midpoints) {
|
|
725
|
+
for (let index = 0; index < midpoints.length; index += 1) {
|
|
726
|
+
if (pointerY < midpoints[index]) {
|
|
727
|
+
return index;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return midpoints.length;
|
|
731
|
+
}
|
|
732
|
+
function movePane(layout, id, column, index) {
|
|
733
|
+
const current = layout.panes.find((pane) => pane.id === id);
|
|
734
|
+
if (!current) {
|
|
735
|
+
return layout;
|
|
736
|
+
}
|
|
737
|
+
const remaining = layout.panes.filter((pane) => pane.id !== id);
|
|
738
|
+
const target = remaining
|
|
739
|
+
.filter((pane) => pane.column === column && paneSpan(pane) === null)
|
|
740
|
+
.sort((a, b) => a.order - b.order);
|
|
741
|
+
const others = remaining.filter((pane) => paneSpan(pane) !== null || pane.column !== column);
|
|
742
|
+
const nextIndex = clamp(index, 0, target.length);
|
|
743
|
+
// Drag only places. Keep Full width when the designer already turned it on; never invent it
|
|
744
|
+
// from a legacy span edge.
|
|
745
|
+
const placed = current.fullWidth === true
|
|
746
|
+
? { ...current, column, span: undefined, fullWidth: true }
|
|
747
|
+
: { ...current, column, span: undefined, fullWidth: undefined };
|
|
748
|
+
target.splice(nextIndex, 0, placed);
|
|
749
|
+
const panes = sortPanes([
|
|
750
|
+
...reindex(others),
|
|
751
|
+
...target.map((pane, order) => ({ ...pane, column, order })),
|
|
752
|
+
]);
|
|
753
|
+
const next = { ...layout, panes };
|
|
754
|
+
return sameLayout(layout, next) ? layout : next;
|
|
755
|
+
}
|
|
756
|
+
/** Adds a region to a column. An omitted index stacks it after the panes already there. */
|
|
757
|
+
function addPane(layout, id, column = 0, index) {
|
|
758
|
+
if (layout.panes.some((pane) => pane.id === id)) {
|
|
759
|
+
return layout;
|
|
760
|
+
}
|
|
761
|
+
const target = layout.panes
|
|
762
|
+
.filter((pane) => pane.column === column && paneSpan(pane) === null)
|
|
763
|
+
.sort((a, b) => a.order - b.order);
|
|
764
|
+
const others = layout.panes.filter((pane) => paneSpan(pane) !== null || pane.column !== column);
|
|
765
|
+
target.splice(index === undefined ? target.length : clamp(index, 0, target.length), 0, {
|
|
766
|
+
id,
|
|
767
|
+
column,
|
|
768
|
+
order: 0,
|
|
769
|
+
weight: id === 'basket' ? 1 : 0,
|
|
770
|
+
});
|
|
771
|
+
return {
|
|
772
|
+
...layout,
|
|
773
|
+
panes: sortPanes([
|
|
774
|
+
...reindex(others),
|
|
775
|
+
...target.map((pane, order) => ({ ...pane, column, order })),
|
|
776
|
+
]),
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
/** Drops a region from the arrangement. A library component can be added again; an empty slot is gone. */
|
|
780
|
+
function removePane(layout, id) {
|
|
781
|
+
if (!layout.panes.some((pane) => pane.id === id)) {
|
|
782
|
+
return layout;
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
...layout,
|
|
786
|
+
panes: sortPanes(reindex(layout.panes.filter((pane) => pane.id !== id))),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
/** Lifts a pane into a row that spans both columns. Column placement is kept for when it returns. */
|
|
790
|
+
function dockFullWidth(layout, id, index) {
|
|
791
|
+
return dockSpan(layout, id, 'top', index);
|
|
792
|
+
}
|
|
793
|
+
/** Places a pane in a row that spans both columns, above or below the split. */
|
|
794
|
+
function dockSpan(layout, id, edge, index) {
|
|
795
|
+
const current = layout.panes.find((pane) => pane.id === id);
|
|
796
|
+
if (!current) {
|
|
797
|
+
return layout;
|
|
798
|
+
}
|
|
799
|
+
const remaining = layout.panes.filter((pane) => pane.id !== id);
|
|
800
|
+
const top = remaining
|
|
801
|
+
.filter((pane) => paneSpan(pane) === 'top')
|
|
802
|
+
.sort((a, b) => a.order - b.order);
|
|
803
|
+
const bottom = remaining
|
|
804
|
+
.filter((pane) => paneSpan(pane) === 'bottom')
|
|
805
|
+
.sort((a, b) => a.order - b.order);
|
|
806
|
+
const body = remaining.filter((pane) => paneSpan(pane) === null);
|
|
807
|
+
const group = edge === 'top' ? top : bottom;
|
|
808
|
+
group.splice(clamp(index, 0, group.length), 0, current);
|
|
809
|
+
const panes = sortPanes([
|
|
810
|
+
...top.map((pane, order) => ({ ...pane, fullWidth: true, span: 'top', order })),
|
|
811
|
+
...bottom.map((pane, order) => ({ ...pane, fullWidth: false, span: 'bottom', order })),
|
|
812
|
+
...reindex(body),
|
|
813
|
+
]);
|
|
814
|
+
const next = { ...layout, panes };
|
|
815
|
+
return sameLayout(layout, next) ? layout : next;
|
|
816
|
+
}
|
|
817
|
+
function resizeAdjacent(layout, id, nextId, delta) {
|
|
818
|
+
const current = layout.panes.find((pane) => pane.id === id);
|
|
819
|
+
const next = layout.panes.find((pane) => pane.id === nextId);
|
|
820
|
+
if (!current || !next || current.id === next.id) {
|
|
821
|
+
return layout;
|
|
822
|
+
}
|
|
823
|
+
const currentBase = current.weight > 0 ? current.weight : 1;
|
|
824
|
+
const nextBase = next.weight > 0 ? next.weight : 1;
|
|
825
|
+
const grown = clamp(currentBase + delta, MIN_WEIGHT, MAX_WEIGHT);
|
|
826
|
+
const requested = grown - currentBase;
|
|
827
|
+
const nextWeight = clamp(nextBase - requested, MIN_WEIGHT, MAX_WEIGHT);
|
|
828
|
+
const taken = nextBase - nextWeight;
|
|
829
|
+
const currentWeight = clamp(currentBase + taken, MIN_WEIGHT, MAX_WEIGHT);
|
|
830
|
+
return {
|
|
831
|
+
...layout,
|
|
832
|
+
panes: layout.panes.map((pane) => {
|
|
833
|
+
if (pane.id === current.id) {
|
|
834
|
+
return { ...pane, weight: roundWeight(currentWeight) };
|
|
835
|
+
}
|
|
836
|
+
if (pane.id === next.id) {
|
|
837
|
+
return { ...pane, weight: roundWeight(nextWeight) };
|
|
838
|
+
}
|
|
839
|
+
return pane;
|
|
840
|
+
}),
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
/** Weight 0 sizes to content. A positive weight shares leftover height in that column. */
|
|
844
|
+
/** Turns full width on at the pane's current place, or returns it to its column width. */
|
|
845
|
+
function setPaneFullWidth(layout, id, enabled) {
|
|
846
|
+
const current = layout.panes.find((pane) => pane.id === id);
|
|
847
|
+
if (!current) {
|
|
848
|
+
return layout;
|
|
849
|
+
}
|
|
850
|
+
const wide = paneFillsWidth(current);
|
|
851
|
+
if (!enabled && !wide) {
|
|
852
|
+
return layout;
|
|
853
|
+
}
|
|
854
|
+
if (enabled && current.fullWidth === true && current.span == null) {
|
|
855
|
+
return layout;
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
...layout,
|
|
859
|
+
panes: layout.panes.map((pane) => {
|
|
860
|
+
if (pane.id !== id) {
|
|
861
|
+
return pane;
|
|
862
|
+
}
|
|
863
|
+
if (!enabled) {
|
|
864
|
+
const { span: _span, fullWidth: _fullWidth, ...rest } = pane;
|
|
865
|
+
return rest;
|
|
866
|
+
}
|
|
867
|
+
const { span: _span, ...rest } = pane;
|
|
868
|
+
return { ...rest, fullWidth: true };
|
|
869
|
+
}),
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function setPaneWeight(layout, id, weight) {
|
|
873
|
+
const current = layout.panes.find((pane) => pane.id === id);
|
|
874
|
+
if (!current || current.weight === weight) {
|
|
875
|
+
return layout;
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
...layout,
|
|
879
|
+
panes: layout.panes.map((pane) => (pane.id === id ? { ...pane, weight } : pane)),
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
/** `false` keeps the pane in the layout and hides it on the till. `true` clears that flag. */
|
|
883
|
+
function setPaneVisible(layout, id, visible) {
|
|
884
|
+
const current = layout.panes.find((pane) => pane.id === id);
|
|
885
|
+
if (!current || (current.visible !== false) === visible) {
|
|
886
|
+
return layout;
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
...layout,
|
|
890
|
+
panes: layout.panes.map((pane) => {
|
|
891
|
+
if (pane.id !== id) {
|
|
892
|
+
return pane;
|
|
893
|
+
}
|
|
894
|
+
if (visible) {
|
|
895
|
+
const { visible: _hidden, ...shown } = pane;
|
|
896
|
+
return shown;
|
|
897
|
+
}
|
|
898
|
+
return { ...pane, visible: false };
|
|
899
|
+
}),
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function setColumnRatio(layout, ratio) {
|
|
903
|
+
const startColumnRatio = roundWeight(clamp(ratio, MIN_RATIO, MAX_RATIO));
|
|
904
|
+
if (startColumnRatio === layout.startColumnRatio) {
|
|
905
|
+
return layout;
|
|
906
|
+
}
|
|
907
|
+
return { ...layout, startColumnRatio };
|
|
908
|
+
}
|
|
909
|
+
function sameLayout(left, right) {
|
|
910
|
+
if (left.startColumnRatio !== right.startColumnRatio ||
|
|
911
|
+
left.panes.length !== right.panes.length) {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
const byId = (layout) => layout.panes.slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
915
|
+
return byId(left).every((pane, index) => {
|
|
916
|
+
const other = byId(right)[index];
|
|
917
|
+
return (pane.id === other.id &&
|
|
918
|
+
pane.column === other.column &&
|
|
919
|
+
pane.order === other.order &&
|
|
920
|
+
pane.weight === other.weight &&
|
|
921
|
+
paneSpan(pane) === paneSpan(other) &&
|
|
922
|
+
(pane.fullWidth === true) === (other.fullWidth === true) &&
|
|
923
|
+
(pane.visible !== false) === (other.visible !== false));
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
function placedPanes(layout, present) {
|
|
927
|
+
const presentIds = new Set(present);
|
|
928
|
+
const seen = new Set();
|
|
929
|
+
const panes = [];
|
|
930
|
+
for (const pane of layout.panes) {
|
|
931
|
+
if (seen.has(pane.id)) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
if (!presentIds.has(pane.id) && !isEmptyPaneId(pane.id)) {
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
seen.add(pane.id);
|
|
938
|
+
panes.push(pane);
|
|
939
|
+
}
|
|
940
|
+
return panes;
|
|
941
|
+
}
|
|
942
|
+
function visualColumns(panes, compact) {
|
|
943
|
+
const left = columnList(panes, 0);
|
|
944
|
+
const right = columnList(panes, 1);
|
|
945
|
+
if (compact) {
|
|
946
|
+
return [compactStack(left, right), []];
|
|
947
|
+
}
|
|
948
|
+
if (right.length === 0) {
|
|
949
|
+
return [left, []];
|
|
950
|
+
}
|
|
951
|
+
if (left.length === 0) {
|
|
952
|
+
return [right, []];
|
|
953
|
+
}
|
|
954
|
+
return [left, right];
|
|
955
|
+
}
|
|
956
|
+
/** On a narrow screen, keep the header at the top and the action bar at the bottom. */
|
|
957
|
+
function compactStack(left, right) {
|
|
958
|
+
const all = [...left, ...right];
|
|
959
|
+
const header = all.filter((pane) => pane.id === 'header');
|
|
960
|
+
const actionBar = all.filter((pane) => pane.id === 'actionBar');
|
|
961
|
+
const middle = all.filter((pane) => pane.id !== 'header' && pane.id !== 'actionBar');
|
|
962
|
+
return [...header, ...middle, ...actionBar];
|
|
963
|
+
}
|
|
964
|
+
function columnList(panes, column) {
|
|
965
|
+
return panes.filter((pane) => pane.column === column).sort((a, b) => a.order - b.order);
|
|
966
|
+
}
|
|
967
|
+
function singleColumnGrid(panes) {
|
|
968
|
+
const rows = panes.map((pane) => rowTrack(pane.weight));
|
|
969
|
+
return {
|
|
970
|
+
columns: 'minmax(0, 1fr)',
|
|
971
|
+
rows: rows.join(' '),
|
|
972
|
+
split: false,
|
|
973
|
+
panes: panes.map((pane, index) => ({
|
|
974
|
+
id: pane.id,
|
|
975
|
+
column: 1,
|
|
976
|
+
rowStart: index + 1,
|
|
977
|
+
rowEnd: index + 2,
|
|
978
|
+
showResize: index < panes.length - 1,
|
|
979
|
+
spanColumns: pane.fullWidth === true ? true : undefined,
|
|
980
|
+
})),
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function splitGrid(left, right, ratio) {
|
|
984
|
+
const spanLeft = left.length === 1 && right.length > 1;
|
|
985
|
+
const spanRight = right.length === 1 && left.length > 1;
|
|
986
|
+
if (!spanLeft && !spanRight) {
|
|
987
|
+
if (left.length === 1 && right.length === 1) {
|
|
988
|
+
return {
|
|
989
|
+
columns: columnTemplate(ratio),
|
|
990
|
+
rows: 'minmax(0, 1fr)',
|
|
991
|
+
split: true,
|
|
992
|
+
panes: [gridPane(left[0], 1, 1, 2, false), gridPane(right[0], 2, 1, 2, false)],
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
return sharedRowGrid(left, right, ratio);
|
|
996
|
+
}
|
|
997
|
+
const stack = spanRight ? left : right;
|
|
998
|
+
const spanning = spanRight ? right[0] : left[0];
|
|
999
|
+
const stackColumn = spanRight ? 1 : 2;
|
|
1000
|
+
const spanColumn = spanRight ? 2 : 1;
|
|
1001
|
+
const rows = stackTracks(stack);
|
|
1002
|
+
return {
|
|
1003
|
+
columns: columnTemplate(ratio),
|
|
1004
|
+
rows: rows.join(' '),
|
|
1005
|
+
split: true,
|
|
1006
|
+
panes: [
|
|
1007
|
+
...stack.map((pane, index) => gridPane(pane, stackColumn, index + 1, index + 2, index < stack.length - 1)),
|
|
1008
|
+
gridPane(spanning, spanColumn, 1, rows.length + 1, false),
|
|
1009
|
+
],
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
/** Row size follows weight. Weight 0 is the content height. A positive weight shares leftover height. */
|
|
1013
|
+
function stackTracks(stack) {
|
|
1014
|
+
return stack.map((pane) => rowTrack(pane.weight));
|
|
1015
|
+
}
|
|
1016
|
+
function sharedRowGrid(left, right, ratio) {
|
|
1017
|
+
const rowCount = Math.max(left.length, right.length);
|
|
1018
|
+
const rows = [];
|
|
1019
|
+
for (let index = 0; index < rowCount; index += 1) {
|
|
1020
|
+
const weight = Math.max(left[index]?.weight ?? 0, right[index]?.weight ?? 0);
|
|
1021
|
+
rows.push(weight > 0 ? rowTrack(weight) : 'auto');
|
|
1022
|
+
}
|
|
1023
|
+
const place = (list, column) => list.map((pane, index) => gridPane(pane, column, index + 1, index + 2, index < list.length - 1));
|
|
1024
|
+
return {
|
|
1025
|
+
columns: columnTemplate(ratio),
|
|
1026
|
+
rows: rows.join(' '),
|
|
1027
|
+
split: true,
|
|
1028
|
+
panes: [...place(left, 1), ...place(right, 2)],
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function gridPane(pane, column, rowStart, rowEnd, showResize) {
|
|
1032
|
+
return { id: pane.id, column, rowStart, rowEnd, showResize };
|
|
1033
|
+
}
|
|
1034
|
+
function columnTemplate(ratio) {
|
|
1035
|
+
const start = roundWeight(clamp(ratio, MIN_RATIO, MAX_RATIO));
|
|
1036
|
+
const end = roundWeight(1 - start);
|
|
1037
|
+
return `minmax(0, ${start}fr) minmax(0, ${end}fr)`;
|
|
1038
|
+
}
|
|
1039
|
+
/** Counts CSS grid tracks. Spaces inside minmax() stay part of one track. */
|
|
1040
|
+
function trackCount(template) {
|
|
1041
|
+
let depth = 0;
|
|
1042
|
+
let count = 0;
|
|
1043
|
+
let current = false;
|
|
1044
|
+
for (const char of template.trim()) {
|
|
1045
|
+
if (char === '(') {
|
|
1046
|
+
depth += 1;
|
|
1047
|
+
}
|
|
1048
|
+
else if (char === ')') {
|
|
1049
|
+
depth = Math.max(0, depth - 1);
|
|
1050
|
+
}
|
|
1051
|
+
if (depth === 0 && char === ' ') {
|
|
1052
|
+
current = false;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (!current) {
|
|
1056
|
+
current = true;
|
|
1057
|
+
count += 1;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return count;
|
|
1061
|
+
}
|
|
1062
|
+
function rowTrack(weight) {
|
|
1063
|
+
if (weight <= 0) {
|
|
1064
|
+
return 'auto';
|
|
1065
|
+
}
|
|
1066
|
+
return `minmax(0, ${roundWeight(weight)}fr)`;
|
|
1067
|
+
}
|
|
1068
|
+
function reindex(panes) {
|
|
1069
|
+
const columns = [0, 1];
|
|
1070
|
+
const result = [];
|
|
1071
|
+
for (const column of columns) {
|
|
1072
|
+
columnList(panes, column).forEach((pane, order) => {
|
|
1073
|
+
result.push({ ...pane, order });
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return result;
|
|
1077
|
+
}
|
|
1078
|
+
function sortPanes(panes) {
|
|
1079
|
+
return panes.slice().sort((a, b) => a.column - b.column || a.order - b.order);
|
|
1080
|
+
}
|
|
1081
|
+
function roundWeight(value) {
|
|
1082
|
+
return Math.round(value * 100) / 100;
|
|
1083
|
+
}
|
|
1084
|
+
function clamp(value, min, max) {
|
|
1085
|
+
return Math.min(max, Math.max(min, value));
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
class PosWorkspacePane {
|
|
1089
|
+
workspace = inject(POS_WORKSPACE);
|
|
1090
|
+
paneId = input.required(...(ngDevMode ? [{ debugName: "paneId" }] : /* istanbul ignore next */ []));
|
|
1091
|
+
/** Draws the unassigned-slot message. The POS replaces this by projecting the same pane id. */
|
|
1092
|
+
placeholder = input(false, { ...(ngDevMode ? { debugName: "placeholder" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1093
|
+
place = computed(() => this.workspace.placements().get(this.paneId()), ...(ngDevMode ? [{ debugName: "place" }] : /* istanbul ignore next */ []));
|
|
1094
|
+
dragging = computed(() => this.workspace.draggingPane() === this.paneId(), ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
|
|
1095
|
+
columnStyle = computed(() => {
|
|
1096
|
+
const place = this.place();
|
|
1097
|
+
if (!place) {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
return place.spanColumns ? '1 / -1' : place.column;
|
|
1101
|
+
}, ...(ngDevMode ? [{ debugName: "columnStyle" }] : /* istanbul ignore next */ []));
|
|
1102
|
+
rowStyle = computed(() => {
|
|
1103
|
+
const place = this.place();
|
|
1104
|
+
return place ? `${place.rowStart} / ${place.rowEnd}` : null;
|
|
1105
|
+
}, ...(ngDevMode ? [{ debugName: "rowStyle" }] : /* istanbul ignore next */ []));
|
|
1106
|
+
showResize = computed(() => this.place()?.showResize === true, ...(ngDevMode ? [{ debugName: "showResize" }] : /* istanbul ignore next */ []));
|
|
1107
|
+
concealed = computed(() => this.place()?.concealed === true, ...(ngDevMode ? [{ debugName: "concealed" }] : /* istanbul ignore next */ []));
|
|
1108
|
+
settingsOpen = computed(() => this.workspace.settingsPane() === this.paneId(), ...(ngDevMode ? [{ debugName: "settingsOpen" }] : /* istanbul ignore next */ []));
|
|
1109
|
+
fullWidth = computed(() => this.workspace.fullWidthIds().has(this.paneId()), ...(ngDevMode ? [{ debugName: "fullWidth" }] : /* istanbul ignore next */ []));
|
|
1110
|
+
shown = computed(() => this.place()?.concealed !== true, ...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
|
|
1111
|
+
flexible = computed(() => this.workspace.flexibleIds().has(this.paneId()), ...(ngDevMode ? [{ debugName: "flexible" }] : /* istanbul ignore next */ []));
|
|
1112
|
+
label = computed(() => workspacePaneLabel(this.paneId()), ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
1113
|
+
menuAbove = computed(() => {
|
|
1114
|
+
const place = this.place();
|
|
1115
|
+
if (!place) {
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
let maxEnd = place.rowEnd;
|
|
1119
|
+
for (const item of this.workspace.placements().values()) {
|
|
1120
|
+
maxEnd = Math.max(maxEnd, item.rowEnd);
|
|
1121
|
+
}
|
|
1122
|
+
return place.rowEnd === maxEnd && this.workspace.placements().size > 1;
|
|
1123
|
+
}, ...(ngDevMode ? [{ debugName: "menuAbove" }] : /* istanbul ignore next */ []));
|
|
1124
|
+
handleLabel = computed(() => `Move ${this.label()}. Arrow keys change its position.`, ...(ngDevMode ? [{ debugName: "handleLabel" }] : /* istanbul ignore next */ []));
|
|
1125
|
+
resizeLabel = computed(() => `Resize ${this.label()}. Arrow keys change its height.`, ...(ngDevMode ? [{ debugName: "resizeLabel" }] : /* istanbul ignore next */ []));
|
|
1126
|
+
settingsLabel = computed(() => `${this.label()} settings`, ...(ngDevMode ? [{ debugName: "settingsLabel" }] : /* istanbul ignore next */ []));
|
|
1127
|
+
closeMenuLabel = computed(() => `Close ${this.label()} settings`, ...(ngDevMode ? [{ debugName: "closeMenuLabel" }] : /* istanbul ignore next */ []));
|
|
1128
|
+
removeLabel = computed(() => `Remove ${this.label()}`, ...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
|
|
1129
|
+
onDragPointerDown(event) {
|
|
1130
|
+
if (!this.workspace.editing()) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
event.preventDefault();
|
|
1134
|
+
event.stopPropagation();
|
|
1135
|
+
this.workspace.closePaneSettings(this.paneId());
|
|
1136
|
+
this.capture(event);
|
|
1137
|
+
this.workspace.beginPaneDrag(this.paneId(), event);
|
|
1138
|
+
}
|
|
1139
|
+
onDragPointerMove(event) {
|
|
1140
|
+
if (this.workspace.draggingPane() !== this.paneId()) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
this.workspace.updatePaneDrag(event);
|
|
1144
|
+
}
|
|
1145
|
+
onDragPointerUp(event) {
|
|
1146
|
+
if (this.workspace.draggingPane() !== this.paneId()) {
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
this.workspace.endPaneDrag(event);
|
|
1150
|
+
}
|
|
1151
|
+
onHandleKeydown(event) {
|
|
1152
|
+
if (!this.isMoveKey(event.key)) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
event.preventDefault();
|
|
1156
|
+
event.stopPropagation();
|
|
1157
|
+
this.workspace.moveByKey(this.paneId(), event.key);
|
|
1158
|
+
}
|
|
1159
|
+
toggleSettings(event) {
|
|
1160
|
+
event.stopPropagation();
|
|
1161
|
+
this.workspace.togglePaneSettings(this.paneId());
|
|
1162
|
+
}
|
|
1163
|
+
closeSettings(event) {
|
|
1164
|
+
event.stopPropagation();
|
|
1165
|
+
this.workspace.closePaneSettings(this.paneId());
|
|
1166
|
+
}
|
|
1167
|
+
onSettingsKeydown(event) {
|
|
1168
|
+
if (event.key !== 'Escape') {
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
event.stopPropagation();
|
|
1172
|
+
this.workspace.closePaneSettings(this.paneId());
|
|
1173
|
+
}
|
|
1174
|
+
onVisible(event) {
|
|
1175
|
+
const target = event.target;
|
|
1176
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
this.workspace.setVisible(this.paneId(), target.checked);
|
|
1180
|
+
}
|
|
1181
|
+
onFlexible(event) {
|
|
1182
|
+
const target = event.target;
|
|
1183
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
this.workspace.setFlexible(this.paneId(), target.checked);
|
|
1187
|
+
}
|
|
1188
|
+
onFullWidth(event) {
|
|
1189
|
+
const target = event.target;
|
|
1190
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
this.workspace.setFullWidth(this.paneId(), target.checked);
|
|
1194
|
+
}
|
|
1195
|
+
onRemove(event) {
|
|
1196
|
+
event.stopPropagation();
|
|
1197
|
+
this.workspace.closePaneSettings(this.paneId());
|
|
1198
|
+
this.workspace.removeFromLayout(this.paneId());
|
|
1199
|
+
}
|
|
1200
|
+
onResizePointerDown(event) {
|
|
1201
|
+
event.preventDefault();
|
|
1202
|
+
event.stopPropagation();
|
|
1203
|
+
this.capture(event);
|
|
1204
|
+
this.workspace.beginPaneResize(this.paneId(), event);
|
|
1205
|
+
}
|
|
1206
|
+
onResizePointerMove(event) {
|
|
1207
|
+
this.workspace.updatePaneResize(event);
|
|
1208
|
+
}
|
|
1209
|
+
onResizePointerUp() {
|
|
1210
|
+
this.workspace.endPaneResize();
|
|
1211
|
+
}
|
|
1212
|
+
onResizeKeydown(event) {
|
|
1213
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
event.preventDefault();
|
|
1217
|
+
event.stopPropagation();
|
|
1218
|
+
this.workspace.resizeByKey(this.paneId(), event.key === 'ArrowDown' ? 1 : -1);
|
|
1219
|
+
}
|
|
1220
|
+
capture(event) {
|
|
1221
|
+
const handle = event.currentTarget;
|
|
1222
|
+
if (!(handle instanceof HTMLElement)) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
try {
|
|
1226
|
+
handle.setPointerCapture(event.pointerId);
|
|
1227
|
+
}
|
|
1228
|
+
catch {
|
|
1229
|
+
// The pointer is not active yet. The drag still follows pointerup on the handle.
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
isMoveKey(key) {
|
|
1233
|
+
return key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight';
|
|
1234
|
+
}
|
|
1235
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosWorkspacePane, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1236
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: PosWorkspacePane, isStandalone: true, selector: "pos-workspace-pane", inputs: { paneId: { classPropertyName: "paneId", publicName: "paneId", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.pos-workspace-pane--editing": "workspace.editing()", "class.pos-workspace-pane--dragging": "dragging()", "class.pos-workspace-pane--hidden": "concealed()", "attr.data-pane": "paneId()", "style.display": "place() ? null : \"none\"", "style.grid-column": "columnStyle()", "style.grid-row": "rowStyle()" }, classAttribute: "pos-workspace-pane" }, ngImport: i0, template: "@if (concealed()) {\r\n <p class=\"pos-workspace-pane__concealed\">Hidden on the till. {{ label() }}.</p>\r\n}\r\n\r\n<div class=\"pos-workspace-pane__content\">\r\n <ng-content />\r\n @if (placeholder()) {\r\n <p class=\"pos-workspace-pane__slot\">{{ label() }}. The POS assigns what appears here.</p>\r\n }\r\n</div>\r\n\r\n@if (workspace.editing()) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__handle\"\r\n [attr.aria-label]=\"handleLabel()\"\r\n aria-keyshortcuts=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\r\n (pointerdown)=\"onDragPointerDown($event)\"\r\n (pointermove)=\"onDragPointerMove($event)\"\r\n (pointerup)=\"onDragPointerUp($event)\"\r\n (pointercancel)=\"onDragPointerUp($event)\"\r\n (keydown)=\"onHandleKeydown($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <circle cx=\"5\" cy=\"3\" r=\"1.2\" />\r\n <circle cx=\"11\" cy=\"3\" r=\"1.2\" />\r\n <circle cx=\"5\" cy=\"8\" r=\"1.2\" />\r\n <circle cx=\"11\" cy=\"8\" r=\"1.2\" />\r\n <circle cx=\"5\" cy=\"13\" r=\"1.2\" />\r\n <circle cx=\"11\" cy=\"13\" r=\"1.2\" />\r\n </svg>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__settings\"\r\n [attr.aria-label]=\"settingsLabel()\"\r\n [attr.aria-expanded]=\"settingsOpen()\"\r\n (click)=\"toggleSettings($event)\"\r\n (keydown)=\"onSettingsKeydown($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <path d=\"M2 4h12M2 8h12M2 12h12\" />\r\n <circle cx=\"6\" cy=\"4\" r=\"1.5\" />\r\n <circle cx=\"11\" cy=\"8\" r=\"1.5\" />\r\n <circle cx=\"7\" cy=\"12\" r=\"1.5\" />\r\n </svg>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__close\"\r\n [attr.aria-label]=\"removeLabel()\"\r\n (click)=\"onRemove($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <path d=\"M4 4l8 8M12 4l-8 8\" />\r\n </svg>\r\n </button>\r\n\r\n @if (settingsOpen()) {\r\n <div\r\n class=\"pos-workspace-pane__menu\"\r\n [class.pos-workspace-pane__menu--above]=\"menuAbove()\"\r\n role=\"group\"\r\n [attr.aria-label]=\"settingsLabel()\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__menu-close\"\r\n [attr.aria-label]=\"closeMenuLabel()\"\r\n (click)=\"closeSettings($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <path d=\"M4 4l8 8M12 4l-8 8\" />\r\n </svg>\r\n </button>\r\n <label class=\"pos-workspace-pane__option\">\r\n <input type=\"checkbox\" [checked]=\"shown()\" (change)=\"onVisible($event)\" />\r\n Visible\r\n </label>\r\n <label class=\"pos-workspace-pane__option\">\r\n <input type=\"checkbox\" [checked]=\"flexible()\" (change)=\"onFlexible($event)\" />\r\n Flexible height\r\n </label>\r\n <label class=\"pos-workspace-pane__option\">\r\n <input type=\"checkbox\" [checked]=\"fullWidth()\" (change)=\"onFullWidth($event)\" />\r\n Full width\r\n </label>\r\n </div>\r\n }\r\n\r\n @if (showResize()) {\r\n <div\r\n class=\"pos-workspace-pane__resize\"\r\n role=\"separator\"\r\n aria-orientation=\"horizontal\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"resizeLabel()\"\r\n aria-keyshortcuts=\"ArrowUp ArrowDown\"\r\n (pointerdown)=\"onResizePointerDown($event)\"\r\n (pointermove)=\"onResizePointerMove($event)\"\r\n (pointerup)=\"onResizePointerUp()\"\r\n (pointercancel)=\"onResizePointerUp()\"\r\n (keydown)=\"onResizeKeydown($event)\"\r\n ></div>\r\n }\r\n}\r\n", styles: [":host{display:flex;position:relative;flex-direction:column;min-width:0;min-height:0;box-sizing:border-box}:host(.pos-workspace-pane--editing){outline:1px dashed var(--pos-color-primary);outline-offset:2px;border-radius:var(--pos-radius-sm)}:host(.pos-workspace-pane--editing) .pos-workspace-pane__content{padding-left:calc(4 * var(--pos-space-xs) + 3 * var(--pos-space-xl) + var(--pos-space-sm))}:host(.pos-workspace-pane--dragging){opacity:.55}:host(.pos-workspace-pane--hidden) .pos-workspace-pane__content{visibility:hidden}.pos-workspace-pane__concealed{position:absolute;z-index:1;inset:0;display:flex;align-items:center;justify-content:center;margin:0;padding:var(--pos-space-lg);color:var(--pos-color-text-muted);font-size:var(--pos-font-size-md);text-align:center;pointer-events:none}.pos-workspace-pane__content{display:flex;flex:1 1 auto;flex-direction:column;min-width:0;min-height:0;overflow:auto}.pos-workspace-pane__content>*{flex:1 1 auto;min-width:0;min-height:0}.pos-workspace-pane__slot{display:flex;align-items:center;justify-content:center;margin:0;min-height:calc(var(--pos-button-min-height) * 2);padding:var(--pos-space-lg);border:1px dashed var(--pos-color-border);border-radius:var(--pos-radius-md);color:var(--pos-color-text-muted);font-size:var(--pos-font-size-md);text-align:center}.pos-workspace-pane__handle{position:absolute;z-index:2;top:0;left:0;display:inline-flex;align-items:flex-start;justify-content:flex-start;width:var(--pos-button-min-height);height:var(--pos-button-min-height);padding:var(--pos-space-xs);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:grab;touch-action:none}.pos-workspace-pane__handle:before{content:\"\";width:var(--pos-space-xl);height:var(--pos-space-xl);border:1px solid var(--pos-color-primary);border-radius:var(--pos-radius-sm);background:var(--pos-color-surface)}.pos-workspace-pane__handle svg{position:absolute;z-index:1;top:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);left:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);width:var(--pos-space-lg);height:var(--pos-space-lg);fill:currentColor}.pos-workspace-pane__handle:active{cursor:grabbing}.pos-workspace-pane__handle:focus-visible,.pos-workspace-pane__close:focus-visible,.pos-workspace-pane__settings:focus-visible,.pos-workspace-pane__menu-close:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}.pos-workspace-pane__close{position:absolute;z-index:3;top:0;left:calc(3 * var(--pos-space-xs) + 2 * var(--pos-space-xl));display:inline-flex;align-items:flex-start;justify-content:flex-start;width:var(--pos-button-min-height);height:var(--pos-button-min-height);padding:var(--pos-space-xs);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:pointer}.pos-workspace-pane__close:before{content:\"\";width:var(--pos-space-xl);height:var(--pos-space-xl);border:1px solid var(--pos-color-primary);border-radius:var(--pos-radius-sm);background:var(--pos-color-surface)}.pos-workspace-pane__close svg{position:absolute;z-index:1;top:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);left:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);width:var(--pos-space-lg);height:var(--pos-space-lg);fill:none;stroke:currentColor;stroke-width:1.4}.pos-workspace-pane__settings{position:absolute;z-index:3;top:0;left:calc(var(--pos-space-xs) + var(--pos-space-xl) + var(--pos-space-xs));display:inline-flex;align-items:flex-start;justify-content:flex-start;width:var(--pos-button-min-height);height:var(--pos-button-min-height);padding:var(--pos-space-xs);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:pointer}.pos-workspace-pane__settings:before{content:\"\";width:var(--pos-space-xl);height:var(--pos-space-xl);border:1px solid var(--pos-color-primary);border-radius:var(--pos-radius-sm);background:var(--pos-color-surface)}.pos-workspace-pane__settings svg{position:absolute;z-index:1;top:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);left:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);width:var(--pos-space-lg);height:var(--pos-space-lg);fill:none;stroke:currentColor;stroke-width:1.4}.pos-workspace-pane__settings circle{fill:var(--pos-color-surface)}.pos-workspace-pane__menu{position:absolute;z-index:4;top:var(--pos-button-min-height);left:calc(var(--pos-space-xs) + var(--pos-space-xl) + var(--pos-space-xs));display:flex;flex-direction:column;padding:var(--pos-space-sm) var(--pos-space-md);border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-md);background:var(--pos-color-surface)}.pos-workspace-pane__menu-close{align-self:flex-end;display:inline-flex;align-items:center;justify-content:center;width:var(--pos-button-min-height);height:var(--pos-button-min-height);margin:calc(var(--pos-space-sm) * -1) calc(var(--pos-space-md) * -1) 0 auto;padding:0;border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:pointer}.pos-workspace-pane__menu-close svg{width:var(--pos-space-lg);height:var(--pos-space-lg);fill:none;stroke:currentColor;stroke-width:1.4}.pos-workspace-pane__menu--above{top:auto;bottom:var(--pos-button-min-height)}.pos-workspace-pane__option{display:inline-flex;align-items:center;gap:var(--pos-space-sm);min-height:var(--pos-button-min-height);font-size:var(--pos-font-size-md);white-space:nowrap}.pos-workspace-pane__option input{width:var(--pos-space-xl);height:var(--pos-space-xl)}.pos-workspace-pane__resize{position:absolute;z-index:2;right:var(--pos-space-xl);bottom:calc(var(--pos-space-xl) / -2);left:var(--pos-space-xl);height:var(--pos-space-xl);padding:0;border:0;background:transparent;cursor:ns-resize;touch-action:none}.pos-workspace-pane__resize:before{content:\"\";position:absolute;right:0;left:0;top:50%;height:var(--pos-space-xs);border-radius:var(--pos-radius-pill);background:var(--pos-color-primary);transform:translateY(-50%)}.pos-workspace-pane__resize:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}@media(max-width:40rem){.pos-workspace-pane__resize{right:var(--pos-space-lg);left:var(--pos-space-lg)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1237
|
+
}
|
|
1238
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosWorkspacePane, decorators: [{
|
|
1239
|
+
type: Component,
|
|
1240
|
+
args: [{ selector: 'pos-workspace-pane', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1241
|
+
class: 'pos-workspace-pane',
|
|
1242
|
+
'[class.pos-workspace-pane--editing]': 'workspace.editing()',
|
|
1243
|
+
'[class.pos-workspace-pane--dragging]': 'dragging()',
|
|
1244
|
+
'[class.pos-workspace-pane--hidden]': 'concealed()',
|
|
1245
|
+
'[attr.data-pane]': 'paneId()',
|
|
1246
|
+
'[style.display]': 'place() ? null : "none"',
|
|
1247
|
+
'[style.grid-column]': 'columnStyle()',
|
|
1248
|
+
'[style.grid-row]': 'rowStyle()',
|
|
1249
|
+
}, template: "@if (concealed()) {\r\n <p class=\"pos-workspace-pane__concealed\">Hidden on the till. {{ label() }}.</p>\r\n}\r\n\r\n<div class=\"pos-workspace-pane__content\">\r\n <ng-content />\r\n @if (placeholder()) {\r\n <p class=\"pos-workspace-pane__slot\">{{ label() }}. The POS assigns what appears here.</p>\r\n }\r\n</div>\r\n\r\n@if (workspace.editing()) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__handle\"\r\n [attr.aria-label]=\"handleLabel()\"\r\n aria-keyshortcuts=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\r\n (pointerdown)=\"onDragPointerDown($event)\"\r\n (pointermove)=\"onDragPointerMove($event)\"\r\n (pointerup)=\"onDragPointerUp($event)\"\r\n (pointercancel)=\"onDragPointerUp($event)\"\r\n (keydown)=\"onHandleKeydown($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <circle cx=\"5\" cy=\"3\" r=\"1.2\" />\r\n <circle cx=\"11\" cy=\"3\" r=\"1.2\" />\r\n <circle cx=\"5\" cy=\"8\" r=\"1.2\" />\r\n <circle cx=\"11\" cy=\"8\" r=\"1.2\" />\r\n <circle cx=\"5\" cy=\"13\" r=\"1.2\" />\r\n <circle cx=\"11\" cy=\"13\" r=\"1.2\" />\r\n </svg>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__settings\"\r\n [attr.aria-label]=\"settingsLabel()\"\r\n [attr.aria-expanded]=\"settingsOpen()\"\r\n (click)=\"toggleSettings($event)\"\r\n (keydown)=\"onSettingsKeydown($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <path d=\"M2 4h12M2 8h12M2 12h12\" />\r\n <circle cx=\"6\" cy=\"4\" r=\"1.5\" />\r\n <circle cx=\"11\" cy=\"8\" r=\"1.5\" />\r\n <circle cx=\"7\" cy=\"12\" r=\"1.5\" />\r\n </svg>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__close\"\r\n [attr.aria-label]=\"removeLabel()\"\r\n (click)=\"onRemove($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <path d=\"M4 4l8 8M12 4l-8 8\" />\r\n </svg>\r\n </button>\r\n\r\n @if (settingsOpen()) {\r\n <div\r\n class=\"pos-workspace-pane__menu\"\r\n [class.pos-workspace-pane__menu--above]=\"menuAbove()\"\r\n role=\"group\"\r\n [attr.aria-label]=\"settingsLabel()\"\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace-pane__menu-close\"\r\n [attr.aria-label]=\"closeMenuLabel()\"\r\n (click)=\"closeSettings($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\r\n <path d=\"M4 4l8 8M12 4l-8 8\" />\r\n </svg>\r\n </button>\r\n <label class=\"pos-workspace-pane__option\">\r\n <input type=\"checkbox\" [checked]=\"shown()\" (change)=\"onVisible($event)\" />\r\n Visible\r\n </label>\r\n <label class=\"pos-workspace-pane__option\">\r\n <input type=\"checkbox\" [checked]=\"flexible()\" (change)=\"onFlexible($event)\" />\r\n Flexible height\r\n </label>\r\n <label class=\"pos-workspace-pane__option\">\r\n <input type=\"checkbox\" [checked]=\"fullWidth()\" (change)=\"onFullWidth($event)\" />\r\n Full width\r\n </label>\r\n </div>\r\n }\r\n\r\n @if (showResize()) {\r\n <div\r\n class=\"pos-workspace-pane__resize\"\r\n role=\"separator\"\r\n aria-orientation=\"horizontal\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"resizeLabel()\"\r\n aria-keyshortcuts=\"ArrowUp ArrowDown\"\r\n (pointerdown)=\"onResizePointerDown($event)\"\r\n (pointermove)=\"onResizePointerMove($event)\"\r\n (pointerup)=\"onResizePointerUp()\"\r\n (pointercancel)=\"onResizePointerUp()\"\r\n (keydown)=\"onResizeKeydown($event)\"\r\n ></div>\r\n }\r\n}\r\n", styles: [":host{display:flex;position:relative;flex-direction:column;min-width:0;min-height:0;box-sizing:border-box}:host(.pos-workspace-pane--editing){outline:1px dashed var(--pos-color-primary);outline-offset:2px;border-radius:var(--pos-radius-sm)}:host(.pos-workspace-pane--editing) .pos-workspace-pane__content{padding-left:calc(4 * var(--pos-space-xs) + 3 * var(--pos-space-xl) + var(--pos-space-sm))}:host(.pos-workspace-pane--dragging){opacity:.55}:host(.pos-workspace-pane--hidden) .pos-workspace-pane__content{visibility:hidden}.pos-workspace-pane__concealed{position:absolute;z-index:1;inset:0;display:flex;align-items:center;justify-content:center;margin:0;padding:var(--pos-space-lg);color:var(--pos-color-text-muted);font-size:var(--pos-font-size-md);text-align:center;pointer-events:none}.pos-workspace-pane__content{display:flex;flex:1 1 auto;flex-direction:column;min-width:0;min-height:0;overflow:auto}.pos-workspace-pane__content>*{flex:1 1 auto;min-width:0;min-height:0}.pos-workspace-pane__slot{display:flex;align-items:center;justify-content:center;margin:0;min-height:calc(var(--pos-button-min-height) * 2);padding:var(--pos-space-lg);border:1px dashed var(--pos-color-border);border-radius:var(--pos-radius-md);color:var(--pos-color-text-muted);font-size:var(--pos-font-size-md);text-align:center}.pos-workspace-pane__handle{position:absolute;z-index:2;top:0;left:0;display:inline-flex;align-items:flex-start;justify-content:flex-start;width:var(--pos-button-min-height);height:var(--pos-button-min-height);padding:var(--pos-space-xs);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:grab;touch-action:none}.pos-workspace-pane__handle:before{content:\"\";width:var(--pos-space-xl);height:var(--pos-space-xl);border:1px solid var(--pos-color-primary);border-radius:var(--pos-radius-sm);background:var(--pos-color-surface)}.pos-workspace-pane__handle svg{position:absolute;z-index:1;top:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);left:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);width:var(--pos-space-lg);height:var(--pos-space-lg);fill:currentColor}.pos-workspace-pane__handle:active{cursor:grabbing}.pos-workspace-pane__handle:focus-visible,.pos-workspace-pane__close:focus-visible,.pos-workspace-pane__settings:focus-visible,.pos-workspace-pane__menu-close:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}.pos-workspace-pane__close{position:absolute;z-index:3;top:0;left:calc(3 * var(--pos-space-xs) + 2 * var(--pos-space-xl));display:inline-flex;align-items:flex-start;justify-content:flex-start;width:var(--pos-button-min-height);height:var(--pos-button-min-height);padding:var(--pos-space-xs);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:pointer}.pos-workspace-pane__close:before{content:\"\";width:var(--pos-space-xl);height:var(--pos-space-xl);border:1px solid var(--pos-color-primary);border-radius:var(--pos-radius-sm);background:var(--pos-color-surface)}.pos-workspace-pane__close svg{position:absolute;z-index:1;top:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);left:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);width:var(--pos-space-lg);height:var(--pos-space-lg);fill:none;stroke:currentColor;stroke-width:1.4}.pos-workspace-pane__settings{position:absolute;z-index:3;top:0;left:calc(var(--pos-space-xs) + var(--pos-space-xl) + var(--pos-space-xs));display:inline-flex;align-items:flex-start;justify-content:flex-start;width:var(--pos-button-min-height);height:var(--pos-button-min-height);padding:var(--pos-space-xs);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:pointer}.pos-workspace-pane__settings:before{content:\"\";width:var(--pos-space-xl);height:var(--pos-space-xl);border:1px solid var(--pos-color-primary);border-radius:var(--pos-radius-sm);background:var(--pos-color-surface)}.pos-workspace-pane__settings svg{position:absolute;z-index:1;top:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);left:calc(var(--pos-space-xs) + (var(--pos-space-xl) - var(--pos-space-lg)) / 2);width:var(--pos-space-lg);height:var(--pos-space-lg);fill:none;stroke:currentColor;stroke-width:1.4}.pos-workspace-pane__settings circle{fill:var(--pos-color-surface)}.pos-workspace-pane__menu{position:absolute;z-index:4;top:var(--pos-button-min-height);left:calc(var(--pos-space-xs) + var(--pos-space-xl) + var(--pos-space-xs));display:flex;flex-direction:column;padding:var(--pos-space-sm) var(--pos-space-md);border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-md);background:var(--pos-color-surface)}.pos-workspace-pane__menu-close{align-self:flex-end;display:inline-flex;align-items:center;justify-content:center;width:var(--pos-button-min-height);height:var(--pos-button-min-height);margin:calc(var(--pos-space-sm) * -1) calc(var(--pos-space-md) * -1) 0 auto;padding:0;border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);cursor:pointer}.pos-workspace-pane__menu-close svg{width:var(--pos-space-lg);height:var(--pos-space-lg);fill:none;stroke:currentColor;stroke-width:1.4}.pos-workspace-pane__menu--above{top:auto;bottom:var(--pos-button-min-height)}.pos-workspace-pane__option{display:inline-flex;align-items:center;gap:var(--pos-space-sm);min-height:var(--pos-button-min-height);font-size:var(--pos-font-size-md);white-space:nowrap}.pos-workspace-pane__option input{width:var(--pos-space-xl);height:var(--pos-space-xl)}.pos-workspace-pane__resize{position:absolute;z-index:2;right:var(--pos-space-xl);bottom:calc(var(--pos-space-xl) / -2);left:var(--pos-space-xl);height:var(--pos-space-xl);padding:0;border:0;background:transparent;cursor:ns-resize;touch-action:none}.pos-workspace-pane__resize:before{content:\"\";position:absolute;right:0;left:0;top:50%;height:var(--pos-space-xs);border-radius:var(--pos-radius-pill);background:var(--pos-color-primary);transform:translateY(-50%)}.pos-workspace-pane__resize:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}@media(max-width:40rem){.pos-workspace-pane__resize{right:var(--pos-space-lg);left:var(--pos-space-lg)}}\n"] }]
|
|
1250
|
+
}], propDecorators: { paneId: [{ type: i0.Input, args: [{ isSignal: true, alias: "paneId", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
|
|
1251
|
+
|
|
1252
|
+
const COMPACT_WIDTH = 720;
|
|
1253
|
+
const DOCK_THRESHOLD = 0.7;
|
|
1254
|
+
/** Band above a docked right column where a drop spans both columns. */
|
|
1255
|
+
const FULL_WIDTH_BAND = 72;
|
|
1256
|
+
class PosWorkspace {
|
|
1257
|
+
hostRef = inject(ElementRef);
|
|
1258
|
+
destroyRef = inject(DestroyRef);
|
|
1259
|
+
paneRefs = contentChildren(PosWorkspacePane, ...(ngDevMode ? [{ debugName: "paneRefs" }] : /* istanbul ignore next */ []));
|
|
1260
|
+
frameRef = viewChild('frame', ...(ngDevMode ? [{ debugName: "frameRef" }] : /* istanbul ignore next */ []));
|
|
1261
|
+
/** Draws outlines, drag handles, and resize controls. */
|
|
1262
|
+
editing = input(false, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
1263
|
+
/** Starting arrangement. Edits stay in this view until a new layout input arrives. */
|
|
1264
|
+
layout = input(POS_WORKSPACE_DEFAULT_LAYOUT, ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
1265
|
+
/** Fired when the arrangement changes. Nothing is stored beyond this view. */
|
|
1266
|
+
layoutChange = output();
|
|
1267
|
+
/** The arrangement to pass back as `layout` from the consuming POS. */
|
|
1268
|
+
layoutExport = output();
|
|
1269
|
+
layoutState = linkedSignal(() => this.layout(), ...(ngDevMode ? [{ debugName: "layoutState" }] : /* istanbul ignore next */ []));
|
|
1270
|
+
viewportWidth = signal(1280, ...(ngDevMode ? [{ debugName: "viewportWidth" }] : /* istanbul ignore next */ []));
|
|
1271
|
+
dropTarget = signal(null, ...(ngDevMode ? [{ debugName: "dropTarget" }] : /* istanbul ignore next */ []));
|
|
1272
|
+
resizeSession = null;
|
|
1273
|
+
paletteGesture = null;
|
|
1274
|
+
ignorePaletteClick = false;
|
|
1275
|
+
paletteOpen = signal(false, ...(ngDevMode ? [{ debugName: "paletteOpen" }] : /* istanbul ignore next */ []));
|
|
1276
|
+
paletteMenuLifted = signal(false, ...(ngDevMode ? [{ debugName: "paletteMenuLifted" }] : /* istanbul ignore next */ []));
|
|
1277
|
+
splitting = false;
|
|
1278
|
+
splitStartLayout = null;
|
|
1279
|
+
history = [];
|
|
1280
|
+
canUndo = signal(false, ...(ngDevMode ? [{ debugName: "canUndo" }] : /* istanbul ignore next */ []));
|
|
1281
|
+
draggingPane = signal(null, ...(ngDevMode ? [{ debugName: "draggingPane" }] : /* istanbul ignore next */ []));
|
|
1282
|
+
settingsPane = signal(null, ...(ngDevMode ? [{ debugName: "settingsPane" }] : /* istanbul ignore next */ []));
|
|
1283
|
+
spans = computed(() => {
|
|
1284
|
+
const map = new Map();
|
|
1285
|
+
for (const pane of this.layoutState().panes) {
|
|
1286
|
+
map.set(pane.id, paneSpan(pane));
|
|
1287
|
+
}
|
|
1288
|
+
return map;
|
|
1289
|
+
}, ...(ngDevMode ? [{ debugName: "spans" }] : /* istanbul ignore next */ []));
|
|
1290
|
+
fullWidthIds = computed(() => {
|
|
1291
|
+
const ids = new Set();
|
|
1292
|
+
for (const pane of this.layoutState().panes) {
|
|
1293
|
+
if (paneFillsWidth(pane)) {
|
|
1294
|
+
ids.add(pane.id);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return ids;
|
|
1298
|
+
}, ...(ngDevMode ? [{ debugName: "fullWidthIds" }] : /* istanbul ignore next */ []));
|
|
1299
|
+
flexibleIds = computed(() => {
|
|
1300
|
+
const ids = new Set();
|
|
1301
|
+
for (const pane of this.layoutState().panes) {
|
|
1302
|
+
if (pane.weight > 0) {
|
|
1303
|
+
ids.add(pane.id);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return ids;
|
|
1307
|
+
}, ...(ngDevMode ? [{ debugName: "flexibleIds" }] : /* istanbul ignore next */ []));
|
|
1308
|
+
dropIndicator = signal(null, ...(ngDevMode ? [{ debugName: "dropIndicator" }] : /* istanbul ignore next */ []));
|
|
1309
|
+
dropHints = signal([], ...(ngDevMode ? [{ debugName: "dropHints" }] : /* istanbul ignore next */ []));
|
|
1310
|
+
status = signal('', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
1311
|
+
compact = computed(() => this.viewportWidth() < COMPACT_WIDTH, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
|
|
1312
|
+
grid = computed(() => workspaceGrid(this.layoutState(), this.paneRefs().map((pane) => pane.paneId()), { compact: this.compact(), includeHidden: this.editing() }), ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
|
|
1313
|
+
/** Library components the host projected that are not on the screen yet. */
|
|
1314
|
+
palette = computed(() => {
|
|
1315
|
+
const placed = new Set(this.layoutState().panes.map((pane) => pane.id));
|
|
1316
|
+
const projected = new Set(this.paneRefs().map((pane) => pane.paneId()));
|
|
1317
|
+
return POS_WORKSPACE_PANE_IDS.filter((id) => projected.has(id) && !placed.has(id));
|
|
1318
|
+
}, ...(ngDevMode ? [{ debugName: "palette" }] : /* istanbul ignore next */ []));
|
|
1319
|
+
/** Empty slots with no projected content. The POS fills a slot by projecting the same id. */
|
|
1320
|
+
emptySlots = computed(() => {
|
|
1321
|
+
const projected = new Set(this.paneRefs().map((pane) => pane.paneId()));
|
|
1322
|
+
const seen = new Set();
|
|
1323
|
+
const slots = [];
|
|
1324
|
+
for (const pane of this.layoutState().panes) {
|
|
1325
|
+
if (!isEmptyPaneId(pane.id)) {
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
if (projected.has(pane.id) || seen.has(pane.id)) {
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
seen.add(pane.id);
|
|
1332
|
+
slots.push(pane.id);
|
|
1333
|
+
}
|
|
1334
|
+
return slots;
|
|
1335
|
+
}, ...(ngDevMode ? [{ debugName: "emptySlots" }] : /* istanbul ignore next */ []));
|
|
1336
|
+
paneLabel = workspacePaneLabel;
|
|
1337
|
+
placements = computed(() => {
|
|
1338
|
+
const map = new Map();
|
|
1339
|
+
for (const pane of this.grid().panes) {
|
|
1340
|
+
map.set(pane.id, pane);
|
|
1341
|
+
}
|
|
1342
|
+
return map;
|
|
1343
|
+
}, ...(ngDevMode ? [{ debugName: "placements" }] : /* istanbul ignore next */ []));
|
|
1344
|
+
showSplitter = computed(() => this.editing() && this.grid().split && !this.compact(), ...(ngDevMode ? [{ debugName: "showSplitter" }] : /* istanbul ignore next */ []));
|
|
1345
|
+
/** The rows the right column occupies, so the bar stays off full-width panes. */
|
|
1346
|
+
splitterRow = computed(() => {
|
|
1347
|
+
const splitPanes = this.grid().panes.filter((pane) => pane.column === 2);
|
|
1348
|
+
if (splitPanes.length === 0) {
|
|
1349
|
+
return '1 / -1';
|
|
1350
|
+
}
|
|
1351
|
+
const start = Math.min(...splitPanes.map((pane) => pane.rowStart));
|
|
1352
|
+
const end = Math.max(...splitPanes.map((pane) => pane.rowEnd));
|
|
1353
|
+
return `${start} / ${end}`;
|
|
1354
|
+
}, ...(ngDevMode ? [{ debugName: "splitterRow" }] : /* istanbul ignore next */ []));
|
|
1355
|
+
splitterLeft = computed(() => {
|
|
1356
|
+
const ratio = this.layoutState().startColumnRatio;
|
|
1357
|
+
return `calc((100% - var(--pos-space-md)) * ${ratio})`;
|
|
1358
|
+
}, ...(ngDevMode ? [{ debugName: "splitterLeft" }] : /* istanbul ignore next */ []));
|
|
1359
|
+
ratioPercent = computed(() => Math.round(this.layoutState().startColumnRatio * 100), ...(ngDevMode ? [{ debugName: "ratioPercent" }] : /* istanbul ignore next */ []));
|
|
1360
|
+
constructor() {
|
|
1361
|
+
effect(() => {
|
|
1362
|
+
if (!this.editing()) {
|
|
1363
|
+
this.clearInteraction();
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
effect(() => {
|
|
1367
|
+
this.layout();
|
|
1368
|
+
untracked(() => {
|
|
1369
|
+
this.history.length = 0;
|
|
1370
|
+
this.canUndo.set(false);
|
|
1371
|
+
});
|
|
1372
|
+
});
|
|
1373
|
+
afterNextRender(() => {
|
|
1374
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const element = this.hostRef.nativeElement;
|
|
1378
|
+
const observer = new ResizeObserver((entries) => {
|
|
1379
|
+
const width = entries[0]?.contentRect.width ?? 0;
|
|
1380
|
+
if (width >= 1) {
|
|
1381
|
+
this.viewportWidth.set(width);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
observer.observe(element);
|
|
1385
|
+
this.destroyRef.onDestroy(() => observer.disconnect());
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
togglePaneSettings(id) {
|
|
1389
|
+
if (!this.editing()) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
this.settingsPane.update((current) => (current === id ? null : id));
|
|
1393
|
+
}
|
|
1394
|
+
closePaneSettings(id) {
|
|
1395
|
+
if (this.settingsPane() === id) {
|
|
1396
|
+
this.settingsPane.set(null);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
moveByKey(id, key) {
|
|
1400
|
+
if (!this.editing()) {
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
const layout = this.layoutState();
|
|
1404
|
+
const pane = layout.panes.find((item) => item.id === id);
|
|
1405
|
+
if (!pane) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (key === 'ArrowLeft' || key === 'ArrowRight') {
|
|
1409
|
+
if (this.compact()) {
|
|
1410
|
+
this.status.set('Widen the screen to dock a pane beside the others.');
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const column = key === 'ArrowRight' ? 1 : 0;
|
|
1414
|
+
if (pane.column === column && paneSpan(pane) === null) {
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
const index = layout.panes.filter((item) => item.column === column && paneSpan(item) === null).length;
|
|
1418
|
+
this.commit(movePane(layout, id, column, index), this.messageForMove(id, column));
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
const edge = paneSpan(pane);
|
|
1422
|
+
if (edge === 'top') {
|
|
1423
|
+
if (key === 'ArrowDown') {
|
|
1424
|
+
this.commit(movePane(layout, id, 0, 0), `${workspacePaneLabel(id)} returned to the left stack.`);
|
|
1425
|
+
}
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (edge === 'bottom') {
|
|
1429
|
+
if (key === 'ArrowUp') {
|
|
1430
|
+
const index = layout.panes.filter((item) => item.column === 0 && paneSpan(item) === null).length;
|
|
1431
|
+
this.commit(movePane(layout, id, 0, index), `${workspacePaneLabel(id)} returned to the left stack.`);
|
|
1432
|
+
}
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const siblings = layout.panes
|
|
1436
|
+
.filter((item) => item.column === pane.column && paneSpan(item) === null)
|
|
1437
|
+
.sort((a, b) => a.order - b.order);
|
|
1438
|
+
const currentIndex = siblings.findIndex((item) => item.id === id);
|
|
1439
|
+
const index = key === 'ArrowUp' ? Math.max(0, currentIndex - 1) : currentIndex + 1;
|
|
1440
|
+
this.commit(movePane(layout, id, pane.column, index), `${workspacePaneLabel(id)} moved.`);
|
|
1441
|
+
}
|
|
1442
|
+
resizeByKey(id, direction) {
|
|
1443
|
+
if (!this.editing()) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
const nextId = this.nextVisualId(id);
|
|
1447
|
+
if (!nextId) {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
this.commit(resizeAdjacent(this.layoutState(), id, nextId, direction * 0.2), `${workspacePaneLabel(id)} height adjusted.`);
|
|
1451
|
+
}
|
|
1452
|
+
beginPaneDrag(id, event) {
|
|
1453
|
+
if (!this.editing() || !this.isPrimaryPointer(event)) {
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
this.resizeSession = null;
|
|
1457
|
+
this.draggingPane.set(id);
|
|
1458
|
+
this.status.set(`Moving ${workspacePaneLabel(id)}. Release to place it.`);
|
|
1459
|
+
this.updatePaneDrag(event);
|
|
1460
|
+
}
|
|
1461
|
+
updatePaneDrag(event) {
|
|
1462
|
+
const id = this.draggingPane();
|
|
1463
|
+
const grid = this.gridElement();
|
|
1464
|
+
if (!id || !grid || !this.editing()) {
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
const rect = grid.getBoundingClientRect();
|
|
1468
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
this.refreshDropHints(rect);
|
|
1472
|
+
const splitTop = this.splitBodyTop();
|
|
1473
|
+
if (splitTop != null && this.inFullWidthDropZone(event.clientY, rect, splitTop)) {
|
|
1474
|
+
const wideIds = this.idsAboveSplit().filter((paneId) => paneId !== id);
|
|
1475
|
+
const midpoints = wideIds.map((paneId) => {
|
|
1476
|
+
const paneRect = this.paneElement(paneId)?.getBoundingClientRect();
|
|
1477
|
+
return paneRect ? paneRect.top + paneRect.height / 2 : Number.POSITIVE_INFINITY;
|
|
1478
|
+
});
|
|
1479
|
+
const index = insertionIndex(event.clientY, midpoints);
|
|
1480
|
+
this.dropTarget.set({ fullWidth: true, column: 0, index });
|
|
1481
|
+
this.dropIndicator.set(this.fullWidthIndicator(index, wideIds, rect));
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
const xRatio = (event.clientX - rect.left) / rect.width;
|
|
1485
|
+
const threshold = this.grid().split ? this.layoutState().startColumnRatio : DOCK_THRESHOLD;
|
|
1486
|
+
const column = dropColumn(xRatio, threshold, this.compact());
|
|
1487
|
+
const ids = this.idsInVisualColumn(column).filter((paneId) => paneId !== id);
|
|
1488
|
+
const midpoints = ids.map((paneId) => {
|
|
1489
|
+
const paneRect = this.paneElement(paneId)?.getBoundingClientRect();
|
|
1490
|
+
return paneRect ? paneRect.top + paneRect.height / 2 : Number.POSITIVE_INFINITY;
|
|
1491
|
+
});
|
|
1492
|
+
const index = insertionIndex(event.clientY, midpoints);
|
|
1493
|
+
this.dropTarget.set({ fullWidth: false, column, index });
|
|
1494
|
+
this.dropIndicator.set(this.indicatorBox(column, index, ids, rect));
|
|
1495
|
+
}
|
|
1496
|
+
endPaneDrag(event) {
|
|
1497
|
+
if (event?.type === 'pointercancel') {
|
|
1498
|
+
this.draggingPane.set(null);
|
|
1499
|
+
this.dropTarget.set(null);
|
|
1500
|
+
this.dropIndicator.set(null);
|
|
1501
|
+
this.dropHints.set([]);
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
if (event) {
|
|
1505
|
+
this.updatePaneDrag(event);
|
|
1506
|
+
}
|
|
1507
|
+
const id = this.draggingPane();
|
|
1508
|
+
const target = this.dropTarget();
|
|
1509
|
+
this.draggingPane.set(null);
|
|
1510
|
+
this.dropTarget.set(null);
|
|
1511
|
+
this.dropIndicator.set(null);
|
|
1512
|
+
this.dropHints.set([]);
|
|
1513
|
+
if (!id || !target || !this.editing()) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const layout = this.layoutState();
|
|
1517
|
+
const added = !layout.panes.some((pane) => pane.id === id);
|
|
1518
|
+
const index = target.fullWidth
|
|
1519
|
+
? this.bodyIndexForFullWidthDrop(target.index)
|
|
1520
|
+
: target.index;
|
|
1521
|
+
let next = added
|
|
1522
|
+
? addPane(layout, id, target.column, index)
|
|
1523
|
+
: movePane(layout, id, target.column, index);
|
|
1524
|
+
if (target.fullWidth) {
|
|
1525
|
+
next = setPaneWeight(setPaneFullWidth(next, id, true), id, 0);
|
|
1526
|
+
}
|
|
1527
|
+
if (sameLayout(layout, next)) {
|
|
1528
|
+
this.status.set(`${workspacePaneLabel(id)} stayed in place.`);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
this.commit(next, this.placeMessage(id, target, added));
|
|
1532
|
+
}
|
|
1533
|
+
onPalettePointerDown(kind, event) {
|
|
1534
|
+
if (!this.editing() || !this.isPrimaryPointer(event)) {
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
event.preventDefault();
|
|
1538
|
+
const id = kind === 'empty' ? nextEmptyId(this.layoutState()) : kind;
|
|
1539
|
+
this.paletteGesture = { id, x: event.clientX, y: event.clientY };
|
|
1540
|
+
this.capture(event);
|
|
1541
|
+
this.beginPaneDrag(id, event);
|
|
1542
|
+
}
|
|
1543
|
+
onPalettePointerMove(event) {
|
|
1544
|
+
const gesture = this.paletteGesture;
|
|
1545
|
+
if (!gesture) {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
if (Math.hypot(event.clientX - gesture.x, event.clientY - gesture.y) > 8) {
|
|
1549
|
+
this.paletteMenuLifted.set(true);
|
|
1550
|
+
}
|
|
1551
|
+
this.updatePaneDrag(event);
|
|
1552
|
+
}
|
|
1553
|
+
onPalettePointerUp(event) {
|
|
1554
|
+
const gesture = this.paletteGesture;
|
|
1555
|
+
if (!gesture) {
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
this.paletteGesture = null;
|
|
1559
|
+
this.ignorePaletteClick = true;
|
|
1560
|
+
setTimeout(() => {
|
|
1561
|
+
this.ignorePaletteClick = false;
|
|
1562
|
+
}, 0);
|
|
1563
|
+
const moved = Math.hypot(event.clientX - gesture.x, event.clientY - gesture.y) > 8;
|
|
1564
|
+
if (!moved) {
|
|
1565
|
+
this.clearDrag();
|
|
1566
|
+
this.commit(addPane(this.layoutState(), gesture.id, 0), `${workspacePaneLabel(gesture.id)} added.`);
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
this.endPaneDrag(event);
|
|
1570
|
+
this.closePalette();
|
|
1571
|
+
}
|
|
1572
|
+
onPalettePointerCancel() {
|
|
1573
|
+
this.paletteGesture = null;
|
|
1574
|
+
this.clearDrag();
|
|
1575
|
+
this.paletteMenuLifted.set(false);
|
|
1576
|
+
}
|
|
1577
|
+
onPaletteClick(kind) {
|
|
1578
|
+
if (this.ignorePaletteClick || !this.editing()) {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
const id = kind === 'empty' ? nextEmptyId(this.layoutState()) : kind;
|
|
1582
|
+
this.commit(addPane(this.layoutState(), id, 0), `${workspacePaneLabel(id)} added.`);
|
|
1583
|
+
}
|
|
1584
|
+
togglePalette() {
|
|
1585
|
+
this.paletteOpen.update((open) => !open);
|
|
1586
|
+
if (!this.paletteOpen()) {
|
|
1587
|
+
this.paletteMenuLifted.set(false);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
closePalette() {
|
|
1591
|
+
this.paletteOpen.set(false);
|
|
1592
|
+
this.paletteMenuLifted.set(false);
|
|
1593
|
+
}
|
|
1594
|
+
onPaletteMenuKeydown(event) {
|
|
1595
|
+
if (event.key === 'Escape') {
|
|
1596
|
+
event.preventDefault();
|
|
1597
|
+
this.closePalette();
|
|
1598
|
+
this.hostRef.nativeElement
|
|
1599
|
+
.querySelector('.pos-workspace__palette-toggle')
|
|
1600
|
+
?.focus();
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
const menu = event.currentTarget;
|
|
1607
|
+
if (!(menu instanceof HTMLElement)) {
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
const items = [...menu.querySelectorAll('.pos-workspace__palette-item')];
|
|
1611
|
+
const current = items.indexOf(document.activeElement);
|
|
1612
|
+
const step = event.key === 'ArrowDown' ? 1 : -1;
|
|
1613
|
+
const next = items[(current + step + items.length) % items.length];
|
|
1614
|
+
event.preventDefault();
|
|
1615
|
+
next?.focus();
|
|
1616
|
+
}
|
|
1617
|
+
undoLayout() {
|
|
1618
|
+
if (!this.editing()) {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
const previous = this.history.pop();
|
|
1622
|
+
if (!previous) {
|
|
1623
|
+
this.canUndo.set(false);
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
this.canUndo.set(this.history.length > 0);
|
|
1627
|
+
this.clearInteraction();
|
|
1628
|
+
this.layoutState.set(previous);
|
|
1629
|
+
this.publish('Last edit undone.');
|
|
1630
|
+
}
|
|
1631
|
+
applyDefaultLayout() {
|
|
1632
|
+
if (!this.editing()) {
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
this.clearInteraction();
|
|
1636
|
+
this.commit(POS_WORKSPACE_DEFAULT_LAYOUT, 'Default layout restored.');
|
|
1637
|
+
}
|
|
1638
|
+
resetLayout() {
|
|
1639
|
+
if (!this.editing()) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
this.clearInteraction();
|
|
1643
|
+
this.commit({ ...this.layoutState(), panes: [] }, 'New layout.');
|
|
1644
|
+
}
|
|
1645
|
+
exportLayout() {
|
|
1646
|
+
if (!this.editing()) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
const layout = this.layoutState();
|
|
1650
|
+
this.clearInteraction();
|
|
1651
|
+
this.layoutExport.emit(layout);
|
|
1652
|
+
this.downloadLayout(workspaceLayoutJson(layout));
|
|
1653
|
+
this.status.set('Layout exported.');
|
|
1654
|
+
}
|
|
1655
|
+
importLayoutFile(event) {
|
|
1656
|
+
if (!this.editing()) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
const input = event.target;
|
|
1660
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const file = input.files?.[0];
|
|
1664
|
+
input.value = '';
|
|
1665
|
+
if (!file) {
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
void file.text().then((text) => {
|
|
1669
|
+
try {
|
|
1670
|
+
const layout = parseWorkspaceLayout(text);
|
|
1671
|
+
this.clearInteraction();
|
|
1672
|
+
this.commit(layout, 'Layout imported.');
|
|
1673
|
+
this.status.set('Layout imported.');
|
|
1674
|
+
}
|
|
1675
|
+
catch {
|
|
1676
|
+
this.status.set('That file is not a workspace layout.');
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
setFullWidth(id, enabled) {
|
|
1681
|
+
if (!this.editing()) {
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const label = workspacePaneLabel(id);
|
|
1685
|
+
this.commit(setPaneFullWidth(this.layoutState(), id, enabled), enabled ? `${label} spans the full width.` : `${label} stays in its column.`);
|
|
1686
|
+
}
|
|
1687
|
+
setVisible(id, visible) {
|
|
1688
|
+
if (!this.editing()) {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
const label = workspacePaneLabel(id);
|
|
1692
|
+
this.commit(setPaneVisible(this.layoutState(), id, visible), visible ? `${label} is visible on the till.` : `${label} is hidden on the till.`);
|
|
1693
|
+
}
|
|
1694
|
+
setFlexible(id, enabled) {
|
|
1695
|
+
if (!this.editing()) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
const pane = this.layoutState().panes.find((item) => item.id === id);
|
|
1699
|
+
if (!pane) {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
const weight = enabled ? (pane.weight > 0 ? pane.weight : 1) : 0;
|
|
1703
|
+
const label = workspacePaneLabel(id);
|
|
1704
|
+
this.commit(setPaneWeight(this.layoutState(), id, weight), enabled ? `${label} fills leftover height.` : `${label} sizes to its content.`);
|
|
1705
|
+
}
|
|
1706
|
+
removeFromLayout(id) {
|
|
1707
|
+
if (!this.editing()) {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
this.commit(removePane(this.layoutState(), id), `${workspacePaneLabel(id)} removed.`);
|
|
1711
|
+
}
|
|
1712
|
+
beginPaneResize(id, event) {
|
|
1713
|
+
if (!this.editing() || !this.isPrimaryPointer(event)) {
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
const nextId = this.nextVisualId(id);
|
|
1717
|
+
const grid = this.gridElement();
|
|
1718
|
+
if (!nextId || !grid) {
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const rect = grid.getBoundingClientRect();
|
|
1722
|
+
if (rect.height <= 0) {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
this.draggingPane.set(null);
|
|
1726
|
+
this.dropIndicator.set(null);
|
|
1727
|
+
this.resizeSession = {
|
|
1728
|
+
id,
|
|
1729
|
+
nextId,
|
|
1730
|
+
startY: event.clientY,
|
|
1731
|
+
startLayout: this.layoutState(),
|
|
1732
|
+
span: rect.height,
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
updatePaneResize(event) {
|
|
1736
|
+
const session = this.resizeSession;
|
|
1737
|
+
if (!session || !this.editing()) {
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
const pair = session.startLayout.panes.filter((pane) => pane.id === session.id || pane.id === session.nextId);
|
|
1741
|
+
const total = pair.reduce((sum, pane) => sum + (pane.weight > 0 ? pane.weight : 1), 0);
|
|
1742
|
+
const delta = ((event.clientY - session.startY) / session.span) * total;
|
|
1743
|
+
this.layoutState.set(resizeAdjacent(session.startLayout, session.id, session.nextId, delta));
|
|
1744
|
+
}
|
|
1745
|
+
endPaneResize() {
|
|
1746
|
+
const session = this.resizeSession;
|
|
1747
|
+
if (!session) {
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
this.resizeSession = null;
|
|
1751
|
+
if (sameLayout(session.startLayout, this.layoutState())) {
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
this.remember(session.startLayout);
|
|
1755
|
+
this.publish(`${workspacePaneLabel(session.id)} height adjusted.`);
|
|
1756
|
+
}
|
|
1757
|
+
onSplitterDown(event) {
|
|
1758
|
+
if (!this.isPrimaryPointer(event)) {
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
event.preventDefault();
|
|
1762
|
+
this.capture(event);
|
|
1763
|
+
this.splitting = true;
|
|
1764
|
+
this.splitStartLayout = this.layoutState();
|
|
1765
|
+
}
|
|
1766
|
+
onSplitterMove(event) {
|
|
1767
|
+
if (!this.splitting) {
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
const grid = this.gridElement();
|
|
1771
|
+
if (!grid) {
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
const rect = grid.getBoundingClientRect();
|
|
1775
|
+
const style = getComputedStyle(grid);
|
|
1776
|
+
const padLeft = Number.parseFloat(style.paddingLeft) || 0;
|
|
1777
|
+
const padRight = Number.parseFloat(style.paddingRight) || 0;
|
|
1778
|
+
const gap = Number.parseFloat(style.columnGap || style.gap) || 0;
|
|
1779
|
+
const inner = rect.width - padLeft - padRight - gap;
|
|
1780
|
+
if (inner <= 0) {
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const ratio = (event.clientX - rect.left - padLeft) / inner;
|
|
1784
|
+
this.layoutState.set(setColumnRatio(this.layoutState(), ratio));
|
|
1785
|
+
}
|
|
1786
|
+
onSplitterUp() {
|
|
1787
|
+
if (!this.splitting) {
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
this.splitting = false;
|
|
1791
|
+
const start = this.splitStartLayout;
|
|
1792
|
+
this.splitStartLayout = null;
|
|
1793
|
+
if (!start || sameLayout(start, this.layoutState())) {
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
this.remember(start);
|
|
1797
|
+
this.publish('Column width adjusted.');
|
|
1798
|
+
}
|
|
1799
|
+
onSplitterKeydown(event) {
|
|
1800
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
event.preventDefault();
|
|
1804
|
+
const delta = event.key === 'ArrowRight' ? 0.04 : -0.04;
|
|
1805
|
+
this.commit(setColumnRatio(this.layoutState(), this.layoutState().startColumnRatio + delta), 'Column width adjusted.');
|
|
1806
|
+
}
|
|
1807
|
+
commit(next, message) {
|
|
1808
|
+
if (sameLayout(this.layoutState(), next)) {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
this.remember(this.layoutState());
|
|
1812
|
+
this.layoutState.set(next);
|
|
1813
|
+
this.publish(message);
|
|
1814
|
+
}
|
|
1815
|
+
remember(layout) {
|
|
1816
|
+
this.history.push(layout);
|
|
1817
|
+
if (this.history.length > 30) {
|
|
1818
|
+
this.history.shift();
|
|
1819
|
+
}
|
|
1820
|
+
this.canUndo.set(true);
|
|
1821
|
+
}
|
|
1822
|
+
downloadLayout(json) {
|
|
1823
|
+
if (typeof URL.createObjectURL !== 'function') {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
1827
|
+
const url = URL.createObjectURL(blob);
|
|
1828
|
+
const link = this.hostRef.nativeElement.ownerDocument.createElement('a');
|
|
1829
|
+
link.href = url;
|
|
1830
|
+
link.download = 'pos-workspace-layout.json';
|
|
1831
|
+
link.click();
|
|
1832
|
+
URL.revokeObjectURL(url);
|
|
1833
|
+
}
|
|
1834
|
+
publish(message) {
|
|
1835
|
+
this.status.set(message);
|
|
1836
|
+
this.layoutChange.emit(this.layoutState());
|
|
1837
|
+
}
|
|
1838
|
+
placeMessage(id, target, added) {
|
|
1839
|
+
const label = workspacePaneLabel(id);
|
|
1840
|
+
if (target.fullWidth) {
|
|
1841
|
+
return added
|
|
1842
|
+
? `${label} added at full width.`
|
|
1843
|
+
: `${label} spans the full width.`;
|
|
1844
|
+
}
|
|
1845
|
+
if (added) {
|
|
1846
|
+
return target.column === 1 ? `${label} added on the right.` : `${label} added.`;
|
|
1847
|
+
}
|
|
1848
|
+
return this.messageForMove(id, target.column);
|
|
1849
|
+
}
|
|
1850
|
+
messageForMove(id, column) {
|
|
1851
|
+
const label = workspacePaneLabel(id);
|
|
1852
|
+
const current = this.layoutState().panes.find((pane) => pane.id === id);
|
|
1853
|
+
const occupants = this.layoutState().panes.filter((pane) => pane.column === column && pane.id !== id);
|
|
1854
|
+
if (column === 1 && occupants.length === 0) {
|
|
1855
|
+
return `${label} docked on the right, full height.`;
|
|
1856
|
+
}
|
|
1857
|
+
if (column === 0 && current?.column === 1) {
|
|
1858
|
+
return `${label} returned to the left stack.`;
|
|
1859
|
+
}
|
|
1860
|
+
return `${label} moved.`;
|
|
1861
|
+
}
|
|
1862
|
+
refreshDropHints(gridRect) {
|
|
1863
|
+
if (this.compact()) {
|
|
1864
|
+
this.dropHints.set([]);
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
const host = this.hostRef.nativeElement.getBoundingClientRect();
|
|
1868
|
+
const top = gridRect.top - host.top;
|
|
1869
|
+
const left = gridRect.left - host.left;
|
|
1870
|
+
const ratio = this.grid().split ? this.layoutState().startColumnRatio : DOCK_THRESHOLD;
|
|
1871
|
+
const hints = [
|
|
1872
|
+
{
|
|
1873
|
+
kind: 'dock',
|
|
1874
|
+
top,
|
|
1875
|
+
left: left + gridRect.width * ratio,
|
|
1876
|
+
width: gridRect.width * (1 - ratio),
|
|
1877
|
+
height: gridRect.height,
|
|
1878
|
+
},
|
|
1879
|
+
];
|
|
1880
|
+
const splitTop = this.splitBodyTop();
|
|
1881
|
+
if (splitTop != null) {
|
|
1882
|
+
const roomAbove = Math.max(0, splitTop - gridRect.top);
|
|
1883
|
+
hints.unshift({
|
|
1884
|
+
kind: 'top',
|
|
1885
|
+
top,
|
|
1886
|
+
left,
|
|
1887
|
+
width: gridRect.width,
|
|
1888
|
+
height: roomAbove > 8 ? roomAbove : FULL_WIDTH_BAND,
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
this.dropHints.set(hints);
|
|
1892
|
+
}
|
|
1893
|
+
/** True when the pointer is above the docked column (or in the top band when nothing sits above it). */
|
|
1894
|
+
inFullWidthDropZone(clientY, gridRect, splitTop) {
|
|
1895
|
+
if (splitTop > gridRect.top + 8) {
|
|
1896
|
+
return clientY < splitTop;
|
|
1897
|
+
}
|
|
1898
|
+
return clientY < gridRect.top + FULL_WIDTH_BAND;
|
|
1899
|
+
}
|
|
1900
|
+
/** Top edge of the side-by-side region (right column). Null when nothing is docked right. */
|
|
1901
|
+
splitBodyTop() {
|
|
1902
|
+
if (this.compact() || !this.grid().split) {
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
let top = Number.POSITIVE_INFINITY;
|
|
1906
|
+
for (const pane of this.grid().panes) {
|
|
1907
|
+
if (pane.column !== 2) {
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
const rect = this.paneElement(pane.id)?.getBoundingClientRect();
|
|
1911
|
+
if (rect) {
|
|
1912
|
+
top = Math.min(top, rect.top);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return Number.isFinite(top) ? top : null;
|
|
1916
|
+
}
|
|
1917
|
+
/** Full-width rows that sit above the split, used for drop ordering in that band. */
|
|
1918
|
+
idsAboveSplit() {
|
|
1919
|
+
const splitTop = this.splitBodyTop();
|
|
1920
|
+
if (splitTop == null) {
|
|
1921
|
+
return [];
|
|
1922
|
+
}
|
|
1923
|
+
return this.grid()
|
|
1924
|
+
.panes.filter((pane) => {
|
|
1925
|
+
if (pane.spanColumns !== true) {
|
|
1926
|
+
return false;
|
|
1927
|
+
}
|
|
1928
|
+
const rect = this.paneElement(pane.id)?.getBoundingClientRect();
|
|
1929
|
+
return rect != null && rect.top + rect.height / 2 < splitTop;
|
|
1930
|
+
})
|
|
1931
|
+
.sort((a, b) => a.rowStart - b.rowStart)
|
|
1932
|
+
.map((pane) => pane.id);
|
|
1933
|
+
}
|
|
1934
|
+
/** Maps a visual index among banner/full-width rows onto the body column order. */
|
|
1935
|
+
bodyIndexForFullWidthDrop(visualIndex) {
|
|
1936
|
+
const banners = new Set(this.layoutState()
|
|
1937
|
+
.panes.filter((pane) => paneSpan(pane) !== null)
|
|
1938
|
+
.map((pane) => pane.id));
|
|
1939
|
+
const bannersAbove = this.idsAboveSplit().filter((id) => banners.has(id)).length;
|
|
1940
|
+
return Math.max(0, visualIndex - bannersAbove);
|
|
1941
|
+
}
|
|
1942
|
+
fullWidthIndicator(index, ids, gridRect) {
|
|
1943
|
+
const host = this.hostRef.nativeElement.getBoundingClientRect();
|
|
1944
|
+
const left = gridRect.left - host.left;
|
|
1945
|
+
const width = gridRect.width;
|
|
1946
|
+
if (ids.length === 0) {
|
|
1947
|
+
const splitTop = this.splitBodyTop() ?? gridRect.top;
|
|
1948
|
+
return {
|
|
1949
|
+
top: Math.max(gridRect.top, splitTop - FULL_WIDTH_BAND / 2) - host.top,
|
|
1950
|
+
left,
|
|
1951
|
+
width,
|
|
1952
|
+
height: FULL_WIDTH_BAND,
|
|
1953
|
+
line: false,
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
const anchorId = index >= ids.length ? ids[ids.length - 1] : ids[index];
|
|
1957
|
+
const anchor = this.paneElement(anchorId)?.getBoundingClientRect();
|
|
1958
|
+
if (!anchor) {
|
|
1959
|
+
return {
|
|
1960
|
+
top: gridRect.top - host.top,
|
|
1961
|
+
left,
|
|
1962
|
+
width,
|
|
1963
|
+
height: FULL_WIDTH_BAND,
|
|
1964
|
+
line: false,
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
const top = index >= ids.length ? anchor.bottom - host.top : anchor.top - host.top;
|
|
1968
|
+
return { top, left, width, height: 4, line: true };
|
|
1969
|
+
}
|
|
1970
|
+
nextVisualId(id) {
|
|
1971
|
+
const place = this.placements().get(id);
|
|
1972
|
+
if (!place?.showResize) {
|
|
1973
|
+
return null;
|
|
1974
|
+
}
|
|
1975
|
+
const next = this.grid()
|
|
1976
|
+
.panes.filter((pane) => pane.column === place.column && pane.rowStart >= place.rowEnd)
|
|
1977
|
+
.sort((a, b) => a.rowStart - b.rowStart)[0];
|
|
1978
|
+
return next?.id ?? null;
|
|
1979
|
+
}
|
|
1980
|
+
idsInVisualColumn(column) {
|
|
1981
|
+
const dragging = this.draggingPane();
|
|
1982
|
+
const edges = new Set(this.layoutState()
|
|
1983
|
+
.panes.filter((pane) => paneSpan(pane) !== null)
|
|
1984
|
+
.map((pane) => pane.id));
|
|
1985
|
+
const gridColumn = this.compact() ? 1 : column + 1;
|
|
1986
|
+
return this.grid()
|
|
1987
|
+
.panes.filter((pane) => {
|
|
1988
|
+
if (pane.id === dragging || edges.has(pane.id)) {
|
|
1989
|
+
return false;
|
|
1990
|
+
}
|
|
1991
|
+
if (this.compact() || column === 0) {
|
|
1992
|
+
return pane.spanColumns === true || pane.column === gridColumn;
|
|
1993
|
+
}
|
|
1994
|
+
return pane.column === gridColumn && pane.spanColumns !== true;
|
|
1995
|
+
})
|
|
1996
|
+
.sort((a, b) => a.rowStart - b.rowStart)
|
|
1997
|
+
.map((pane) => pane.id);
|
|
1998
|
+
}
|
|
1999
|
+
indicatorBox(column, index, ids, gridRect) {
|
|
2000
|
+
const host = this.hostRef.nativeElement.getBoundingClientRect();
|
|
2001
|
+
if (ids.length === 0) {
|
|
2002
|
+
const threshold = this.grid().split ? this.layoutState().startColumnRatio : DOCK_THRESHOLD;
|
|
2003
|
+
const bandLeft = column === 1 ? gridRect.width * threshold : 0;
|
|
2004
|
+
const bandWidth = column === 1 ? gridRect.width * (1 - threshold) : gridRect.width * 0.3;
|
|
2005
|
+
return {
|
|
2006
|
+
top: gridRect.top - host.top,
|
|
2007
|
+
left: gridRect.left - host.left + bandLeft,
|
|
2008
|
+
width: bandWidth,
|
|
2009
|
+
height: gridRect.height,
|
|
2010
|
+
line: false,
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
const anchorId = index >= ids.length ? ids[ids.length - 1] : ids[index];
|
|
2014
|
+
const anchor = this.paneElement(anchorId)?.getBoundingClientRect();
|
|
2015
|
+
if (!anchor) {
|
|
2016
|
+
return null;
|
|
2017
|
+
}
|
|
2018
|
+
const top = index >= ids.length ? anchor.bottom - host.top : anchor.top - host.top;
|
|
2019
|
+
return {
|
|
2020
|
+
top,
|
|
2021
|
+
left: anchor.left - host.left,
|
|
2022
|
+
width: anchor.width,
|
|
2023
|
+
height: 4,
|
|
2024
|
+
line: true,
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
paneElement(id) {
|
|
2028
|
+
return this.hostRef.nativeElement.querySelector(`[data-pane="${id}"]`);
|
|
2029
|
+
}
|
|
2030
|
+
gridElement() {
|
|
2031
|
+
return this.frameRef()?.nativeElement ?? null;
|
|
2032
|
+
}
|
|
2033
|
+
clearDrag() {
|
|
2034
|
+
this.draggingPane.set(null);
|
|
2035
|
+
this.dropTarget.set(null);
|
|
2036
|
+
this.dropIndicator.set(null);
|
|
2037
|
+
this.dropHints.set([]);
|
|
2038
|
+
}
|
|
2039
|
+
clearInteraction() {
|
|
2040
|
+
this.paletteGesture = null;
|
|
2041
|
+
this.paletteOpen.set(false);
|
|
2042
|
+
this.paletteMenuLifted.set(false);
|
|
2043
|
+
this.settingsPane.set(null);
|
|
2044
|
+
this.clearDrag();
|
|
2045
|
+
this.resizeSession = null;
|
|
2046
|
+
this.splitting = false;
|
|
2047
|
+
this.splitStartLayout = null;
|
|
2048
|
+
}
|
|
2049
|
+
isPrimaryPointer(event) {
|
|
2050
|
+
return event.pointerType !== 'mouse' || event.button === 0;
|
|
2051
|
+
}
|
|
2052
|
+
capture(event) {
|
|
2053
|
+
const handle = event.currentTarget;
|
|
2054
|
+
if (!(handle instanceof HTMLElement)) {
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
try {
|
|
2058
|
+
handle.setPointerCapture(event.pointerId);
|
|
2059
|
+
}
|
|
2060
|
+
catch {
|
|
2061
|
+
// The pointer is not active yet. The drag still follows later pointer events.
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosWorkspace, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2065
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: PosWorkspace, isStandalone: true, selector: "pos-workspace", inputs: { editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { layoutChange: "layoutChange", layoutExport: "layoutExport" }, host: { properties: { "class.pos-workspace--editing": "editing()", "class.pos-workspace--dragging": "draggingPane() !== null" }, classAttribute: "pos-workspace" }, providers: [{ provide: POS_WORKSPACE, useExisting: forwardRef(() => PosWorkspace) }], queries: [{ propertyName: "paneRefs", predicate: PosWorkspacePane, isSignal: true }], viewQueries: [{ propertyName: "frameRef", first: true, predicate: ["frame"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (editing()) {\r\n <div class=\"pos-workspace__tools\">\r\n <div class=\"pos-workspace__palette\">\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-toggle\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"paletteOpen()\"\r\n (click)=\"togglePalette()\"\r\n >\r\n Components\r\n </button>\r\n @if (paletteOpen()) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-backdrop\"\r\n tabindex=\"-1\"\r\n aria-label=\"Close components\"\r\n (click)=\"closePalette()\"\r\n ></button>\r\n <div\r\n class=\"pos-workspace__palette-menu\"\r\n [class.pos-workspace__palette-menu--lifted]=\"paletteMenuLifted()\"\r\n role=\"menu\"\r\n aria-label=\"Components\"\r\n (keydown)=\"onPaletteMenuKeydown($event)\"\r\n >\r\n @for (id of palette(); track id) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-item\"\r\n role=\"menuitem\"\r\n [attr.data-palette]=\"id\"\r\n (pointerdown)=\"onPalettePointerDown(id, $event)\"\r\n (pointermove)=\"onPalettePointerMove($event)\"\r\n (pointerup)=\"onPalettePointerUp($event)\"\r\n (pointercancel)=\"onPalettePointerCancel()\"\r\n (click)=\"onPaletteClick(id)\"\r\n >\r\n {{ paneLabel(id) }}\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-item\"\r\n role=\"menuitem\"\r\n data-palette=\"empty\"\r\n (pointerdown)=\"onPalettePointerDown('empty', $event)\"\r\n (pointermove)=\"onPalettePointerMove($event)\"\r\n (pointerup)=\"onPalettePointerUp($event)\"\r\n (pointercancel)=\"onPalettePointerCancel()\"\r\n (click)=\"onPaletteClick('empty')\"\r\n >\r\n Custom container\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n <div class=\"pos-workspace__actions\">\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__undo\"\r\n [disabled]=\"!canUndo()\"\r\n (click)=\"undoLayout()\"\r\n >\r\n Undo\r\n </button>\r\n <button type=\"button\" class=\"pos-workspace__new\" (click)=\"resetLayout()\">New</button>\r\n <button type=\"button\" class=\"pos-workspace__default\" (click)=\"applyDefaultLayout()\">\r\n Default layout\r\n </button>\r\n <button type=\"button\" class=\"pos-workspace__import\" (click)=\"layoutFile.click()\">\r\n Import layout\r\n </button>\r\n <button type=\"button\" class=\"pos-workspace__export\" (click)=\"exportLayout()\">\r\n Export layout\r\n </button>\r\n <input\r\n #layoutFile\r\n class=\"pos-workspace__file\"\r\n type=\"file\"\r\n accept=\"application/json,.json\"\r\n aria-label=\"Import layout file\"\r\n (change)=\"importLayoutFile($event)\"\r\n />\r\n </div>\r\n </div>\r\n}\r\n\r\n<div\r\n #frame\r\n class=\"pos-workspace__grid\"\r\n [style.grid-template-columns]=\"grid().columns\"\r\n [style.grid-template-rows]=\"grid().rows\"\r\n>\r\n <ng-content />\r\n @for (slot of emptySlots(); track slot) {\r\n <pos-workspace-pane [paneId]=\"slot\" [placeholder]=\"true\" />\r\n }\r\n @if (showSplitter()) {\r\n <div\r\n class=\"pos-workspace__splitter\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n aria-label=\"Resize columns\"\r\n aria-valuemin=\"28\"\r\n aria-valuemax=\"72\"\r\n [attr.aria-valuenow]=\"ratioPercent()\"\r\n tabindex=\"0\"\r\n [style.grid-row]=\"splitterRow()\"\r\n [style.left]=\"splitterLeft()\"\r\n (pointerdown)=\"onSplitterDown($event)\"\r\n (pointermove)=\"onSplitterMove($event)\"\r\n (pointerup)=\"onSplitterUp()\"\r\n (pointercancel)=\"onSplitterUp()\"\r\n (keydown)=\"onSplitterKeydown($event)\"\r\n ></div>\r\n }\r\n</div>\r\n\r\n@for (hint of dropHints(); track hint.kind) {\r\n <div\r\n class=\"pos-workspace__hint\"\r\n [class.pos-workspace__hint--top]=\"hint.kind === 'top'\"\r\n [class.pos-workspace__hint--bottom]=\"hint.kind === 'bottom'\"\r\n [class.pos-workspace__hint--dock]=\"hint.kind === 'dock'\"\r\n [style.top.px]=\"hint.top\"\r\n [style.left.px]=\"hint.left\"\r\n [style.width.px]=\"hint.width\"\r\n [style.height.px]=\"hint.height\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n}\r\n\r\n@if (dropIndicator(); as indicator) {\r\n <div\r\n class=\"pos-workspace__drop\"\r\n [class.pos-workspace__drop--line]=\"indicator.line\"\r\n [style.top.px]=\"indicator.top\"\r\n [style.left.px]=\"indicator.left\"\r\n [style.width.px]=\"indicator.width\"\r\n [style.height.px]=\"indicator.height\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n}\r\n\r\n<div class=\"pos-workspace__status\" aria-live=\"polite\">{{ status() }}</div>\r\n", styles: [":host{display:flex;position:relative;flex-direction:column;box-sizing:border-box;height:100%;min-height:0;color:var(--pos-color-text);font-family:var(--pos-font-family)}.pos-workspace__tools{display:flex;flex:0 0 auto;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--pos-space-sm);padding:var(--pos-space-sm) var(--pos-space-lg) 0}.pos-workspace__palette{position:relative}.pos-workspace__palette-toggle{min-height:var(--pos-button-min-height);padding:0 var(--pos-space-lg);border:1px dashed var(--pos-color-primary);border-radius:var(--pos-radius-md);background:var(--pos-color-surface);color:var(--pos-color-text);font:inherit;font-size:var(--pos-font-size-md);cursor:pointer}.pos-workspace__palette-backdrop{position:fixed;z-index:5;inset:0;padding:0;border:0;background:transparent}.pos-workspace__palette-menu{position:absolute;z-index:6;top:calc(100% + var(--pos-space-xs));left:0;display:flex;flex-direction:column;min-width:14rem;max-height:min(20rem,70vh);overflow:auto;padding:var(--pos-space-xs);border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-md);background:var(--pos-color-surface)}.pos-workspace__palette-menu--lifted{visibility:hidden}.pos-workspace__palette-item{min-height:var(--pos-button-min-height);padding:0 var(--pos-space-md);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);font:inherit;font-size:var(--pos-font-size-md);text-align:left;cursor:grab;touch-action:none}.pos-workspace__palette-item:hover{background:color-mix(in srgb,var(--pos-color-primary) 12%,var(--pos-color-surface))}.pos-workspace__palette-toggle:focus-visible,.pos-workspace__palette-item:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}.pos-workspace__actions{display:flex;flex-wrap:wrap;gap:var(--pos-space-sm)}.pos-workspace__undo,.pos-workspace__new,.pos-workspace__default,.pos-workspace__export,.pos-workspace__import{min-height:var(--pos-button-min-height);padding:0 var(--pos-space-lg);border:1px solid var(--pos-color-key-border);border-radius:var(--pos-radius-md);background:var(--pos-color-key);color:var(--pos-color-text);font:inherit;font-size:var(--pos-font-size-md);cursor:pointer}.pos-workspace__file{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.pos-workspace__undo:disabled{background:var(--pos-color-disabled);color:var(--pos-color-on-disabled);cursor:default}.pos-workspace__undo:focus-visible,.pos-workspace__new:focus-visible,.pos-workspace__default:focus-visible,.pos-workspace__export:focus-visible,.pos-workspace__import:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}:host(.pos-workspace--dragging){cursor:grabbing;-webkit-user-select:none;user-select:none}.pos-workspace__grid{display:grid;position:relative;flex:1 1 auto;box-sizing:border-box;min-height:0;padding:var(--pos-space-lg);gap:var(--pos-space-md)}.pos-workspace__hint{position:absolute;z-index:2;box-sizing:border-box;border:2px dashed color-mix(in srgb,var(--pos-color-primary) 55%,transparent);border-radius:var(--pos-radius-md);background:color-mix(in srgb,var(--pos-color-primary) 8%,transparent);pointer-events:none}.pos-workspace__drop{position:absolute;z-index:3;box-sizing:border-box;border:2px dashed var(--pos-color-primary);border-radius:var(--pos-radius-md);background:color-mix(in srgb,var(--pos-color-primary) 24%,transparent);pointer-events:none}.pos-workspace__drop--line{border:0;border-radius:var(--pos-radius-pill);background:var(--pos-color-primary)}.pos-workspace__splitter{position:absolute;z-index:4;top:0;bottom:0;grid-column:1/-1;width:var(--pos-space-xl);padding:0;border:0;background:transparent;cursor:ew-resize;touch-action:none;transform:translate(-50%)}.pos-workspace__splitter:before{content:\"\";position:absolute;top:0;bottom:0;left:50%;width:var(--pos-space-xs);border-radius:var(--pos-radius-pill);background:var(--pos-color-primary);transform:translate(-50%)}.pos-workspace__splitter:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}.pos-workspace__status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: PosWorkspacePane, selector: "pos-workspace-pane", inputs: ["paneId", "placeholder"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2066
|
+
}
|
|
2067
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PosWorkspace, decorators: [{
|
|
2068
|
+
type: Component,
|
|
2069
|
+
args: [{ selector: 'pos-workspace', changeDetection: ChangeDetectionStrategy.OnPush, imports: [PosWorkspacePane], providers: [{ provide: POS_WORKSPACE, useExisting: forwardRef(() => PosWorkspace) }], host: {
|
|
2070
|
+
class: 'pos-workspace',
|
|
2071
|
+
'[class.pos-workspace--editing]': 'editing()',
|
|
2072
|
+
'[class.pos-workspace--dragging]': 'draggingPane() !== null',
|
|
2073
|
+
}, template: "@if (editing()) {\r\n <div class=\"pos-workspace__tools\">\r\n <div class=\"pos-workspace__palette\">\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-toggle\"\r\n aria-haspopup=\"menu\"\r\n [attr.aria-expanded]=\"paletteOpen()\"\r\n (click)=\"togglePalette()\"\r\n >\r\n Components\r\n </button>\r\n @if (paletteOpen()) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-backdrop\"\r\n tabindex=\"-1\"\r\n aria-label=\"Close components\"\r\n (click)=\"closePalette()\"\r\n ></button>\r\n <div\r\n class=\"pos-workspace__palette-menu\"\r\n [class.pos-workspace__palette-menu--lifted]=\"paletteMenuLifted()\"\r\n role=\"menu\"\r\n aria-label=\"Components\"\r\n (keydown)=\"onPaletteMenuKeydown($event)\"\r\n >\r\n @for (id of palette(); track id) {\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-item\"\r\n role=\"menuitem\"\r\n [attr.data-palette]=\"id\"\r\n (pointerdown)=\"onPalettePointerDown(id, $event)\"\r\n (pointermove)=\"onPalettePointerMove($event)\"\r\n (pointerup)=\"onPalettePointerUp($event)\"\r\n (pointercancel)=\"onPalettePointerCancel()\"\r\n (click)=\"onPaletteClick(id)\"\r\n >\r\n {{ paneLabel(id) }}\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__palette-item\"\r\n role=\"menuitem\"\r\n data-palette=\"empty\"\r\n (pointerdown)=\"onPalettePointerDown('empty', $event)\"\r\n (pointermove)=\"onPalettePointerMove($event)\"\r\n (pointerup)=\"onPalettePointerUp($event)\"\r\n (pointercancel)=\"onPalettePointerCancel()\"\r\n (click)=\"onPaletteClick('empty')\"\r\n >\r\n Custom container\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n <div class=\"pos-workspace__actions\">\r\n <button\r\n type=\"button\"\r\n class=\"pos-workspace__undo\"\r\n [disabled]=\"!canUndo()\"\r\n (click)=\"undoLayout()\"\r\n >\r\n Undo\r\n </button>\r\n <button type=\"button\" class=\"pos-workspace__new\" (click)=\"resetLayout()\">New</button>\r\n <button type=\"button\" class=\"pos-workspace__default\" (click)=\"applyDefaultLayout()\">\r\n Default layout\r\n </button>\r\n <button type=\"button\" class=\"pos-workspace__import\" (click)=\"layoutFile.click()\">\r\n Import layout\r\n </button>\r\n <button type=\"button\" class=\"pos-workspace__export\" (click)=\"exportLayout()\">\r\n Export layout\r\n </button>\r\n <input\r\n #layoutFile\r\n class=\"pos-workspace__file\"\r\n type=\"file\"\r\n accept=\"application/json,.json\"\r\n aria-label=\"Import layout file\"\r\n (change)=\"importLayoutFile($event)\"\r\n />\r\n </div>\r\n </div>\r\n}\r\n\r\n<div\r\n #frame\r\n class=\"pos-workspace__grid\"\r\n [style.grid-template-columns]=\"grid().columns\"\r\n [style.grid-template-rows]=\"grid().rows\"\r\n>\r\n <ng-content />\r\n @for (slot of emptySlots(); track slot) {\r\n <pos-workspace-pane [paneId]=\"slot\" [placeholder]=\"true\" />\r\n }\r\n @if (showSplitter()) {\r\n <div\r\n class=\"pos-workspace__splitter\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n aria-label=\"Resize columns\"\r\n aria-valuemin=\"28\"\r\n aria-valuemax=\"72\"\r\n [attr.aria-valuenow]=\"ratioPercent()\"\r\n tabindex=\"0\"\r\n [style.grid-row]=\"splitterRow()\"\r\n [style.left]=\"splitterLeft()\"\r\n (pointerdown)=\"onSplitterDown($event)\"\r\n (pointermove)=\"onSplitterMove($event)\"\r\n (pointerup)=\"onSplitterUp()\"\r\n (pointercancel)=\"onSplitterUp()\"\r\n (keydown)=\"onSplitterKeydown($event)\"\r\n ></div>\r\n }\r\n</div>\r\n\r\n@for (hint of dropHints(); track hint.kind) {\r\n <div\r\n class=\"pos-workspace__hint\"\r\n [class.pos-workspace__hint--top]=\"hint.kind === 'top'\"\r\n [class.pos-workspace__hint--bottom]=\"hint.kind === 'bottom'\"\r\n [class.pos-workspace__hint--dock]=\"hint.kind === 'dock'\"\r\n [style.top.px]=\"hint.top\"\r\n [style.left.px]=\"hint.left\"\r\n [style.width.px]=\"hint.width\"\r\n [style.height.px]=\"hint.height\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n}\r\n\r\n@if (dropIndicator(); as indicator) {\r\n <div\r\n class=\"pos-workspace__drop\"\r\n [class.pos-workspace__drop--line]=\"indicator.line\"\r\n [style.top.px]=\"indicator.top\"\r\n [style.left.px]=\"indicator.left\"\r\n [style.width.px]=\"indicator.width\"\r\n [style.height.px]=\"indicator.height\"\r\n aria-hidden=\"true\"\r\n ></div>\r\n}\r\n\r\n<div class=\"pos-workspace__status\" aria-live=\"polite\">{{ status() }}</div>\r\n", styles: [":host{display:flex;position:relative;flex-direction:column;box-sizing:border-box;height:100%;min-height:0;color:var(--pos-color-text);font-family:var(--pos-font-family)}.pos-workspace__tools{display:flex;flex:0 0 auto;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--pos-space-sm);padding:var(--pos-space-sm) var(--pos-space-lg) 0}.pos-workspace__palette{position:relative}.pos-workspace__palette-toggle{min-height:var(--pos-button-min-height);padding:0 var(--pos-space-lg);border:1px dashed var(--pos-color-primary);border-radius:var(--pos-radius-md);background:var(--pos-color-surface);color:var(--pos-color-text);font:inherit;font-size:var(--pos-font-size-md);cursor:pointer}.pos-workspace__palette-backdrop{position:fixed;z-index:5;inset:0;padding:0;border:0;background:transparent}.pos-workspace__palette-menu{position:absolute;z-index:6;top:calc(100% + var(--pos-space-xs));left:0;display:flex;flex-direction:column;min-width:14rem;max-height:min(20rem,70vh);overflow:auto;padding:var(--pos-space-xs);border:1px solid var(--pos-color-border);border-radius:var(--pos-radius-md);background:var(--pos-color-surface)}.pos-workspace__palette-menu--lifted{visibility:hidden}.pos-workspace__palette-item{min-height:var(--pos-button-min-height);padding:0 var(--pos-space-md);border:0;border-radius:var(--pos-radius-sm);background:transparent;color:var(--pos-color-text);font:inherit;font-size:var(--pos-font-size-md);text-align:left;cursor:grab;touch-action:none}.pos-workspace__palette-item:hover{background:color-mix(in srgb,var(--pos-color-primary) 12%,var(--pos-color-surface))}.pos-workspace__palette-toggle:focus-visible,.pos-workspace__palette-item:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}.pos-workspace__actions{display:flex;flex-wrap:wrap;gap:var(--pos-space-sm)}.pos-workspace__undo,.pos-workspace__new,.pos-workspace__default,.pos-workspace__export,.pos-workspace__import{min-height:var(--pos-button-min-height);padding:0 var(--pos-space-lg);border:1px solid var(--pos-color-key-border);border-radius:var(--pos-radius-md);background:var(--pos-color-key);color:var(--pos-color-text);font:inherit;font-size:var(--pos-font-size-md);cursor:pointer}.pos-workspace__file{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.pos-workspace__undo:disabled{background:var(--pos-color-disabled);color:var(--pos-color-on-disabled);cursor:default}.pos-workspace__undo:focus-visible,.pos-workspace__new:focus-visible,.pos-workspace__default:focus-visible,.pos-workspace__export:focus-visible,.pos-workspace__import:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}:host(.pos-workspace--dragging){cursor:grabbing;-webkit-user-select:none;user-select:none}.pos-workspace__grid{display:grid;position:relative;flex:1 1 auto;box-sizing:border-box;min-height:0;padding:var(--pos-space-lg);gap:var(--pos-space-md)}.pos-workspace__hint{position:absolute;z-index:2;box-sizing:border-box;border:2px dashed color-mix(in srgb,var(--pos-color-primary) 55%,transparent);border-radius:var(--pos-radius-md);background:color-mix(in srgb,var(--pos-color-primary) 8%,transparent);pointer-events:none}.pos-workspace__drop{position:absolute;z-index:3;box-sizing:border-box;border:2px dashed var(--pos-color-primary);border-radius:var(--pos-radius-md);background:color-mix(in srgb,var(--pos-color-primary) 24%,transparent);pointer-events:none}.pos-workspace__drop--line{border:0;border-radius:var(--pos-radius-pill);background:var(--pos-color-primary)}.pos-workspace__splitter{position:absolute;z-index:4;top:0;bottom:0;grid-column:1/-1;width:var(--pos-space-xl);padding:0;border:0;background:transparent;cursor:ew-resize;touch-action:none;transform:translate(-50%)}.pos-workspace__splitter:before{content:\"\";position:absolute;top:0;bottom:0;left:50%;width:var(--pos-space-xs);border-radius:var(--pos-radius-pill);background:var(--pos-color-primary);transform:translate(-50%)}.pos-workspace__splitter:focus-visible{outline:2px solid var(--pos-color-ink);outline-offset:2px}.pos-workspace__status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"] }]
|
|
2074
|
+
}], ctorParameters: () => [], propDecorators: { paneRefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => PosWorkspacePane), { isSignal: true }] }], frameRef: [{ type: i0.ViewChild, args: ['frame', { isSignal: true }] }], editing: [{ type: i0.Input, args: [{ isSignal: true, alias: "editing", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], layoutChange: [{ type: i0.Output, args: ["layoutChange"] }], layoutExport: [{ type: i0.Output, args: ["layoutExport"] }] } });
|
|
2075
|
+
|
|
357
2076
|
const COIN_AMOUNTS = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2];
|
|
358
2077
|
const NOTE_AMOUNTS = [5, 10, 20, 50];
|
|
359
2078
|
function roundMoney(amount) {
|
|
@@ -1278,5 +2997,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1278
2997
|
* Generated bundle index. Do not edit.
|
|
1279
2998
|
*/
|
|
1280
2999
|
|
|
1281
|
-
export { PosBasket, PosHeader, PosItemEntry, PosLogin, PosReceipt, PosTender, PosTokens, PosTransactionActionBar };
|
|
3000
|
+
export { POS_WORKSPACE_BASKET_RIGHT_LAYOUT, POS_WORKSPACE_DEFAULT_LAYOUT, POS_WORKSPACE_EMPTY_LAYOUT, POS_WORKSPACE_HEADER_SPAN_LAYOUT, PosBasket, PosHeader, PosItemEntry, PosLogin, PosReceipt, PosTender, PosTokens, PosTransactionActionBar, PosWorkspace, PosWorkspacePane, parseWorkspaceLayout, workspaceLayoutJson };
|
|
1282
3001
|
//# sourceMappingURL=retalia-pos-components.mjs.map
|