@kubex/zinc 1.1.73 → 1.1.75
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/dist/custom-elements.json +575 -91
- package/dist/vscode.html-custom-data.json +20 -15
- package/dist/web-types.json +37 -27
- package/dist/zn.d.ts +71 -5
- package/dist/zn.min.js +229 -229
- package/docs/data/preview-frame-payload-tall.json +7 -0
- package/docs/pages/components/preview-frame-demo.njk +45 -2
- package/docs/pages/components/preview-frame.md +79 -0
- package/docs/pages/components/tabs.md +9 -2
- package/package.json +1 -2
- package/scripts/build.js +5 -5
- package/src/components/defined-label/defined-label.component.ts +10 -17
- package/src/components/defined-label/defined-label.test.ts +107 -1
- package/src/components/expanding-action/expanding-action.component.ts +4 -2
- package/src/components/expanding-action/expanding-action.scss +14 -2
- package/src/components/expanding-action/expanding-action.test.ts +44 -1
- package/src/components/page/page.component.ts +71 -6
- package/src/components/page/page.test.ts +247 -0
- package/src/components/preview-frame/preview-frame.component.ts +84 -8
- package/src/components/preview-frame/preview-frame.scss +7 -1
- package/src/components/preview-frame/preview-frame.test.ts +184 -0
- package/src/components/tabs/tabs-navigation.ts +197 -0
- package/src/components/tabs/tabs.component.ts +188 -56
- package/src/components/tabs/tabs.test.ts +128 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// A tab selection belongs to a visit: one starts the first time a tab is
|
|
2
|
+
// recorded at a location and ends when that location is left. A reload continues
|
|
3
|
+
// the visit it interrupted, so the tab stays open and Back still steps through
|
|
4
|
+
// the tabs the visit opened; navigating away ends it, so returning starts from
|
|
5
|
+
// the default tab with no tab history behind it.
|
|
6
|
+
//
|
|
7
|
+
// Selections are remembered in two places, because neither is sufficient alone:
|
|
8
|
+
//
|
|
9
|
+
// - Every history entry carries the tab each container was showing when the
|
|
10
|
+
// entry was created, so a tab change is its own Back step. Entries are always
|
|
11
|
+
// merged into, never replaced, so nested pages keep their own records.
|
|
12
|
+
// - Session storage keyed by location survives a reload, which history state
|
|
13
|
+
// does not: the console pushes a fresh `{uri}` state on every document load,
|
|
14
|
+
// discarding whatever the reloaded entry held before a page can read it.
|
|
15
|
+
//
|
|
16
|
+
// Both name the visit they were written for. Ending a visit deletes its stored
|
|
17
|
+
// tabs outright; the tabs left on its history entries cannot be rewritten, so
|
|
18
|
+
// they are retired instead - the visit they name no longer exists.
|
|
19
|
+
|
|
20
|
+
export const TAB_STORE_PREFIX = 'zntab:';
|
|
21
|
+
|
|
22
|
+
const RESTORING_NAVIGATION_TYPES = ['reload', 'back_forward'];
|
|
23
|
+
|
|
24
|
+
const VISIT_STORE_KEY = '__znTabsVisit';
|
|
25
|
+
|
|
26
|
+
const TABS_HISTORY_KEY = '__znTabs';
|
|
27
|
+
|
|
28
|
+
interface TabsHistoryRecord {
|
|
29
|
+
visit: string;
|
|
30
|
+
tabs: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface TabsHistoryState {
|
|
34
|
+
[TABS_HISTORY_KEY]?: TabsHistoryRecord;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const restorableLocations = new Set<string>();
|
|
38
|
+
|
|
39
|
+
let visitCount = 0;
|
|
40
|
+
|
|
41
|
+
function sessionStore(): Storage | null {
|
|
42
|
+
try {
|
|
43
|
+
return window.sessionStorage;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function locationKey(): string {
|
|
50
|
+
return window.location.pathname + window.location.search;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function documentNavigationType(): string {
|
|
54
|
+
const entries = window.performance?.getEntriesByType('navigation') as PerformanceNavigationTiming[] | undefined;
|
|
55
|
+
return entries?.[0]?.type ?? '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Scopes a store key to the current location, so each page keeps its own tab. */
|
|
59
|
+
export function locationScopedKey(key: string): string {
|
|
60
|
+
return `${key}@${locationKey()}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function visitStoreKey(): string {
|
|
64
|
+
return TAB_STORE_PREFIX + locationScopedKey(VISIT_STORE_KEY);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The visit the current location is on, or an empty string before one starts. */
|
|
68
|
+
function currentVisit(): string {
|
|
69
|
+
return sessionStore()?.getItem(visitStoreKey()) ?? '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Starts a visit for the current location, unless one is already under way - a
|
|
73
|
+
// reload lands mid visit and must continue it rather than begin a new one.
|
|
74
|
+
function startLocationVisit(): void {
|
|
75
|
+
const store = sessionStore();
|
|
76
|
+
if (store === null || currentVisit() !== '') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
visitCount += 1;
|
|
81
|
+
store.setItem(visitStoreKey(), `${Date.now()}-${visitCount}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Ends the visits to every location other than the one on screen, discarding
|
|
86
|
+
* the tabs they were left showing. Called whenever the location may have
|
|
87
|
+
* changed, so the only tabs ever remembered are the current page's.
|
|
88
|
+
*/
|
|
89
|
+
export function endVisitsToOtherLocations(): void {
|
|
90
|
+
const store = sessionStore();
|
|
91
|
+
if (store === null) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const suffix = `@${locationKey()}`;
|
|
96
|
+
for (let index = store.length - 1; index >= 0; index--) {
|
|
97
|
+
const key = store.key(index);
|
|
98
|
+
if (key !== null && key.startsWith(TAB_STORE_PREFIX) && !key.endsWith(suffix)) {
|
|
99
|
+
store.removeItem(key);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (RESTORING_NAVIGATION_TYPES.includes(documentNavigationType())) {
|
|
105
|
+
restorableLocations.add(locationKey());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// A document load lands on the only location still worth remembering: every
|
|
109
|
+
// other one was navigated away from, whether or not a page was around to see it.
|
|
110
|
+
endVisitsToOtherLocations();
|
|
111
|
+
|
|
112
|
+
window.addEventListener('popstate', () => {
|
|
113
|
+
restorableLocations.add(locationKey());
|
|
114
|
+
endVisitsToOtherLocations();
|
|
115
|
+
}, {passive: true});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether the current location was reached in a way that should replay the tab
|
|
119
|
+
* it was last left on: a reload, or a history traversal. A fresh navigation -
|
|
120
|
+
* including a client side one to a location visited earlier - returns false.
|
|
121
|
+
*/
|
|
122
|
+
export function isRestorableLocation(): boolean {
|
|
123
|
+
return restorableLocations.has(locationKey());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
127
|
+
return value !== null && typeof value === 'object' ? value as Record<string, unknown> : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function historyRecord(): TabsHistoryRecord | null {
|
|
131
|
+
const state = asRecord(window.history.state);
|
|
132
|
+
const record = state === null ? null : asRecord((state as TabsHistoryState)[TABS_HISTORY_KEY]);
|
|
133
|
+
const tabs = record === null ? null : asRecord(record.tabs);
|
|
134
|
+
|
|
135
|
+
if (record === null || tabs === null || typeof record.visit !== 'string') {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {visit: record.visit, tabs: tabs as Record<string, string>};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The tab the current history entry was left showing, if it recorded one for the visit under way. */
|
|
143
|
+
export function getHistoryTab(key: string): string | null {
|
|
144
|
+
if (!key) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const visit = currentVisit();
|
|
149
|
+
const record = historyRecord();
|
|
150
|
+
if (visit === '' || record === null || record.visit !== visit) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const tab = record.tabs[key];
|
|
155
|
+
return typeof tab === 'string' ? tab : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// The host's own state is carried over so its entry stays intact - the console
|
|
159
|
+
// reads `state.uri` on popstate - and the url is left exactly as it is.
|
|
160
|
+
function writeHistoryTab(key: string, tab: string, push: boolean): void {
|
|
161
|
+
startLocationVisit();
|
|
162
|
+
|
|
163
|
+
const visit = currentVisit();
|
|
164
|
+
if (!key || visit === '') {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const record = historyRecord();
|
|
169
|
+
const tabs = record !== null && record.visit === visit ? record.tabs : {};
|
|
170
|
+
|
|
171
|
+
// Re-recording what the entry already says would be a wasted history write,
|
|
172
|
+
// and browsers cap how many of those a page may make.
|
|
173
|
+
if (!push && tabs[key] === tab) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const state = {
|
|
178
|
+
...asRecord(window.history.state),
|
|
179
|
+
[TABS_HISTORY_KEY]: {visit, tabs: {...tabs, [key]: tab}}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (push) {
|
|
183
|
+
window.history.pushState(state, '', window.location.href);
|
|
184
|
+
} else {
|
|
185
|
+
window.history.replaceState(state, '');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Adds a history entry for a tab change, making it its own Back step. */
|
|
190
|
+
export function pushHistoryTab(key: string, tab: string): void {
|
|
191
|
+
writeHistoryTab(key, tab, true);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Records the tab on the current entry without adding a Back step. */
|
|
195
|
+
export function replaceHistoryTab(key: string, tab: string): void {
|
|
196
|
+
writeHistoryTab(key, tab, false);
|
|
197
|
+
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
|
|
2
|
+
import {deepQuerySelectorAll} from "../../utilities/query";
|
|
3
|
+
import {
|
|
4
|
+
endVisitsToOtherLocations,
|
|
5
|
+
getHistoryTab,
|
|
6
|
+
isRestorableLocation,
|
|
7
|
+
locationScopedKey,
|
|
8
|
+
pushHistoryTab,
|
|
9
|
+
replaceHistoryTab,
|
|
10
|
+
TAB_STORE_PREFIX
|
|
11
|
+
} from './tabs-navigation';
|
|
12
|
+
import {HasSlotController} from "../../internal/slot";
|
|
13
|
+
import {ifDefined} from "lit/directives/if-defined.js";
|
|
14
|
+
import {md5} from "../../utilities/md5";
|
|
15
|
+
import {MutationController} from '@lit-labs/observers/mutation-controller.js';
|
|
16
|
+
import {property} from 'lit/decorators.js';
|
|
17
|
+
import {Store} from "../../internal/storage";
|
|
9
18
|
import ZincElement from '../../internal/zinc-element';
|
|
10
19
|
|
|
11
20
|
import styles from './tabs.scss';
|
|
@@ -34,25 +43,25 @@ const tabContainerSelector = 'zn-tabs, zn-page, zn-page-nav';
|
|
|
34
43
|
*/
|
|
35
44
|
export default class ZnTabs extends ZincElement {
|
|
36
45
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
37
|
-
@property({
|
|
38
|
-
@property({
|
|
39
|
-
@property({
|
|
40
|
-
@property({
|
|
41
|
-
@property({
|
|
42
|
-
@property({
|
|
43
|
-
@property({
|
|
44
|
-
@property({
|
|
45
|
-
@property({
|
|
46
|
-
@property({
|
|
47
|
-
@property({
|
|
46
|
+
@property({attribute: 'master-id', reflect: true}) masterId: string;
|
|
47
|
+
@property({attribute: 'default-uri', reflect: true}) defaultUri = '';
|
|
48
|
+
@property({attribute: 'active', reflect: true}) _current = '';
|
|
49
|
+
@property({attribute: 'split', type: Number, reflect: true}) _split: number;
|
|
50
|
+
@property({attribute: 'split-min', type: Number, reflect: true}) _splitMin = 60;
|
|
51
|
+
@property({attribute: 'split-min-secondary', type: Number, reflect: true}) _splitMinSecondary: number;
|
|
52
|
+
@property({attribute: 'split-max', type: Number, reflect: true}) _splitMax: number;
|
|
53
|
+
@property({attribute: 'primary-caption', reflect: true}) primaryCaption = 'Navigation';
|
|
54
|
+
@property({attribute: 'secondary-caption', reflect: true}) secondaryCaption = 'Content';
|
|
55
|
+
@property({attribute: 'no-prefetch', type: Boolean, reflect: true}) noPrefetch = false;
|
|
56
|
+
@property({attribute: 'no-cache', type: Boolean, reflect: true}) noCache = false;
|
|
48
57
|
// session storage if not local
|
|
49
|
-
@property({
|
|
50
|
-
@property({
|
|
51
|
-
@property({
|
|
52
|
-
@property({
|
|
53
|
-
@property({
|
|
54
|
-
@property({
|
|
55
|
-
@property({
|
|
58
|
+
@property({attribute: 'local-storage', type: Boolean, reflect: true}) localStorage: boolean;
|
|
59
|
+
@property({attribute: 'store-key'}) storeKey: string;
|
|
60
|
+
@property({attribute: 'store-ttl', type: Number, reflect: true}) storeTtl = 0;
|
|
61
|
+
@property({attribute: 'padded', type: Boolean, reflect: true}) padded = false;
|
|
62
|
+
@property({attribute: 'fetch-style', type: String, reflect: true}) fetchStyle = "";
|
|
63
|
+
@property({attribute: 'full-width', type: Boolean, reflect: true}) fullWidth = false;
|
|
64
|
+
@property({attribute: 'padded-right', type: Boolean, reflect: true}) paddedRight = false;
|
|
56
65
|
@property() monitor: string;
|
|
57
66
|
// Creating a header
|
|
58
67
|
@property() caption: string;
|
|
@@ -66,11 +75,12 @@ export default class ZnTabs extends ZincElement {
|
|
|
66
75
|
private _tabs: HTMLElement[] = [];
|
|
67
76
|
private _actions: HTMLElement[] = [];
|
|
68
77
|
private _knownUri: Map<string, string> = new Map<string, string>();
|
|
78
|
+
private _defaultTab = '';
|
|
69
79
|
private readonly hasSlotController = new HasSlotController(this, '[default]', 'bottom', 'right', 'left', 'top', 'actions');
|
|
70
80
|
|
|
71
81
|
private readonly _domObserver = new MutationController(this, {
|
|
72
82
|
target: null,
|
|
73
|
-
config: {
|
|
83
|
+
config: {childList: true, subtree: true},
|
|
74
84
|
callback: mutations => {
|
|
75
85
|
mutations.forEach(mutation => {
|
|
76
86
|
if (mutation.type !== 'childList') return;
|
|
@@ -89,17 +99,16 @@ export default class ZnTabs extends ZincElement {
|
|
|
89
99
|
|
|
90
100
|
private readonly _monitorObserver = new MutationController(this, {
|
|
91
101
|
target: null,
|
|
92
|
-
config: {
|
|
102
|
+
config: {childList: true, subtree: true},
|
|
93
103
|
callback: mutations => {
|
|
94
104
|
mutations.forEach(mutation => {
|
|
95
105
|
if (mutation.type !== 'childList') return;
|
|
96
106
|
mutation.addedNodes.forEach(node => {
|
|
97
107
|
if (node instanceof HTMLElement && node.id === this.monitor) {
|
|
98
108
|
this.reRegisterTabs();
|
|
99
|
-
const storedValue = this.
|
|
109
|
+
const storedValue = this.getStoredTab();
|
|
100
110
|
if (storedValue !== null) {
|
|
101
|
-
this.
|
|
102
|
-
this.setActiveTab(storedValue, false, false);
|
|
111
|
+
this.restoreStoredTab(storedValue);
|
|
103
112
|
}
|
|
104
113
|
}
|
|
105
114
|
});
|
|
@@ -133,7 +142,7 @@ export default class ZnTabs extends ZincElement {
|
|
|
133
142
|
|
|
134
143
|
const defaultID = this.defaultUri ? this._uriToId(this.defaultUri) : '';
|
|
135
144
|
|
|
136
|
-
this._store = new Store(this.localStorage ? window.localStorage : window.sessionStorage,
|
|
145
|
+
this._store = new Store(this.localStorage ? window.localStorage : window.sessionStorage, TAB_STORE_PREFIX, this.storeTtl);
|
|
137
146
|
Array.from(this.children).forEach((element) => {
|
|
138
147
|
if (element.slot === '') {
|
|
139
148
|
this._panels.set(element.getAttribute('id') || defaultID, [element]);
|
|
@@ -142,6 +151,21 @@ export default class ZnTabs extends ZincElement {
|
|
|
142
151
|
|
|
143
152
|
this.observerDom();
|
|
144
153
|
this.monitorDom();
|
|
154
|
+
window.addEventListener('popstate', this.handlePopState, {passive: true});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
disconnectedCallback() {
|
|
158
|
+
super.disconnectedCallback();
|
|
159
|
+
window.removeEventListener('popstate', this.handlePopState);
|
|
160
|
+
|
|
161
|
+
// The container is torn down before the url it is being replaced by is
|
|
162
|
+
// pushed, so the check waits a task: by then a navigation has changed the
|
|
163
|
+
// location and this visit is over, while a re-render is back on screen.
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
if (!this.isConnected) {
|
|
166
|
+
endVisitsToOtherLocations();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
145
169
|
}
|
|
146
170
|
|
|
147
171
|
monitorDom() {
|
|
@@ -150,6 +174,116 @@ export default class ZnTabs extends ZincElement {
|
|
|
150
174
|
}
|
|
151
175
|
}
|
|
152
176
|
|
|
177
|
+
/** The key the active tab is persisted under. Null disables persistence. */
|
|
178
|
+
protected getTabStoreKey(): string | null {
|
|
179
|
+
return this.storeKey || null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
protected getStoredTab(): string | null {
|
|
183
|
+
const key = this.getTabStoreKey();
|
|
184
|
+
if (key === null || !this._store) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Local storage persists across fresh navigation and browser sessions.
|
|
189
|
+
if (this.localStorage) {
|
|
190
|
+
return this._store.get(key);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Nothing is remembered for a location that has been navigated away from:
|
|
194
|
+
// its stored tab went with the visit, and so did the tabs on its entries.
|
|
195
|
+
const stored = this._store.get(locationScopedKey(key));
|
|
196
|
+
if (stored === null) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// The history entry is authoritative where it survived; a reload wipes it,
|
|
201
|
+
// leaving the selection the location was last left on.
|
|
202
|
+
const fromHistory = getHistoryTab(key);
|
|
203
|
+
if (fromHistory !== null) {
|
|
204
|
+
return fromHistory;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return isRestorableLocation() ? stored : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
protected storeActiveTab(tabName: string) {
|
|
211
|
+
const key = this.getTabStoreKey();
|
|
212
|
+
if (key === null || !this._store) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Uri tab ids are derived from the master id, so store the uri itself.
|
|
217
|
+
const value = this.getUriForTabId(tabName) ?? tabName;
|
|
218
|
+
this._store.set(this.localStorage ? key : locationScopedKey(key), value);
|
|
219
|
+
if (!this.localStorage) {
|
|
220
|
+
replaceHistoryTab(key, value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
protected restoreStoredTab(stored: string) {
|
|
225
|
+
const uriTab = this._tabs.find(tab => tab.getAttribute('tab-uri') === stored);
|
|
226
|
+
if (uriTab) {
|
|
227
|
+
this.clickTab(uriTab, false);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this._prepareTab(stored);
|
|
232
|
+
this.setActiveTab(stored, true, false);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Activates the tab the container starts on, ignoring any stored selection. */
|
|
236
|
+
protected activateDefaultTab() {
|
|
237
|
+
if (!this._panels.has(this._defaultTab) && this._tabs.length > 0) {
|
|
238
|
+
const tabUri = this._tabs[0].getAttribute('tab-uri');
|
|
239
|
+
if (tabUri) {
|
|
240
|
+
this.clickTab(this._tabs[0], false);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.setActiveTab(this._defaultTab, true, false);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Back and forward move between recorded tabs.
|
|
249
|
+
private readonly handlePopState = () => {
|
|
250
|
+
const key = this.getTabStoreKey();
|
|
251
|
+
if (key === null) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Only entries that name a tab move the container. Entries that say nothing
|
|
256
|
+
// about it - the extra one the console pushes for every document load, one
|
|
257
|
+
// pushed before this container was on screen, one belonging to another page,
|
|
258
|
+
// one left by a visit that has ended - leave the open tab where it is.
|
|
259
|
+
// Overwriting it with the default tab would show the first tab on the way
|
|
260
|
+
// through, in place of the tab the entry being stepped onto stands for.
|
|
261
|
+
const tab = getHistoryTab(key);
|
|
262
|
+
if (tab === null) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.restoreStoredTab(tab);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
private pushTabHistory(tabName: string) {
|
|
270
|
+
const key = this.getTabStoreKey();
|
|
271
|
+
if (key === null || this.localStorage) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
pushHistoryTab(key, this.getUriForTabId(tabName) ?? tabName);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private getUriForTabId(tabId: string): string | null {
|
|
279
|
+
for (const [uri, id] of this._knownUri) {
|
|
280
|
+
if (id === tabId) {
|
|
281
|
+
return uri;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
153
287
|
_addPanel(panel: HTMLElement) {
|
|
154
288
|
if (this._panels.has(panel.getAttribute('id')!)) {
|
|
155
289
|
return;
|
|
@@ -176,29 +310,20 @@ export default class ZnTabs extends ZincElement {
|
|
|
176
310
|
super.firstUpdated(_changedProperties);
|
|
177
311
|
setTimeout(() => {
|
|
178
312
|
this._registerTabs();
|
|
313
|
+
this._defaultTab = this._current || '';
|
|
179
314
|
|
|
180
|
-
const storedValue = this.
|
|
315
|
+
const storedValue = this.getStoredTab();
|
|
181
316
|
if (storedValue !== null) {
|
|
182
|
-
this.
|
|
183
|
-
this.setActiveTab(storedValue, false, false);
|
|
317
|
+
this.restoreStoredTab(storedValue);
|
|
184
318
|
return;
|
|
185
319
|
}
|
|
186
320
|
|
|
187
|
-
|
|
188
|
-
if (!this._panels.has(defaultTab) && this._tabs.length > 0) {
|
|
189
|
-
const tabUri = this._tabs[0].getAttribute('tab-uri');
|
|
190
|
-
if (tabUri) {
|
|
191
|
-
this.clickTab(this._tabs[0], false);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
this.setActiveTab(defaultTab, false, false);
|
|
321
|
+
this.activateDefaultTab();
|
|
197
322
|
}, 10);
|
|
198
323
|
|
|
199
324
|
this.addEventListener('zn-menu-select', () => {
|
|
200
325
|
setTimeout(this.reRegisterTabs, 200);
|
|
201
|
-
}, {
|
|
326
|
+
}, {passive: true});
|
|
202
327
|
}
|
|
203
328
|
|
|
204
329
|
switchTab(inc: number) {
|
|
@@ -215,7 +340,7 @@ export default class ZnTabs extends ZincElement {
|
|
|
215
340
|
nextIndex = 0; // wrap around to the first tab
|
|
216
341
|
}
|
|
217
342
|
const nextTabId = Array.from(this._panels.keys())[nextIndex];
|
|
218
|
-
this.setActiveTab(nextTabId, true, false);
|
|
343
|
+
this.setActiveTab(nextTabId, true, false, null, true);
|
|
219
344
|
}
|
|
220
345
|
|
|
221
346
|
nextTab() {
|
|
@@ -277,7 +402,7 @@ export default class ZnTabs extends ZincElement {
|
|
|
277
402
|
}
|
|
278
403
|
|
|
279
404
|
document.dispatchEvent(new CustomEvent('zn-new-element', {
|
|
280
|
-
detail: {
|
|
405
|
+
detail: {element: tabNode, source: tabEle}
|
|
281
406
|
}));
|
|
282
407
|
return tabNode;
|
|
283
408
|
}
|
|
@@ -288,9 +413,9 @@ export default class ZnTabs extends ZincElement {
|
|
|
288
413
|
if (target) {
|
|
289
414
|
if ('startViewTransition' in document) {
|
|
290
415
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
291
|
-
(document as any).startViewTransition(() => this.clickTab(target, event.altKey));
|
|
416
|
+
(document as any).startViewTransition(() => this.clickTab(target, event.altKey, true));
|
|
292
417
|
} else {
|
|
293
|
-
this.clickTab(target, event.altKey)
|
|
418
|
+
this.clickTab(target, event.altKey, true)
|
|
294
419
|
}
|
|
295
420
|
}
|
|
296
421
|
}
|
|
@@ -303,7 +428,7 @@ export default class ZnTabs extends ZincElement {
|
|
|
303
428
|
}
|
|
304
429
|
}
|
|
305
430
|
|
|
306
|
-
clickTab(target: HTMLElement, refresh: boolean) {
|
|
431
|
+
clickTab(target: HTMLElement, refresh: boolean, pushHistory = false) {
|
|
307
432
|
const tabUri = target.getAttribute('tab-uri');
|
|
308
433
|
const wasCached = !!tabUri && this._panels.has(this._uriToId(tabUri));
|
|
309
434
|
this.fetchUriTab(target);
|
|
@@ -311,7 +436,7 @@ export default class ZnTabs extends ZincElement {
|
|
|
311
436
|
if (target.hasAttribute('tab')) {
|
|
312
437
|
const forceRefresh = refresh || (this.noCache && wasCached);
|
|
313
438
|
setTimeout(() => {
|
|
314
|
-
this.setActiveTab(target.getAttribute('tab') || '', true, forceRefresh, this.getRefTab(target));
|
|
439
|
+
this.setActiveTab(target.getAttribute('tab') || '', true, forceRefresh, this.getRefTab(target), pushHistory);
|
|
315
440
|
}, 10);
|
|
316
441
|
}
|
|
317
442
|
}
|
|
@@ -331,7 +456,8 @@ export default class ZnTabs extends ZincElement {
|
|
|
331
456
|
return null;
|
|
332
457
|
}
|
|
333
458
|
|
|
334
|
-
setActiveTab(tabName: string, store: boolean, refresh: boolean, refTab: string | null = null) {
|
|
459
|
+
setActiveTab(tabName: string, store: boolean, refresh: boolean, refTab: string | null = null, pushHistory = false) {
|
|
460
|
+
const previous = this._current;
|
|
335
461
|
let hasActive = false;
|
|
336
462
|
this._tabs.forEach(tab => {
|
|
337
463
|
|
|
@@ -355,8 +481,14 @@ export default class ZnTabs extends ZincElement {
|
|
|
355
481
|
//Set on the element as a failsafe before TabPanel is loaded
|
|
356
482
|
//This must be done AFTER selectTab to avoid panel bugs
|
|
357
483
|
|
|
358
|
-
|
|
359
|
-
|
|
484
|
+
// The entry must be pushed before the tab is recorded, so the entry being
|
|
485
|
+
// left keeps the tab it was showing.
|
|
486
|
+
if (pushHistory && this._current === tabName && previous !== tabName) {
|
|
487
|
+
this.pushTabHistory(tabName);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (store) {
|
|
491
|
+
this.storeActiveTab(tabName);
|
|
360
492
|
}
|
|
361
493
|
}
|
|
362
494
|
|
|
@@ -402,7 +534,7 @@ export default class ZnTabs extends ZincElement {
|
|
|
402
534
|
gaid = this._activeTab.getAttribute('gaid')!;
|
|
403
535
|
}
|
|
404
536
|
document.dispatchEvent(new CustomEvent('zn-refresh-element', {
|
|
405
|
-
detail: {
|
|
537
|
+
detail: {element: element, uri: uri, gaid: gaid}
|
|
406
538
|
}));
|
|
407
539
|
}
|
|
408
540
|
});
|