@itfin/components 2.0.30 → 2.0.31

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.
@@ -1,9 +1,6 @@
1
1
  <template>
2
2
  <div tabindex="-1" :class="{'b-panel': !nocard, 'b-panel__collapsed': collapsed, 'b-panel__active': !collapsed}">
3
- <div v-if="alert" class="b-panel-alert d-flex align-items-center justify-content-center gap-2" :class="[`b-panel-alert--${alertType}`]">
4
- <itf-icon name="bell" :size="24" new class="b-panel-alert__icon" />
5
- <p class="mb-0">{{ alert }}</p>
6
- </div>
3
+ <slot name="before-header"></slot>
7
4
  <div v-if="collapsed && !nocard" class="b-panel__expand" @click.stop.prevent="expandPanel">
8
5
  <itf-button v-if="closeable" icon small class="b-panel__expand_button" @click="closePanel">
9
6
  <itf-icon name="cross" />
@@ -26,9 +23,14 @@
26
23
  </div>
27
24
  <div class="d-flex gap-1">
28
25
  <slot name="buttons"></slot>
29
- <itf-button v-if="expandable" icon default class="b-panel__expand_button d-none d-md-block" @click="fullsizePanel">
30
- <itf-icon new name="expand" />
31
- </itf-button>
26
+ <template v-if="expandable">
27
+ <itf-button v-if="!isFullSize" icon default class="b-panel__expand_button d-none d-md-block" @click="fullsizePanel">
28
+ <itf-icon new name="expand" />
29
+ </itf-button>
30
+ <itf-button v-else icon default class="b-panel__expand_button d-none d-md-block" @click="collapsePanel">
31
+ <itf-icon new name="collapse" />
32
+ </itf-button>
33
+ </template>
32
34
  <itf-button v-if="closeable" icon default class="b-panel__expand_button" @click="closePanel">
33
35
  <itf-icon new name="close" />
34
36
  </itf-button>
@@ -161,29 +163,6 @@
161
163
  top: 0;
162
164
  z-index: 10;
163
165
  }
164
- .b-panel-alert {
165
- border-radius: 6px 6px 0 0;
166
- padding: 2px 16px;
167
- font-size: 12px;
168
- line-height: 16px;
169
- text-align: center;
170
- color: var(--b-panel-white);
171
- background-color: var(--b-panel-info);
172
-
173
- &__icon {
174
- animation: bellRing 5s ease-in-out infinite;
175
- }
176
-
177
- &--success {
178
- background-color: var(--b-panel-success);
179
- }
180
- &--warning {
181
- background-color: var(--b-panel-warning);
182
- }
183
- &--danger {
184
- background-color: var(--b-panel-danger);
185
- }
186
- }
187
166
  </style>
188
167
  <script>
189
168
  import { Vue, Prop, Component } from 'vue-property-decorator';
@@ -211,11 +190,10 @@ class Panel extends Vue {
211
190
  @Prop() panel;
212
191
  @Prop(Boolean) collapsed;
213
192
  @Prop(Boolean) closeable;
193
+ @Prop(Boolean) isFullSize;
214
194
  @Prop(Boolean) expandable;
215
195
  @Prop(Boolean) animate;
216
196
  @Prop(Boolean) nocard;
217
- @Prop(String) alert;
218
- @Prop({ type: String, default: 'info' }) alertType; // info | success | warning | danger
219
197
 
220
198
  openPanel(...args) {
221
199
  this.$emit('open', args);
@@ -225,6 +203,10 @@ class Panel extends Vue {
225
203
  this.$emit('expand');
226
204
  }
227
205
 
206
+ collapsePanel() {
207
+ this.$emit('collapse');
208
+ }
209
+
228
210
  fullsizePanel() {
229
211
  this.$emit('fullsize');
230
212
  }
@@ -5,6 +5,7 @@
5
5
  import { Vue, Component, Inject, Prop } from 'vue-property-decorator';
6
6
  import { IPanel } from './PanelList.vue';
7
7
  import {stackToHash} from "@itfin/components/src/components/panels/helpers";
8
+ import {getRootPanelList} from "@itfin/components/src/components/panels";
8
9
 
9
10
  @Component({
10
11
  components: {
@@ -15,7 +16,6 @@ import {stackToHash} from "@itfin/components/src/components/panels/helpers";
15
16
  }
16
17
  })
17
18
  export default class PanelLink extends Vue {
18
- @Inject({ default: null }) panelList;
19
19
  @Inject({ default: null }) currentPanel;
20
20
 
21
21
  @Prop(Boolean) global: boolean;
@@ -35,7 +35,7 @@ export default class PanelLink extends Vue {
35
35
  }
36
36
 
37
37
  get activeList() {
38
- return this.list ?? this.panelList;
38
+ return this.list ?? getRootPanelList();
39
39
  }
40
40
 
41
41
  get isActive() {
@@ -57,11 +57,13 @@ export default class PanelLink extends Vue {
57
57
  type: this.panel,
58
58
  payload: this.payload || {}
59
59
  }
60
- ], true);
60
+ ]);
61
61
  return hash;
62
62
  }
63
63
 
64
64
  onClick(e) {
65
+ e.preventDefault();
66
+ e.stopPropagation();
65
67
  this.$emit('open', {
66
68
  panel: this.panel,
67
69
  payload: this.payload || {},
@@ -70,8 +72,6 @@ export default class PanelLink extends Vue {
70
72
  if (!this.activeList) {
71
73
  return;
72
74
  }
73
- e.preventDefault();
74
- e.stopPropagation();
75
75
  this.activeList.openPanel(this.panel, this.payload || {}, this.append ? undefined : this.currentPanel?.index + 1);
76
76
  }
77
77
  }
@@ -17,17 +17,20 @@
17
17
  :icon="panel.icon"
18
18
  :payload="panel.payload"
19
19
  :expandable="panelsStack.length > 1"
20
+ :isFullSize="isFullSize"
20
21
  :collapsed="panel.isCollapsed"
21
22
  :closeable="panel.isCloseable"
22
23
  :animate="panel.isAnimate"
23
- :alert="panel.alert"
24
- :alertType="panel.alertType"
25
24
  @open="openPanel($event[0], $event[1], n + 1)"
26
25
  @expand="expandPanel(panel)"
27
26
  @fullsize="fullsizePanel(panel)"
27
+ @collapse="collapsePanel(panel)"
28
28
  @close="closePanel(panel)"
29
29
  @open-menu="$emit('open-menu', panel.type, panel.payload)"
30
30
  >
31
+ <template #before-header>
32
+ <slot name="before-header" :panel="panel" :index="n" :payload="panel.payload"></slot>
33
+ </template>
31
34
  <slot
32
35
  :name="panel.type"
33
36
  :panel="panel"
@@ -151,7 +154,6 @@ $double-an-time: $an-time * 2;
151
154
  //transition: opacity $an-time linear;
152
155
  }
153
156
  }
154
-
155
157
  //.slide-enter-active > div {
156
158
  // opacity: 0;
157
159
  //}
@@ -162,16 +164,16 @@ $double-an-time: $an-time * 2;
162
164
  </style>
163
165
  <script lang="ts">
164
166
  import { Vue, Component, Prop } from 'vue-property-decorator';
167
+ import itfIcon from '../icon/Icon.vue';
165
168
  import Panel from './Panel.vue';
166
169
  import {hashToStack, stackToHash} from "@itfin/components/src/components/panels/helpers";
170
+ import {setRootPanelList} from "@itfin/components/src/components/panels";
167
171
 
168
172
  interface VisualOptions {
169
173
  title: string;
170
174
  icon?: string;
171
175
  }
172
176
 
173
- export type PanelAlertType = 'info' | 'success' | 'warning' | 'danger';
174
-
175
177
  export interface IPanel {
176
178
  id: number;
177
179
  title: string;
@@ -182,8 +184,6 @@ export interface IPanel {
182
184
  isCollapsed: boolean;
183
185
  isCloseable: boolean;
184
186
  isAnimate: boolean;
185
- alert?: string;
186
- alertType?: PanelAlertType;
187
187
  open: (type: string, visOptions: VisualOptions, payload: any) => void;
188
188
  close: () => void;
189
189
  expand: () => void;
@@ -202,6 +202,7 @@ export interface IPanel {
202
202
 
203
203
  @Component({
204
204
  components: {
205
+ itfIcon,
205
206
  Panel
206
207
  },
207
208
  directives: {
@@ -223,6 +224,7 @@ export default class PanelList extends Vue {
223
224
  nextId:number = 0;
224
225
 
225
226
  created() {
227
+ setRootPanelList(this);
226
228
  if (this.firstPanel) {
227
229
  this.internalOpenPanel(this.firstPanel.type, this.firstPanel.payload);
228
230
  }
@@ -289,15 +291,10 @@ export default class PanelList extends Vue {
289
291
  if (typeof panel.caption !== 'function') {
290
292
  throw new Error('Panel component must have a "caption" function');
291
293
  }
292
- if (this.panels[type].alert && typeof this.panels[type].alert !== 'function') {
293
- throw new Error('Panel component "alert" field must have function type');
294
- }
295
294
  const newPanel:any = {
296
295
  id: this.nextId++,
297
296
  nocard: panel.nocard,
298
297
  title: panel.caption(this.$t.bind(this), payload),
299
- alert: this.panels[type].alert ? this.panels[type].alert(this.$t.bind(this), payload) : undefined,
300
- alertType: this.panels[type].alertType,
301
298
  icon: panel.icon ? panel.icon(this.$t.bind(this), payload) : null,
302
299
  components: {
303
300
  default: panel.default ?? undefined,
@@ -325,6 +322,10 @@ export default class PanelList extends Vue {
325
322
  isAnimation = newStack.length === openIndex;
326
323
  newStack = newStack.slice(0, openIndex);
327
324
  }
325
+ if (newStack.length > 0 && !newStack.find(p => !p.isCollapsed)) {
326
+ // якщо немає відкритих панелей, то перша панель має бути розгорнута
327
+ newStack[0].isCollapsed = false;
328
+ }
328
329
  this.panelsStack = newStack;
329
330
  return new Promise(res => {
330
331
  this.$nextTick(() => { // щоб панелі змінювались при редагуванні
@@ -364,8 +365,6 @@ export default class PanelList extends Vue {
364
365
  newPanel.setPayload = (value: any) => {
365
366
  newPanel.payload = value;
366
367
  newPanel.title = panel.caption(this.$t.bind(this), value);
367
- newPanel.alert = panel.alert ? panel.alert(this.$t.bind(this), payload) : undefined;
368
- newPanel.alertType = panel.alertType;
369
368
  newPanel.icon = panel.icon ? panel.icon(this.$t.bind(this), payload) : null,
370
369
  this.setPanelHash()
371
370
  }
@@ -416,12 +415,40 @@ export default class PanelList extends Vue {
416
415
  fullsizePanel(panel: IPanel) {
417
416
  const newStack = [...this.panelsStack];
418
417
  for (const p of newStack) {
418
+ p.isLastOpened = !p.isCollapsed && p !== panel;
419
419
  p.isCollapsed = p !== panel;
420
420
  }
421
421
  this.panelsStack = newStack;
422
422
  this.setPanelHash();
423
423
  }
424
424
 
425
+ get isFullSize() {
426
+ return this.panelsStack.filter(p => !p.isCollapsed).length === 1;
427
+ }
428
+
429
+ expandPanel(panel: IPanel) {
430
+ const newStack = [...this.panelsStack];
431
+ const index = newStack.findIndex(p => p.id === panel.id);
432
+ newStack[index].isCollapsed = false;
433
+ this.panelsStack = newStack;
434
+ this.ensureOnlyTwoOpenPanels(panel.id);
435
+ this.setPanelHash();
436
+ }
437
+
438
+ collapsePanel(panel: IPanel) {
439
+ const newStack = [...this.panelsStack];
440
+ const currenctIndex = newStack.findIndex(p => p.id === panel.id);
441
+ const lastOpenedIndex = newStack.findIndex(p => p.isLastOpened);
442
+ if (lastOpenedIndex !== -1) { // якщо зебрежена остання відкрита панель
443
+ newStack[lastOpenedIndex].isCollapsed = false
444
+ } else if (newStack[currenctIndex-1]) { // якщо після оновлення сторінки відсутнє значення "остання відкрита", то відкриваємо ту, що зліва
445
+ newStack[currenctIndex-1].isCollapsed = false;
446
+ }
447
+ this.panelsStack = newStack;
448
+ this.ensureOnlyTwoOpenPanels(panel.id);
449
+ this.setPanelHash();
450
+ }
451
+
425
452
  getPanels(type) {
426
453
  return this.panelsStack.filter(panel => panel.type === type);
427
454
  }
@@ -1,4 +1,5 @@
1
1
  import JSON5 from 'json5'
2
+ import {isPathType} from "@itfin/components/src/components/panels";
2
3
 
3
4
  export interface IPanel {
4
5
  type: string;
@@ -6,27 +7,27 @@ export interface IPanel {
6
7
  isCollapsed?: boolean;
7
8
  }
8
9
 
9
- export function stackToHash(stack: IPanel[], isPath = false) {
10
+ export function stackToHash(stack: IPanel[]) {
10
11
  const hash = stack.map(panel => {
11
12
  let json = JSON5.stringify(panel.payload || {});
12
13
  json = json.substring(1, json.length - 1); // Remove the outer {}
13
- return `${panel.type}${panel.isCollapsed ? '' : '!'}${json ? '=' : ''}${json}`;
14
- }).join(isPath ? '/' : '&');
15
- return isPath ? `/${hash}` : `#${hash}`;
14
+ return `${panel.type}${panel.isCollapsed ? '!' : ''}${json ? '=' : ''}${json}`;
15
+ }).join(isPathType() ? '/' : '&');
16
+ return isPathType() ? `/${hash}` : `#${hash}`;
16
17
  }
17
18
 
18
19
 
19
- export function hashToStack(hash: string|undefined, isPath = false): IPanel[] {
20
+ export function hashToStack(hash: string|undefined): IPanel[] {
20
21
  let stack:IPanel[] = [];
21
22
  if (hash) {
22
- const str = hash.replace(isPath ? /^\// : /^#/, '');
23
+ const str = hash.replace(isPathType() ? /^\// : /^#/, '');
23
24
 
24
- stack = str.split(isPath ? '/' : '&').map(item => {
25
+ stack = str.split(isPathType() ? '/' : '&').map(item => {
25
26
  if (!item.includes('=')) {
26
- return { type: item.replace('!', ''), isCollapsed: !item.includes('!'), payload: {} };
27
+ return { type: item.replace('!', ''), isCollapsed: item.includes('!'), payload: {} };
27
28
  }
28
29
  const [type, payload] = item.split('=');
29
- const isCollapsed = !type.includes('!');
30
+ const isCollapsed = type.includes('!');
30
31
  let payloadObj:any = {};
31
32
  try {
32
33
  let json = decodeURIComponent(payload);
@@ -0,0 +1,24 @@
1
+ const PanelsSettings = {
2
+ pathType: 'path', // 'hash' | 'path'
3
+ rootPanelList: null, // This will be set to the root panel list when the app is initialized
4
+ };
5
+
6
+ export function getPanelsSettings() {
7
+ return PanelsSettings;
8
+ }
9
+
10
+ export function getRootPanelList() {
11
+ return PanelsSettings.rootPanelList;
12
+ }
13
+
14
+ export function isPathType() {
15
+ return PanelsSettings.pathType === 'path';
16
+ }
17
+
18
+ export function setPanelsPathType(settings: any) {
19
+ PanelsSettings.pathType = settings.pathType ?? 'path';
20
+ }
21
+
22
+ export function setRootPanelList(rootPanelList: any) {
23
+ PanelsSettings.rootPanelList = rootPanelList;
24
+ }