@operato/layout 10.29.46 → 10.29.50
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/CHANGELOG.md +26 -0
- package/dist/src/actions/entity360-door.d.ts +28 -0
- package/dist/src/actions/entity360-door.js +45 -0
- package/dist/src/actions/entity360-door.js.map +1 -1
- package/dist/src/actions/entity360-registry.d.ts +13 -3
- package/dist/src/actions/entity360-registry.js +49 -8
- package/dist/src/actions/entity360-registry.js.map +1 -1
- package/dist/src/actions/entity360.d.ts +2 -0
- package/dist/src/actions/entity360.js +4 -0
- package/dist/src/actions/entity360.js.map +1 -1
- package/dist/src/actions/id-lens.d.ts +67 -0
- package/dist/src/actions/id-lens.js +51 -0
- package/dist/src/actions/id-lens.js.map +1 -0
- package/dist/src/actions/layout.d.ts +5 -0
- package/dist/src/actions/layout.js.map +1 -1
- package/dist/src/components/ox-entity360-dock.d.ts +18 -2
- package/dist/src/components/ox-entity360-dock.js +39 -17
- package/dist/src/components/ox-entity360-dock.js.map +1 -1
- package/dist/src/components/ox-id-lens.d.ts +58 -0
- package/dist/src/components/ox-id-lens.js +362 -0
- package/dist/src/components/ox-id-lens.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/layouts/ox-aside-bar.js +134 -6
- package/dist/src/layouts/ox-aside-bar.js.map +1 -1
- package/dist/test/entity360-companion.test.d.ts +1 -0
- package/dist/test/entity360-companion.test.js +127 -0
- package/dist/test/entity360-companion.test.js.map +1 -0
- package/dist/test/entity360-kind.test.js +136 -10
- package/dist/test/entity360-kind.test.js.map +1 -1
- package/dist/test/entity360-section.test.js +31 -1
- package/dist/test/entity360-section.test.js.map +1 -1
- package/dist/test/entity360.test.js +29 -6
- package/dist/test/entity360.test.js.map +1 -1
- package/dist/test/id-lens.test.d.ts +2 -0
- package/dist/test/id-lens.test.js +162 -0
- package/dist/test/id-lens.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -6
- package/test/entity360-companion.test.ts +110 -0
- package/test/entity360-kind.test.ts +145 -11
- package/test/entity360-section.test.ts +34 -1
- package/test/entity360.test.ts +31 -6
- package/test/id-lens.test.ts +198 -0
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -1136
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -63268
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import '@material/web/button/filled-button.js';
|
|
3
|
+
import '@material/web/button/text-button.js';
|
|
4
|
+
import '@material/web/icon/icon.js';
|
|
5
|
+
import '@operato/input/ox-input-barcode.js';
|
|
6
|
+
import '@operato/popup/ox-popup-actions.js';
|
|
7
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
8
|
+
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
9
|
+
import { i18next, localize } from '@operato/i18n';
|
|
10
|
+
import { askIdLensDoors, idLensHitToOpen, lookupText } from '../actions/id-lens.js';
|
|
11
|
+
import { entity360KindRegistered, openEntity360Kind } from '../actions/entity360-registry.js';
|
|
12
|
+
import { openPopup } from '../actions/layout.js';
|
|
13
|
+
/**
|
|
14
|
+
* The ID Lens - type or scan an identifier in the band, and its Entity 360 opens (ADR-0133).
|
|
15
|
+
*
|
|
16
|
+
* The product gives the doors (`.doors`, one per kind of identifier it keeps) and what the field says
|
|
17
|
+
* (`placeholder`). Everything about a kind - its name, its rules, who may read it, its states - is the product's;
|
|
18
|
+
* the lens only asks, folds and opens.
|
|
19
|
+
*
|
|
20
|
+
* Input is `ox-input-barcode`: typing, a keyboard scanner, or the camera. Nothing listens to the whole page - a
|
|
21
|
+
* scanner reaches the lens only while its field has focus, so a page's own scan listener never races it.
|
|
22
|
+
*/
|
|
23
|
+
let OxIdLens = class OxIdLens extends localize(i18next)(LitElement) {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
/** One door per kind of identifier the product keeps. */
|
|
27
|
+
this.doors = [];
|
|
28
|
+
/**
|
|
29
|
+
* What the field says before anything is typed - the product names its own kinds. A function is read at each render,
|
|
30
|
+
* so a band registered once still follows the language.
|
|
31
|
+
*/
|
|
32
|
+
this.placeholder = '';
|
|
33
|
+
this.busy = false;
|
|
34
|
+
this.said = '';
|
|
35
|
+
/**
|
|
36
|
+
* `ox-input-barcode` says `change` on Enter, after a camera scan, and also when the field loses focus with
|
|
37
|
+
* new text. Only the first two are a request to look - leaving the field is not. After Enter or a scan the
|
|
38
|
+
* focus is still in the lens; after a blur it has gone elsewhere.
|
|
39
|
+
*/
|
|
40
|
+
this.committed = () => {
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
if (this.matches(':focus-within'))
|
|
43
|
+
this.find();
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
render() {
|
|
48
|
+
const find = i18next.t('button.find', { defaultValue: 'find' });
|
|
49
|
+
const scan = i18next.t('button.scan', { defaultValue: 'scan' });
|
|
50
|
+
return html `
|
|
51
|
+
<span class="lens" role="search">
|
|
52
|
+
<button type="button" class="find" aria-label=${find} title=${find} @click=${() => this.find()}>
|
|
53
|
+
<md-icon aria-hidden="true">search</md-icon>
|
|
54
|
+
</button>
|
|
55
|
+
<button type="button" class="scan" aria-label=${scan} title=${scan} @click=${() => { var _a, _b; return (_b = (_a = this.field) === null || _a === void 0 ? void 0 : _a.input) === null || _b === void 0 ? void 0 : _b.focus(); }}>
|
|
56
|
+
<md-icon aria-hidden="true">qr_code_scanner</md-icon>
|
|
57
|
+
</button>
|
|
58
|
+
<ox-input-barcode placeholder=${typeof this.placeholder === 'function' ? this.placeholder() : this.placeholder} @change=${this.committed}></ox-input-barcode>
|
|
59
|
+
</span>
|
|
60
|
+
<span class="live" role="status" aria-live="polite">${this.said}</span>
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
async find() {
|
|
64
|
+
var _a;
|
|
65
|
+
const text = lookupText((_a = this.field) === null || _a === void 0 ? void 0 : _a.value);
|
|
66
|
+
if (!text || this.busy)
|
|
67
|
+
return;
|
|
68
|
+
this.busy = true;
|
|
69
|
+
this.said = i18next.t('text.id-lens-searching', { defaultValue: 'searching' });
|
|
70
|
+
try {
|
|
71
|
+
const outcome = await askIdLensDoors(this.doors, text);
|
|
72
|
+
this.said = '';
|
|
73
|
+
this.show(outcome);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
this.busy = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
show(outcome) {
|
|
80
|
+
const back = () => { var _a, _b; return (_b = (_a = this.field) === null || _a === void 0 ? void 0 : _a.input) === null || _b === void 0 ? void 0 : _b.focus(); };
|
|
81
|
+
const open = idLensHitToOpen(outcome, entity360KindRegistered);
|
|
82
|
+
if (open) {
|
|
83
|
+
openEntity360Kind(open.kind, { id: open.id, name: open.name }, back);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const popup = openPopup(html `<ox-id-lens-results
|
|
87
|
+
.outcome=${outcome}
|
|
88
|
+
.doors=${this.doors}
|
|
89
|
+
.onPick=${(hit) => {
|
|
90
|
+
var _a, _b;
|
|
91
|
+
(_a = popup === null || popup === void 0 ? void 0 : popup.close) === null || _a === void 0 ? void 0 : _a.call(popup);
|
|
92
|
+
if (entity360KindRegistered(hit.kind))
|
|
93
|
+
openEntity360Kind(hit.kind, { id: hit.id, name: hit.name }, back);
|
|
94
|
+
else
|
|
95
|
+
(_b = hit.open) === null || _b === void 0 ? void 0 : _b.call(hit);
|
|
96
|
+
}}
|
|
97
|
+
.onRetry=${() => {
|
|
98
|
+
var _a;
|
|
99
|
+
(_a = popup === null || popup === void 0 ? void 0 : popup.close) === null || _a === void 0 ? void 0 : _a.call(popup);
|
|
100
|
+
this.find();
|
|
101
|
+
}}
|
|
102
|
+
.onClose=${() => {
|
|
103
|
+
var _a;
|
|
104
|
+
(_a = popup === null || popup === void 0 ? void 0 : popup.close) === null || _a === void 0 ? void 0 : _a.call(popup);
|
|
105
|
+
back();
|
|
106
|
+
}}
|
|
107
|
+
></ox-id-lens-results>`, { backdrop: true, size: 'small', title: i18next.t('title.id-lens', { defaultValue: 'find by number' }) });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
OxIdLens.styles = [
|
|
111
|
+
css `
|
|
112
|
+
:host {
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
min-width: 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
A pill in the band. The width is set here: the barcode field's input asks for none (width 10px by design), so a
|
|
119
|
+
flex basis alone would shrink the pill to its minimum. 320px holds a long identifier at 12px; the band can
|
|
120
|
+
still narrow it.
|
|
121
|
+
*/
|
|
122
|
+
.lens {
|
|
123
|
+
display: inline-flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 6px;
|
|
126
|
+
width: 320px;
|
|
127
|
+
max-width: 100%;
|
|
128
|
+
box-sizing: border-box;
|
|
129
|
+
min-width: 120px;
|
|
130
|
+
padding: 3px 10px;
|
|
131
|
+
background: var(--md-sys-color-surface-container);
|
|
132
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
133
|
+
border-radius: 999px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.lens:focus-within {
|
|
137
|
+
outline: 2px solid var(--md-sys-color-primary);
|
|
138
|
+
outline-offset: 2px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/*
|
|
142
|
+
The pill is the field's outline: the component's own border, fill and form height would draw a taller box
|
|
143
|
+
inside it. 20px keeps the pill at the band's 28px.
|
|
144
|
+
*/
|
|
145
|
+
ox-input-barcode {
|
|
146
|
+
flex: 1;
|
|
147
|
+
min-width: 0;
|
|
148
|
+
height: 20px;
|
|
149
|
+
--ox-input-border: none;
|
|
150
|
+
--ox-input-background: transparent;
|
|
151
|
+
--ox-input-color: var(--md-sys-color-on-surface);
|
|
152
|
+
--md-sys-typescale-label-large-size: 12px;
|
|
153
|
+
/*
|
|
154
|
+
The camera button's barcode at the front mark's height: its bars fill 22 of the PNG's 24px, the 17px symbol's
|
|
155
|
+
glyph is about 14px - so 15px tall.
|
|
156
|
+
*/
|
|
157
|
+
--barcodescan-input-button-icon-size: auto 15px;
|
|
158
|
+
--barcodescan-input-button-width: 20px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
One leading mark, chosen by whether the field found a camera. With one, the field draws its own camera button at
|
|
163
|
+
the end, so the front is the magnifier that finds. Without one, the front is the QR mark - it says a number is
|
|
164
|
+
scanned here and puts the cursor in the field for a keyboard scanner.
|
|
165
|
+
*/
|
|
166
|
+
.lens:has(> ox-input-barcode[scannable]) .scan,
|
|
167
|
+
.lens:not(:has(> ox-input-barcode[scannable])) .find {
|
|
168
|
+
display: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
button {
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
flex: none;
|
|
174
|
+
padding: 0;
|
|
175
|
+
border: none;
|
|
176
|
+
background: none;
|
|
177
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
button:hover {
|
|
182
|
+
color: var(--md-sys-color-on-surface);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
md-icon {
|
|
186
|
+
--md-icon-size: 17px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.live {
|
|
190
|
+
position: absolute;
|
|
191
|
+
width: 1px;
|
|
192
|
+
height: 1px;
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
clip-path: inset(50%);
|
|
195
|
+
white-space: nowrap;
|
|
196
|
+
}
|
|
197
|
+
`
|
|
198
|
+
];
|
|
199
|
+
__decorate([
|
|
200
|
+
property({ attribute: false })
|
|
201
|
+
], OxIdLens.prototype, "doors", void 0);
|
|
202
|
+
__decorate([
|
|
203
|
+
property({ attribute: false })
|
|
204
|
+
], OxIdLens.prototype, "placeholder", void 0);
|
|
205
|
+
__decorate([
|
|
206
|
+
state()
|
|
207
|
+
], OxIdLens.prototype, "said", void 0);
|
|
208
|
+
__decorate([
|
|
209
|
+
query('ox-input-barcode')
|
|
210
|
+
], OxIdLens.prototype, "field", void 0);
|
|
211
|
+
OxIdLens = __decorate([
|
|
212
|
+
customElement('ox-id-lens')
|
|
213
|
+
], OxIdLens);
|
|
214
|
+
export { OxIdLens };
|
|
215
|
+
/**
|
|
216
|
+
* What a look-up found when it cannot just open one 360: several hits to choose from, none, or a door that
|
|
217
|
+
* did not answer. The first hit is never picked for the person (ADR-0133 2-2).
|
|
218
|
+
*/
|
|
219
|
+
let OxIdLensResults = class OxIdLensResults extends localize(i18next)(LitElement) {
|
|
220
|
+
constructor() {
|
|
221
|
+
super(...arguments);
|
|
222
|
+
this.doors = [];
|
|
223
|
+
}
|
|
224
|
+
render() {
|
|
225
|
+
const outcome = this.outcome;
|
|
226
|
+
if (!outcome)
|
|
227
|
+
return html ``;
|
|
228
|
+
return html `
|
|
229
|
+
${outcome.hits.length
|
|
230
|
+
? html `<p>${i18next.t('text.id-lens-choose', { value: outcome.text, count: outcome.hits.length, defaultValue: '{count} matches for {value} - choose one' })}</p>
|
|
231
|
+
<ul>
|
|
232
|
+
${outcome.hits.map(hit => html `<li>${this.hit(hit)}</li>`)}
|
|
233
|
+
</ul>`
|
|
234
|
+
: html `<p>${i18next.t('text.id-lens-none', { value: outcome.text, defaultValue: 'Nothing is numbered {value}.' })}</p>`}
|
|
235
|
+
${outcome.forbidden.length
|
|
236
|
+
? html `<p class="sub">
|
|
237
|
+
${i18next.t('text.id-lens-forbidden', {
|
|
238
|
+
kinds: outcome.forbidden.map(kind => this.kindText(kind)).join(', '),
|
|
239
|
+
defaultValue: 'Not searched - you may not view: {kinds}'
|
|
240
|
+
})}
|
|
241
|
+
</p>`
|
|
242
|
+
: nothing}
|
|
243
|
+
${outcome.failed.map(one => html `<p class="failed">
|
|
244
|
+
${i18next.t('text.id-lens-failed', { kind: this.kindText(one.kind), defaultValue: 'Could not read {kind}' })}${one.message ? ` - ${one.message}` : ''}
|
|
245
|
+
</p>`)}
|
|
246
|
+
|
|
247
|
+
<ox-popup-actions>
|
|
248
|
+
${outcome.failed.length
|
|
249
|
+
? html `<md-text-button slot="cancel" @click=${() => { var _a; return (_a = this.onRetry) === null || _a === void 0 ? void 0 : _a.call(this); }}>${i18next.t('button.retry', { defaultValue: 'retry' })}</md-text-button>`
|
|
250
|
+
: nothing}
|
|
251
|
+
<md-filled-button slot="confirm" @click=${() => { var _a; return (_a = this.onClose) === null || _a === void 0 ? void 0 : _a.call(this); }}>${i18next.t('button.close', { defaultValue: 'close' })}</md-filled-button>
|
|
252
|
+
</ox-popup-actions>
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
/** The product's word for a kind; the kind name itself only when no door names it. */
|
|
256
|
+
kindText(kind) {
|
|
257
|
+
var _a, _b;
|
|
258
|
+
return (_b = (_a = this.doors.find(door => door.kind === kind)) === null || _a === void 0 ? void 0 : _a.label()) !== null && _b !== void 0 ? _b : kind;
|
|
259
|
+
}
|
|
260
|
+
hit(hit) {
|
|
261
|
+
var _a;
|
|
262
|
+
const body = html `<span class="kind">${this.kindText(hit.kind)}</span>
|
|
263
|
+
<span class="name">${hit.name}</span>
|
|
264
|
+
<span class="kind">${(_a = hit.status) !== null && _a !== void 0 ? _a : ''}</span>
|
|
265
|
+
${hit.context ? html `<span class="sub">${hit.context}</span>` : nothing}`;
|
|
266
|
+
/*
|
|
267
|
+
* A kind with a 360 opens it; one without opens the work screen its product named. A kind with neither is not a
|
|
268
|
+
* dead link - it says so and stays text (ADR-0067).
|
|
269
|
+
*/
|
|
270
|
+
return entity360KindRegistered(hit.kind) || hit.open
|
|
271
|
+
? html `<button type="button" class="hit" @click=${() => { var _a; return (_a = this.onPick) === null || _a === void 0 ? void 0 : _a.call(this, hit); }}>${body}</button>`
|
|
272
|
+
: html `<div class="hit">${body}<span class="sub">${i18next.t('text.entity360-not-yet', { defaultValue: 'there is no 360 for this kind yet.' })}</span></div>`;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
OxIdLensResults.styles = [
|
|
276
|
+
css `
|
|
277
|
+
:host {
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
gap: 12px;
|
|
281
|
+
padding: 16px;
|
|
282
|
+
font: 14px/1.5 sans-serif;
|
|
283
|
+
color: var(--md-sys-color-on-surface);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
ul {
|
|
287
|
+
list-style: none;
|
|
288
|
+
margin: 0;
|
|
289
|
+
padding: 0;
|
|
290
|
+
display: flex;
|
|
291
|
+
flex-direction: column;
|
|
292
|
+
gap: 6px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.hit {
|
|
296
|
+
display: grid;
|
|
297
|
+
grid-template-columns: max-content 1fr max-content;
|
|
298
|
+
align-items: baseline;
|
|
299
|
+
gap: 2px 10px;
|
|
300
|
+
width: 100%;
|
|
301
|
+
padding: 8px 10px;
|
|
302
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
303
|
+
border-radius: 8px;
|
|
304
|
+
background: none;
|
|
305
|
+
color: inherit;
|
|
306
|
+
font: inherit;
|
|
307
|
+
text-align: start;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
button.hit {
|
|
311
|
+
cursor: pointer;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
button.hit:hover,
|
|
315
|
+
button.hit:focus-visible {
|
|
316
|
+
background: var(--md-sys-color-surface-container-high);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.kind,
|
|
320
|
+
.sub {
|
|
321
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
322
|
+
font-size: 12px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.name {
|
|
326
|
+
overflow-wrap: anywhere;
|
|
327
|
+
font-weight: 600;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.sub {
|
|
331
|
+
grid-column: 2 / -1;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
p {
|
|
335
|
+
margin: 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.failed {
|
|
339
|
+
color: var(--md-sys-color-error);
|
|
340
|
+
}
|
|
341
|
+
`
|
|
342
|
+
];
|
|
343
|
+
__decorate([
|
|
344
|
+
property({ attribute: false })
|
|
345
|
+
], OxIdLensResults.prototype, "outcome", void 0);
|
|
346
|
+
__decorate([
|
|
347
|
+
property({ attribute: false })
|
|
348
|
+
], OxIdLensResults.prototype, "doors", void 0);
|
|
349
|
+
__decorate([
|
|
350
|
+
property({ attribute: false })
|
|
351
|
+
], OxIdLensResults.prototype, "onPick", void 0);
|
|
352
|
+
__decorate([
|
|
353
|
+
property({ attribute: false })
|
|
354
|
+
], OxIdLensResults.prototype, "onRetry", void 0);
|
|
355
|
+
__decorate([
|
|
356
|
+
property({ attribute: false })
|
|
357
|
+
], OxIdLensResults.prototype, "onClose", void 0);
|
|
358
|
+
OxIdLensResults = __decorate([
|
|
359
|
+
customElement('ox-id-lens-results')
|
|
360
|
+
], OxIdLensResults);
|
|
361
|
+
export { OxIdLensResults };
|
|
362
|
+
//# sourceMappingURL=ox-id-lens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ox-id-lens.js","sourceRoot":"","sources":["../../../src/components/ox-id-lens.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/icon/icon.js'\nimport '@operato/input/ox-input-barcode.js'\nimport '@operato/popup/ox-popup-actions.js'\n\nimport { LitElement, css, html, nothing, type TemplateResult } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\n\nimport { askIdLensDoors, idLensHitToOpen, lookupText, type IdLensDoor, type IdLensHit, type IdLensOutcome } from '../actions/id-lens.js'\nimport { entity360KindRegistered, openEntity360Kind } from '../actions/entity360-registry.js'\nimport { openPopup } from '../actions/layout.js'\n\n/**\n * The ID Lens - type or scan an identifier in the band, and its Entity 360 opens (ADR-0133).\n *\n * The product gives the doors (`.doors`, one per kind of identifier it keeps) and what the field says\n * (`placeholder`). Everything about a kind - its name, its rules, who may read it, its states - is the product's;\n * the lens only asks, folds and opens.\n *\n * Input is `ox-input-barcode`: typing, a keyboard scanner, or the camera. Nothing listens to the whole page - a\n * scanner reaches the lens only while its field has focus, so a page's own scan listener never races it.\n */\n@customElement('ox-id-lens')\nexport class OxIdLens extends localize(i18next)(LitElement) {\n static styles = [\n css`\n :host {\n display: inline-flex;\n min-width: 0;\n }\n\n /*\n A pill in the band. The width is set here: the barcode field's input asks for none (width 10px by design), so a\n flex basis alone would shrink the pill to its minimum. 320px holds a long identifier at 12px; the band can\n still narrow it.\n */\n .lens {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n width: 320px;\n max-width: 100%;\n box-sizing: border-box;\n min-width: 120px;\n padding: 3px 10px;\n background: var(--md-sys-color-surface-container);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 999px;\n }\n\n .lens:focus-within {\n outline: 2px solid var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n\n /*\n The pill is the field's outline: the component's own border, fill and form height would draw a taller box\n inside it. 20px keeps the pill at the band's 28px.\n */\n ox-input-barcode {\n flex: 1;\n min-width: 0;\n height: 20px;\n --ox-input-border: none;\n --ox-input-background: transparent;\n --ox-input-color: var(--md-sys-color-on-surface);\n --md-sys-typescale-label-large-size: 12px;\n /*\n The camera button's barcode at the front mark's height: its bars fill 22 of the PNG's 24px, the 17px symbol's\n glyph is about 14px - so 15px tall.\n */\n --barcodescan-input-button-icon-size: auto 15px;\n --barcodescan-input-button-width: 20px;\n }\n\n /*\n One leading mark, chosen by whether the field found a camera. With one, the field draws its own camera button at\n the end, so the front is the magnifier that finds. Without one, the front is the QR mark - it says a number is\n scanned here and puts the cursor in the field for a keyboard scanner.\n */\n .lens:has(> ox-input-barcode[scannable]) .scan,\n .lens:not(:has(> ox-input-barcode[scannable])) .find {\n display: none;\n }\n\n button {\n display: inline-flex;\n flex: none;\n padding: 0;\n border: none;\n background: none;\n color: var(--md-sys-color-on-surface-variant);\n cursor: pointer;\n }\n\n button:hover {\n color: var(--md-sys-color-on-surface);\n }\n\n md-icon {\n --md-icon-size: 17px;\n }\n\n .live {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n }\n `\n ]\n\n /** One door per kind of identifier the product keeps. */\n @property({ attribute: false }) doors: IdLensDoor[] = []\n /**\n * What the field says before anything is typed - the product names its own kinds. A function is read at each render,\n * so a band registered once still follows the language.\n */\n @property({ attribute: false }) placeholder: string | (() => string) = ''\n\n private busy = false\n @state() private said = ''\n @query('ox-input-barcode') private field?: HTMLElement & { value?: string; input?: HTMLInputElement }\n\n render(): TemplateResult {\n const find = i18next.t('button.find', { defaultValue: 'find' })\n const scan = i18next.t('button.scan', { defaultValue: 'scan' })\n\n return html`\n <span class=\"lens\" role=\"search\">\n <button type=\"button\" class=\"find\" aria-label=${find} title=${find} @click=${() => this.find()}>\n <md-icon aria-hidden=\"true\">search</md-icon>\n </button>\n <button type=\"button\" class=\"scan\" aria-label=${scan} title=${scan} @click=${() => this.field?.input?.focus()}>\n <md-icon aria-hidden=\"true\">qr_code_scanner</md-icon>\n </button>\n <ox-input-barcode placeholder=${typeof this.placeholder === 'function' ? this.placeholder() : this.placeholder} @change=${this.committed}></ox-input-barcode>\n </span>\n <span class=\"live\" role=\"status\" aria-live=\"polite\">${this.said}</span>\n `\n }\n\n /**\n * `ox-input-barcode` says `change` on Enter, after a camera scan, and also when the field loses focus with\n * new text. Only the first two are a request to look - leaving the field is not. After Enter or a scan the\n * focus is still in the lens; after a blur it has gone elsewhere.\n */\n private committed = () => {\n setTimeout(() => {\n if (this.matches(':focus-within')) this.find()\n })\n }\n\n private async find() {\n const text = lookupText(this.field?.value)\n if (!text || this.busy) return\n\n this.busy = true\n this.said = i18next.t('text.id-lens-searching', { defaultValue: 'searching' })\n\n try {\n const outcome = await askIdLensDoors(this.doors, text)\n this.said = ''\n this.show(outcome)\n } finally {\n this.busy = false\n }\n }\n\n private show(outcome: IdLensOutcome) {\n const back = () => this.field?.input?.focus()\n const open = idLensHitToOpen(outcome, entity360KindRegistered)\n\n if (open) {\n openEntity360Kind(open.kind, { id: open.id, name: open.name }, back)\n return\n }\n\n const popup: any = openPopup(\n html`<ox-id-lens-results\n .outcome=${outcome}\n .doors=${this.doors}\n .onPick=${(hit: IdLensHit) => {\n popup?.close?.()\n if (entity360KindRegistered(hit.kind)) openEntity360Kind(hit.kind, { id: hit.id, name: hit.name }, back)\n else hit.open?.()\n }}\n .onRetry=${() => {\n popup?.close?.()\n this.find()\n }}\n .onClose=${() => {\n popup?.close?.()\n back()\n }}\n ></ox-id-lens-results>`,\n { backdrop: true, size: 'small', title: i18next.t('title.id-lens', { defaultValue: 'find by number' }) }\n )\n }\n}\n\n/**\n * What a look-up found when it cannot just open one 360: several hits to choose from, none, or a door that\n * did not answer. The first hit is never picked for the person (ADR-0133 2-2).\n */\n@customElement('ox-id-lens-results')\nexport class OxIdLensResults extends localize(i18next)(LitElement) {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n font: 14px/1.5 sans-serif;\n color: var(--md-sys-color-on-surface);\n }\n\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .hit {\n display: grid;\n grid-template-columns: max-content 1fr max-content;\n align-items: baseline;\n gap: 2px 10px;\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 8px;\n background: none;\n color: inherit;\n font: inherit;\n text-align: start;\n }\n\n button.hit {\n cursor: pointer;\n }\n\n button.hit:hover,\n button.hit:focus-visible {\n background: var(--md-sys-color-surface-container-high);\n }\n\n .kind,\n .sub {\n color: var(--md-sys-color-on-surface-variant);\n font-size: 12px;\n }\n\n .name {\n overflow-wrap: anywhere;\n font-weight: 600;\n }\n\n .sub {\n grid-column: 2 / -1;\n }\n\n p {\n margin: 0;\n }\n\n .failed {\n color: var(--md-sys-color-error);\n }\n `\n ]\n\n @property({ attribute: false }) outcome?: IdLensOutcome\n @property({ attribute: false }) doors: IdLensDoor[] = []\n @property({ attribute: false }) onPick?: (hit: IdLensHit) => void\n @property({ attribute: false }) onRetry?: () => void\n @property({ attribute: false }) onClose?: () => void\n\n render(): TemplateResult {\n const outcome = this.outcome\n if (!outcome) return html``\n\n return html`\n ${outcome.hits.length\n ? html`<p>${i18next.t('text.id-lens-choose', { value: outcome.text, count: outcome.hits.length, defaultValue: '{count} matches for {value} - choose one' })}</p>\n <ul>\n ${outcome.hits.map(hit => html`<li>${this.hit(hit)}</li>`)}\n </ul>`\n : html`<p>${i18next.t('text.id-lens-none', { value: outcome.text, defaultValue: 'Nothing is numbered {value}.' })}</p>`}\n ${outcome.forbidden.length\n ? html`<p class=\"sub\">\n ${i18next.t('text.id-lens-forbidden', {\n kinds: outcome.forbidden.map(kind => this.kindText(kind)).join(', '),\n defaultValue: 'Not searched - you may not view: {kinds}'\n })}\n </p>`\n : nothing}\n ${outcome.failed.map(\n one =>\n html`<p class=\"failed\">\n ${i18next.t('text.id-lens-failed', { kind: this.kindText(one.kind), defaultValue: 'Could not read {kind}' })}${one.message ? ` - ${one.message}` : ''}\n </p>`\n )}\n\n <ox-popup-actions>\n ${outcome.failed.length\n ? html`<md-text-button slot=\"cancel\" @click=${() => this.onRetry?.()}>${i18next.t('button.retry', { defaultValue: 'retry' })}</md-text-button>`\n : nothing}\n <md-filled-button slot=\"confirm\" @click=${() => this.onClose?.()}>${i18next.t('button.close', { defaultValue: 'close' })}</md-filled-button>\n </ox-popup-actions>\n `\n }\n\n /** The product's word for a kind; the kind name itself only when no door names it. */\n private kindText(kind: string): string {\n return this.doors.find(door => door.kind === kind)?.label() ?? kind\n }\n\n private hit(hit: IdLensHit): TemplateResult {\n const body = html`<span class=\"kind\">${this.kindText(hit.kind)}</span>\n <span class=\"name\">${hit.name}</span>\n <span class=\"kind\">${hit.status ?? ''}</span>\n ${hit.context ? html`<span class=\"sub\">${hit.context}</span>` : nothing}`\n\n /*\n * A kind with a 360 opens it; one without opens the work screen its product named. A kind with neither is not a\n * dead link - it says so and stays text (ADR-0067).\n */\n return entity360KindRegistered(hit.kind) || hit.open\n ? html`<button type=\"button\" class=\"hit\" @click=${() => this.onPick?.(hit)}>${body}</button>`\n : html`<div class=\"hit\">${body}<span class=\"sub\">${i18next.t('text.entity360-not-yet', { defaultValue: 'there is no 360 for this kind yet.' })}</span></div>`\n }\n}\n"]}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -18,4 +18,5 @@ export * from './actions/entity360.js';
|
|
|
18
18
|
export * from './actions/entity360-registry.js';
|
|
19
19
|
export * from './actions/entity360-door.js';
|
|
20
20
|
export * from './actions/entity360-name.js';
|
|
21
|
+
export * from './actions/id-lens.js';
|
|
21
22
|
export * from './components/ox-entity360-section.js';
|
package/dist/src/index.js
CHANGED
|
@@ -17,5 +17,6 @@ export * from './actions/entity360.js';
|
|
|
17
17
|
export * from './actions/entity360-registry.js';
|
|
18
18
|
export * from './actions/entity360-door.js';
|
|
19
19
|
export * from './actions/entity360-name.js';
|
|
20
|
+
export * from './actions/id-lens.js';
|
|
20
21
|
export * from './components/ox-entity360-section.js';
|
|
21
22
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAA;AAIzB,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAE/C,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,sCAAsC,CAAA","sourcesContent":["import './initializer.js'\n\nexport type { PopupHandle, PopupOptions } from '@operato/popup'\n\nexport * from './layouts/ox-snack-bar.js'\nexport * from './layouts/ox-header-bar.js'\nexport * from './layouts/ox-nav-bar.js'\nexport * from './layouts/ox-aside-bar.js'\nexport * from './layouts/ox-footer-bar.js'\nexport * from './layouts/ox-page-header-bar.js'\nexport * from './layouts/ox-page-footer-bar.js'\n\nexport * from './actions/layout.js'\nexport * from './actions/snackbar.js'\n\nexport * from './components/ox-split-pane.js'\nexport * from './components/ox-entity360-dock.js'\nexport * from './components/ox-three-pane.js'\nexport * from './components/ox-canvas-bar.js'\nexport * from './components/ox-url-tabs.js'\nexport * from './actions/entity360.js'\nexport * from './actions/entity360-registry.js'\nexport * from './actions/entity360-door.js'\nexport * from './actions/entity360-name.js'\nexport * from './components/ox-entity360-section.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAA;AAIzB,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAE/C,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mCAAmC,CAAA;AACjD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,sBAAsB,CAAA;AACpC,cAAc,sCAAsC,CAAA","sourcesContent":["import './initializer.js'\n\nexport type { PopupHandle, PopupOptions } from '@operato/popup'\n\nexport * from './layouts/ox-snack-bar.js'\nexport * from './layouts/ox-header-bar.js'\nexport * from './layouts/ox-nav-bar.js'\nexport * from './layouts/ox-aside-bar.js'\nexport * from './layouts/ox-footer-bar.js'\nexport * from './layouts/ox-page-header-bar.js'\nexport * from './layouts/ox-page-footer-bar.js'\n\nexport * from './actions/layout.js'\nexport * from './actions/snackbar.js'\n\nexport * from './components/ox-split-pane.js'\nexport * from './components/ox-entity360-dock.js'\nexport * from './components/ox-three-pane.js'\nexport * from './components/ox-canvas-bar.js'\nexport * from './components/ox-url-tabs.js'\nexport * from './actions/entity360.js'\nexport * from './actions/entity360-registry.js'\nexport * from './actions/entity360-door.js'\nexport * from './actions/entity360-name.js'\nexport * from './actions/id-lens.js'\nexport * from './components/ox-entity360-section.js'\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import '@operato/popup/ox-floating-overlay.js';
|
|
3
3
|
import '../components/ox-resize-splitter.js';
|
|
4
|
-
import { css, html, LitElement } from 'lit';
|
|
4
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
5
5
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
6
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
6
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
7
7
|
import { LayoutController } from '@operato/shell';
|
|
8
|
-
import {
|
|
8
|
+
import { i18next } from '@operato/i18n';
|
|
9
|
+
import { ButtonStyles, ScrollbarStyles } from '@operato/styles';
|
|
9
10
|
import { VIEWPART_LEVEL } from '../actions/layout.js';
|
|
10
11
|
let AsideBar = class AsideBar extends LitElement {
|
|
11
12
|
constructor() {
|
|
@@ -13,8 +14,92 @@ let AsideBar = class AsideBar extends LitElement {
|
|
|
13
14
|
this._layout = new LayoutController(this);
|
|
14
15
|
this.fullbleed = false;
|
|
15
16
|
this._startWidth = 0;
|
|
17
|
+
this.pane = 'main';
|
|
18
|
+
this.compact = matchMedia('(max-width: 1023.98px)').matches;
|
|
19
|
+
this.media = matchMedia('(max-width: 1023.98px)');
|
|
20
|
+
this.shared = false;
|
|
21
|
+
this.revealCompanion = false;
|
|
22
|
+
this.onPresentation = (event) => {
|
|
23
|
+
this.expandedDock = event.composedPath()[0];
|
|
24
|
+
this.requestUpdate();
|
|
25
|
+
};
|
|
26
|
+
this.onMedia = () => { this.compact = this.media.matches; };
|
|
27
|
+
this.onFocus = (event) => {
|
|
28
|
+
if (!this.shared)
|
|
29
|
+
return;
|
|
30
|
+
const path = event.composedPath();
|
|
31
|
+
if (path.some(one => { var _a, _b; return (_b = (_a = one).hasAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, 'workspace-companion'); }))
|
|
32
|
+
this.pane = 'companion';
|
|
33
|
+
else if (path.some(one => { var _a, _b; return (_b = (_a = one).hasAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, 'workspace-main'); }))
|
|
34
|
+
this.pane = 'main';
|
|
35
|
+
};
|
|
36
|
+
this.onKey = (event) => {
|
|
37
|
+
var _a, _b, _c;
|
|
38
|
+
if (!this.shared || event.defaultPrevented)
|
|
39
|
+
return;
|
|
40
|
+
// An actual overlay above the workspace owns its keys. The supporting dock is itself an overlay entry.
|
|
41
|
+
if (((_a = history.state) === null || _a === void 0 ? void 0 : _a.overlay) && history.state.overlay.name !== this.companionName)
|
|
42
|
+
return;
|
|
43
|
+
if (event.key === 'Escape') {
|
|
44
|
+
if (event.composedPath().includes(this.expandedDock)) {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
(_c = (_b = this.expandedDock) === null || _b === void 0 ? void 0 : _b.view) === null || _c === void 0 ? void 0 : _c.onClose();
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (event.key !== 'Tab')
|
|
51
|
+
return;
|
|
52
|
+
const walk = (root) => Array.from(root.querySelectorAll('*'))
|
|
53
|
+
.flatMap(el => [el, ...(el.shadowRoot ? walk(el.shadowRoot) : [])]);
|
|
54
|
+
// Follow visual order: switch, primary work, supporting conversation. No background controls are reached.
|
|
55
|
+
const roots = ['.workspace-switch', '[workspace-main]', '[workspace-companion]'].map(selector => this.renderRoot.querySelector(selector)).filter(Boolean);
|
|
56
|
+
const controls = roots.flatMap(root => root.hidden ? [] : walk(root)).filter(el => el.matches('button,a[href],input,select,textarea,summary,[tabindex]') && el.tabIndex >= 0 &&
|
|
57
|
+
!el.hasAttribute('disabled') && !el.closest('[inert]') && el.getClientRects().length && getComputedStyle(el).visibility !== 'hidden');
|
|
58
|
+
if (!controls.length)
|
|
59
|
+
return;
|
|
60
|
+
const index = controls.indexOf(event.composedPath()[0]);
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
controls[index < 0 ? (event.shiftKey ? controls.length - 1 : 0) : (index + (event.shiftKey ? -1 : 1) + controls.length) % controls.length].focus();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
connectedCallback() {
|
|
66
|
+
super.connectedCallback();
|
|
67
|
+
this.addEventListener('entity360-presentation', this.onPresentation);
|
|
68
|
+
this.renderRoot.addEventListener('focusin', this.onFocus);
|
|
69
|
+
this.media.addEventListener('change', this.onMedia);
|
|
70
|
+
document.addEventListener('keydown', this.onKey);
|
|
71
|
+
}
|
|
72
|
+
disconnectedCallback() {
|
|
73
|
+
this.expandedDock && (this.expandedDock.contained = false);
|
|
74
|
+
this.removeEventListener('entity360-presentation', this.onPresentation);
|
|
75
|
+
this.renderRoot.removeEventListener('focusin', this.onFocus);
|
|
76
|
+
this.media.removeEventListener('change', this.onMedia);
|
|
77
|
+
document.removeEventListener('keydown', this.onKey);
|
|
78
|
+
super.disconnectedCallback();
|
|
79
|
+
}
|
|
80
|
+
updated() {
|
|
81
|
+
var _a, _b, _c;
|
|
82
|
+
this.toggleAttribute('shared-fullscreen', this.shared);
|
|
83
|
+
if (this.shared) {
|
|
84
|
+
this.setAttribute('role', 'dialog');
|
|
85
|
+
this.setAttribute('aria-modal', 'true');
|
|
86
|
+
this.setAttribute('aria-label', (_c = (_b = (_a = this.expandedDock) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.title) !== null && _c !== void 0 ? _c : '');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.removeAttribute('role');
|
|
90
|
+
this.removeAttribute('aria-modal');
|
|
91
|
+
this.removeAttribute('aria-label');
|
|
92
|
+
}
|
|
93
|
+
if (this.expandedDock)
|
|
94
|
+
this.expandedDock.contained = this.shared;
|
|
95
|
+
if (this.revealCompanion) {
|
|
96
|
+
this.revealCompanion = false;
|
|
97
|
+
this.pane = 'companion';
|
|
98
|
+
void this.updateComplete.then(() => { var _a; return (_a = this.renderRoot.querySelectorAll('.workspace-switch button')[1]) === null || _a === void 0 ? void 0 : _a.focus(); });
|
|
99
|
+
}
|
|
16
100
|
}
|
|
17
101
|
render() {
|
|
102
|
+
var _a, _b, _c;
|
|
18
103
|
var viewparts = this._layout.viewparts;
|
|
19
104
|
var asidebars = Object.keys(viewparts)
|
|
20
105
|
.map(name => {
|
|
@@ -28,7 +113,16 @@ let AsideBar = class AsideBar extends LitElement {
|
|
|
28
113
|
...asidebars.filter(viewpart => viewpart.level == VIEWPART_LEVEL.TOPMOST),
|
|
29
114
|
...asidebars.filter(viewpart => viewpart.level !== VIEWPART_LEVEL.TOPMOST)
|
|
30
115
|
];
|
|
116
|
+
const companion = asidebars.find(one => one.show && !one.hovering && one.expansionCompanion);
|
|
117
|
+
const main = asidebars.find(one => one.show && !one.hovering && one.name === 'entity360');
|
|
118
|
+
this.shared = !!(main && companion && ((_a = this.expandedDock) === null || _a === void 0 ? void 0 : _a.isConnected) && this.expandedDock.fullscreen);
|
|
119
|
+
this.revealCompanion = this.shared && this.compact && !!companion && this.companionName !== companion.name;
|
|
120
|
+
this.companionName = companion === null || companion === void 0 ? void 0 : companion.name;
|
|
31
121
|
return html `
|
|
122
|
+
${this.shared ? html `<nav class="workspace-switch">
|
|
123
|
+
<button quiet small aria-pressed=${this.pane === 'main'} @click=${() => this.pane = 'main'}>${(_c = (_b = this.expandedDock) === null || _b === void 0 ? void 0 : _b.view) === null || _c === void 0 ? void 0 : _c.title}</button>
|
|
124
|
+
<button quiet small aria-pressed=${this.pane === 'companion'} @click=${() => this.pane = 'companion'}>${i18next.t(companion.expansionCompanion.labelKey, { defaultValue: companion.expansionCompanion.defaultLabel })}</button>
|
|
125
|
+
</nav>` : nothing}
|
|
32
126
|
${asidebars.map(asidebar => !asidebar.show
|
|
33
127
|
? html ``
|
|
34
128
|
: asidebar.hovering
|
|
@@ -53,8 +147,12 @@ let AsideBar = class AsideBar extends LitElement {
|
|
|
53
147
|
>
|
|
54
148
|
`
|
|
55
149
|
: html `
|
|
56
|
-
<div asidebar
|
|
57
|
-
|
|
150
|
+
<div asidebar
|
|
151
|
+
?workspace-main=${asidebar.name === (main === null || main === void 0 ? void 0 : main.name)}
|
|
152
|
+
?workspace-companion=${asidebar.name === (companion === null || companion === void 0 ? void 0 : companion.name)}
|
|
153
|
+
?hidden=${this.shared && (asidebar.name !== (main === null || main === void 0 ? void 0 : main.name) && asidebar.name !== (companion === null || companion === void 0 ? void 0 : companion.name) || this.compact && (this.pane === 'main' ? asidebar.name !== (main === null || main === void 0 ? void 0 : main.name) : asidebar.name !== (companion === null || companion === void 0 ? void 0 : companion.name)))}
|
|
154
|
+
>${asidebar.template}</div>
|
|
155
|
+
${asidebar.resizable && !this.shared
|
|
58
156
|
? html `
|
|
59
157
|
<ox-resize-splitter
|
|
60
158
|
@splitter-dragstart=${(e) => this.resizeStart(e)}
|
|
@@ -81,7 +179,7 @@ let AsideBar = class AsideBar extends LitElement {
|
|
|
81
179
|
}
|
|
82
180
|
};
|
|
83
181
|
AsideBar.styles = [
|
|
84
|
-
ScrollbarStyles,
|
|
182
|
+
ScrollbarStyles, ButtonStyles,
|
|
85
183
|
css `
|
|
86
184
|
:host {
|
|
87
185
|
display: flex;
|
|
@@ -91,6 +189,27 @@ AsideBar.styles = [
|
|
|
91
189
|
position: relative;
|
|
92
190
|
}
|
|
93
191
|
|
|
192
|
+
:host([shared-fullscreen]) {
|
|
193
|
+
position: fixed; inset: 0; width: 100vw; height: 100dvh; z-index: 901;
|
|
194
|
+
display: grid; grid-template-columns: minmax(0, 1fr) 420px; grid-template-rows: minmax(0, 1fr);
|
|
195
|
+
background: var(--md-sys-color-surface, white); color: var(--md-sys-color-on-surface, #222);
|
|
196
|
+
}
|
|
197
|
+
:host([shared-fullscreen]) [asidebar] { min-width: 0; min-height: 0; overflow: hidden; }
|
|
198
|
+
:host([shared-fullscreen]) [workspace-main] { grid-column: 1; grid-row: 1; }
|
|
199
|
+
:host([shared-fullscreen]) [workspace-companion] { grid-column: 2; grid-row: 1; }
|
|
200
|
+
:host([shared-fullscreen]) [workspace-companion] > * {
|
|
201
|
+
position: relative; inset: auto; width: 100%; max-width: none; height: 100%; z-index: auto;
|
|
202
|
+
}
|
|
203
|
+
[hidden] { display: none !important; }
|
|
204
|
+
.workspace-switch { display: none; }
|
|
205
|
+
@media (max-width: 1023.98px) {
|
|
206
|
+
:host([shared-fullscreen]) { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
|
|
207
|
+
:host([shared-fullscreen]) [workspace-main], :host([shared-fullscreen]) [workspace-companion] { grid-column: 1; grid-row: 2; }
|
|
208
|
+
.workspace-switch { display: flex; gap: var(--spacing-small, 4px); padding: var(--spacing-small, 4px); min-width: 0; border-bottom: 1px solid var(--ui-line, #ccc); }
|
|
209
|
+
.workspace-switch button { min-width: 0; max-width: 50%; white-space: normal; overflow-wrap: anywhere; }
|
|
210
|
+
.workspace-switch button[aria-pressed=true] { background: var(--md-sys-color-secondary-container, #ddd); }
|
|
211
|
+
}
|
|
212
|
+
|
|
94
213
|
*[asidebar] {
|
|
95
214
|
display: block;
|
|
96
215
|
overflow-y: auto;
|
|
@@ -100,6 +219,15 @@ AsideBar.styles = [
|
|
|
100
219
|
__decorate([
|
|
101
220
|
property({ type: Boolean, attribute: 'fullbleed' })
|
|
102
221
|
], AsideBar.prototype, "fullbleed", void 0);
|
|
222
|
+
__decorate([
|
|
223
|
+
state()
|
|
224
|
+
], AsideBar.prototype, "expandedDock", void 0);
|
|
225
|
+
__decorate([
|
|
226
|
+
state()
|
|
227
|
+
], AsideBar.prototype, "pane", void 0);
|
|
228
|
+
__decorate([
|
|
229
|
+
state()
|
|
230
|
+
], AsideBar.prototype, "compact", void 0);
|
|
103
231
|
AsideBar = __decorate([
|
|
104
232
|
customElement('ox-aside-bar')
|
|
105
233
|
], AsideBar);
|