Compare commits

...

756 Commits

Author SHA1 Message Date
mathuo
1e63da807b
chore(release): publish v4.2.5 2025-05-01 17:59:15 +01:00
mathuo
68d7947dea
chore: v4.2.5 docs 2025-05-01 17:58:55 +01:00
mathuo
093a034738
Merge pull request #921 from mathuo/883-tab-overflow-dropdown-doesnt-collide-with-edge-of-screen
883 tab overflow dropdown doesnt collide with edge of screen
2025-04-30 20:43:39 +01:00
mathuo
f9ca5b99d5
Merge pull request #920 from mathuo/891-first-tab-does-not-drag-drop-when-there-is-not-enough-space-above-the-dockview-area
bug: fix ghost image rendering
2025-04-30 20:43:16 +01:00
mathuo
420466398d
bug: popout service overflow handle 2025-04-29 22:01:27 +01:00
mathuo
20b5c844f3
Merge branch 'master' of https://github.com/mathuo/dockview into 883-tab-overflow-dropdown-doesnt-collide-with-edge-of-screen 2025-04-29 22:00:44 +01:00
mathuo
6d2f2f1012
bug: fix ghost image rendering 2025-04-29 21:45:23 +01:00
mathuo
3ddea55834
chore(release): publish v4.2.4 2025-04-29 21:01:28 +01:00
mathuo
2f334c63c1
chore: v4.2.4 docs 2025-04-29 21:01:04 +01:00
mathuo
8334f9795c
Merge pull request #917 from mathuo/913-returning-group-in-popout-to-main-window-via-drag-creates-empty-space-ghost-group
bug: ghost groups appearing
2025-04-28 21:32:47 +01:00
mathuo
7ea3154946
Merge pull request #828 from dzek69/patch-1
Fix provenance npm link
2025-04-28 21:27:28 +01:00
mathuo
1ef3dc06ce
Merge pull request #827 from AlexErrant/patch-1
drop extraneous <BrowserHeader />
2025-04-28 21:25:47 +01:00
mathuo
1ff74f2e65
Merge pull request #916 from mathuo/903-popover-overflow-when-hovering-right-aligned-more-icon-in-panel-group
903 popover overflow when hovering right aligned more icon in panel group
2025-04-28 21:24:40 +01:00
mathuo
a82aac09bc
bug: ghost groups appearing 2025-04-23 20:57:35 +01:00
mathuo
d1239a5ee5
bug: popup within floating panel 2025-04-23 20:35:46 +01:00
mathuo
22deb851dc
bug: popup overflow 2025-04-15 19:24:23 +01:00
mathuo
87f257df1e
chore(release): publish v4.2.3 2025-04-08 18:23:57 +01:00
mathuo
a92a056981
chore: correct versions 2025-04-08 18:23:44 +01:00
mathuo
c3e203f420
chore: update lerna 2025-04-08 18:22:33 +01:00
mathuo
8bdd15161c
chore: v4.2.3 docs 2025-04-08 18:19:12 +01:00
mathuo
8a3a7e5063
chore(release): publish v4.2.2 2025-03-26 22:15:46 +00:00
mathuo
65d95f1271
chore: v4.2.2 docs 2025-03-26 22:15:14 +00:00
mathuo
3ec6627013
Merge pull request #892 from mathuo/889-splitviewreact-and-gridviewreact-encounter-error-invalid-operation-resource-is-already-disposed
bug: remove reference to component once disposed preventing further u…
2025-03-26 22:14:59 +00:00
mathuo
c0227d620c
bug: remove reference to component once disposed preventing further usage 2025-03-26 22:08:31 +00:00
mathuo
aad168da84
chore(release): publish v4.2.1 2025-03-18 19:52:39 +00:00
mathuo
dfee4111a2
chore: v4.2.1 docs 2025-03-18 19:46:05 +00:00
mathuo
712f3f860d
Merge pull request #888 from mathuo/886-drag-and-drop-broken-after-latest-version-420
bug: fix options init
2025-03-18 19:45:23 +00:00
mathuo
f545a0a5c7
Merge pull request #887 from mathuo/884-cant-drop-tabs-between-panels-and-some-other-issues-with-v4-1
bug: dnd issues
2025-03-18 19:40:21 +00:00
mathuo
e3d39fd2ed
bug: fix options init 2025-03-18 19:38:32 +00:00
mathuo
dca0eb0df7
bug: dnd issues 2025-03-18 19:32:54 +00:00
mathuo
8188fd9429
chore(release): publish v4.2.0 2025-03-17 19:49:00 +00:00
mathuo
54a51ed10b
Merge branch 'master' of https://github.com/mathuo/dockview 2025-03-17 19:48:49 +00:00
mathuo
1a11a4ba2c
chore: docs 2025-03-17 19:48:37 +00:00
mathuo
b6d83df1aa
chore: v4.2.0 docs 2025-03-17 19:47:34 +00:00
mathuo
6444e4cb7d
Merge pull request #885 from mathuo/884-cant-drop-tabs-between-panels-and-some-other-issues-with-v4
feat: disableCustomScrollbars option
2025-03-17 19:47:13 +00:00
mathuo
ca7bd0a86d
bug: popover z-index 2025-03-17 19:29:06 +00:00
mathuo
dc828b7658
feat: disableCustomScrollbars option 2025-03-17 19:28:36 +00:00
mathuo
6e02e59fec
chore: v4.1.0 docs 2025-03-16 21:09:38 +00:00
mathuo
f79bbe0a6a
chore(release): publish v4.1.0 2025-03-16 21:08:08 +00:00
mathuo
12448e006f
chore: v4.1.0 docs 2025-03-16 21:07:51 +00:00
mathuo
5e380affe7
Merge pull request #882 from mathuo/676-is-it-possiable-that-the-id-and-title-of-group-can-be-assigned
676 is it possiable that the id and title of group can be assigned
2025-03-16 21:01:18 +00:00
mathuo
a44fc01f34
Merge branch 'master' of https://github.com/mathuo/dockview into 676-is-it-possiable-that-the-id-and-title-of-group-can-be-assigned 2025-03-16 20:48:41 +00:00
mathuo
8e03e584a7
Merge pull request #876 from mathuo/857-ondidrepositionpopoutgroup-event
feat: popout group resize events
2025-03-16 20:45:40 +00:00
mathuo
1ddcaefe80
test: add tests 2025-03-16 20:43:04 +00:00
mathuo
d086dfd081
Merge pull request #864 from dqhieuu/master
Make `id` option in addGroup without referenceGroup/referencePanel work
2025-03-16 20:40:54 +00:00
mathuo
b231367e44
Merge pull request #791 from mathuo/774-drag-and-drop-not-working-for-iframe-panels-within-a-shadow-dom
bug: disable iframes within shadowdom during dnd
2025-03-16 20:33:07 +00:00
mathuo
bba22fedd4
feat: popout group resize events 2025-03-16 20:28:35 +00:00
mathuo
3ebcb8eaef
Merge pull request #875 from mathuo/863-headercomponents-for-paneviewreact-is-never-used-and-cannot-set-height-because-harcoded-to-22px
feat: custom paneview header height
2025-03-16 20:26:33 +00:00
mathuo
39dd5f0759
feat: more efficient search 2025-03-16 20:23:39 +00:00
mathuo
e4f07dfbda
Merge pull request #881 from mathuo/721-loading-of-splitview-state-from-json-adds-extra-div-containers-that-break-layout
bug: remove splitview dom element
2025-03-16 20:09:31 +00:00
mathuo
d30263af67
bug: remove splitview dom element 2025-03-14 22:12:22 +00:00
mathuo
54ae457ccd
chore(release): publish v4.0.1 2025-03-14 22:01:56 +00:00
mathuo
416039bc99
chore: v4.0.1 docs 2025-03-14 21:59:59 +00:00
mathuo
e8fdabba08
Merge pull request #880 from mathuo/878-fullwidth-tabs-are-not-rendered-full-width-in-40
bug: fix full-width css
2025-03-14 21:59:08 +00:00
mathuo
9373802115
Merge pull request #879 from mathuo/877-missing-tab-divider-in-40
bug: fix tab divider css
2025-03-14 21:59:03 +00:00
mathuo
759dcc7b05
bug: fix full-width css 2025-03-14 21:57:37 +00:00
mathuo
f8faca731d
bug: fix tab divider css 2025-03-14 21:55:33 +00:00
mathuo
d168356344
Merge branch 'master' of https://github.com/mathuo/dockview into 774-drag-and-drop-not-working-for-iframe-panels-within-a-shadow-dom 2025-03-12 21:23:46 +00:00
mathuo
64e11a880c
bug: disable iframes within shadowdom during dnd 2025-03-12 21:16:21 +00:00
mathuo
a306742508
feat: custom paneview header height 2025-03-12 21:03:05 +00:00
mathuo
bffd2dea89
chore(release): publish v4.0.0 2025-03-12 20:25:13 +00:00
mathuo
ac92d59d0a
chore: v4.0.0 docs 2025-03-12 20:24:59 +00:00
mathuo
21d94b654b
chore: generate docs 2025-03-12 20:18:27 +00:00
mathuo
1030bdb778
feat: rename theme property 2025-03-12 20:16:57 +00:00
mathuo
082b811d20
Merge pull request #870 from mathuo/866-splitviewreact-rendered-twice-in-react-strict-mode
bug: remove element after dispose
2025-03-12 20:06:31 +00:00
mathuo
4eff83e9a0
bug: remove element after dispose 2025-03-10 21:35:17 +00:00
mathuo
090f2d26f8
chore: docs 2025-03-10 21:28:33 +00:00
mathuo
e34fb43913
chore: theme docs 2025-03-10 21:25:11 +00:00
mathuo
c0119d65c0
chore: fixup docs 2025-03-10 21:17:33 +00:00
mathuo
d3d57b62b2
Merge pull request #822 from mathuo/610-feature-request-dropdown-menu-to-handle-overflow-tabs-4
tabs overflow menu
2025-03-03 21:52:23 +00:00
mathuo
96d6947aa6
feat: scrollbars 2025-03-03 21:45:32 +00:00
mathuo
5fb13dd8a2
chore: update sonarqube build version 2025-03-03 20:47:54 +00:00
mathuo
cfe37766a9
feat: scrollbars 2025-03-03 20:47:44 +00:00
mathuo
7a6b2cb26d
feat: tab panel overflow dropdown 2025-02-27 21:15:38 +00:00
Hieu
6511b8d936
Make addGroup wiithout reference panel/group allow id option 2025-02-24 04:54:01 +07:00
mathuo
5754ddc3f4
Merge branch 'master' of https://github.com/mathuo/dockview into 610-feature-request-dropdown-menu-to-handle-overflow-tabs-4 2025-02-23 20:19:12 +00:00
mathuo
eec57c1cde
Merge pull request #850 from mathuo/849-improved-dnd-overlay-model
feat: improved dnd model
2025-02-23 20:16:37 +00:00
mathuo
3530c9896e
Merge branch 'master' of https://github.com/mathuo/dockview into 849-improved-dnd-overlay-model 2025-02-23 20:09:03 +00:00
mathuo
d811ca6554
feat: improved dnd model 2025-02-23 19:53:43 +00:00
mathuo
ab965ca726
chore(release): publish v3.2.0 2025-02-12 21:29:30 +00:00
mathuo
b13c841416
chore: v3.2.0 docs 2025-02-12 21:29:04 +00:00
mathuo
773bf5a53c
Merge pull request #860 from mathuo/859-added-support-for-sash-delay-and-duration-customization
859 added support for sash delay and duration customization
2025-02-12 21:28:34 +00:00
mathuo
837fabac86
feat: active sash animation css properties 2025-02-12 21:22:19 +00:00
mathuo
21caabce60
Merge pull request #858 from amirmamaghani/master
Added support for sash delay and duration customization
2025-02-12 21:15:52 +00:00
mathuo
14b918b7ae
chore: enhance examples page 2025-02-12 21:01:36 +00:00
Amir
f98be640ce Added support for sash delay and duration customization 2025-02-12 14:54:41 +01:00
mathuo
cd1aa81d93
Merge pull request #856 from mathuo/833-vanilla-js-expand-header-buttons
chore: add template example
2025-02-09 22:03:05 +00:00
mathuo
490fc05baf
chore: add template example 2025-02-09 14:49:01 +00:00
mathuo
16373429a8
chore(release): publish v3.1.1 2025-02-09 14:40:58 +00:00
mathuo
59fba25ade
Merge branch 'master' of https://github.com/mathuo/dockview 2025-02-09 14:40:47 +00:00
mathuo
cdd0183fa9
chore: v3.1.1 docs 2025-02-09 14:40:29 +00:00
mathuo
98cbe377c2
Merge pull request #855 from mathuo/853-middle-button-click---closes-tabs-with-hideclose
conditional middle-btn tab close
2025-02-09 14:38:46 +00:00
mathuo
d8da2f29c3
conditional middle-btn tab close 2025-02-08 15:38:31 +00:00
mathuo
8ed0fb5586
chore : v3.1.0 docs 2025-02-02 22:31:11 +00:00
mathuo
a56f01215b
chore : v3.1.0 docs 2025-02-02 22:30:58 +00:00
mathuo
e71d8b1d09
chore(release): publish v3.1.0 2025-02-02 22:30:35 +00:00
mathuo
71b99541b3
chore: v3.0.3 docs 2025-02-02 20:57:55 +00:00
mathuo
0e3afcf83f
Merge pull request #847 from mathuo/830-cannot-close-tab-with-middle-mouse-button
feat: close tab with middle btn
2025-01-31 22:16:59 +00:00
mathuo
1a85444b81
Merge pull request #848 from mathuo/835-crash-on-navigation-with-open-popup
835 crash on navigation with open popup
2025-01-31 22:16:40 +00:00
mathuo
19a22c49c3
bug: popup disposal runs after instance dispose 2025-01-30 20:40:18 +00:00
mathuo
06f02ba411
Merge pull request #845 from chris-mrl/fix-crash-open-popout-navigation
check tabToRemove is not undefined before access
2025-01-30 20:37:09 +00:00
mathuo
0e9c648fa9
feat: close tab with middle btn 2025-01-30 20:30:22 +00:00
mathuo
bb93c9e4c8
Merge pull request #843 from mathuo/842-api-init-bug
bug: setup onDidAcitvePanelChange subscription quicker
2025-01-28 20:50:49 +00:00
mathuo
c6dcef537f
bug: setup onDidAcitvePanelChange subscription quicker 2025-01-28 20:30:38 +00:00
Chris Nolan
3e409c4265
check tabToRemove is not undefined before access 2025-01-28 08:15:19 +00:00
mathuo
ca09ae537d
Create SECURITY.md 2025-01-27 22:35:43 +00:00
mathuo
e9985df262
Merge pull request #831 from milomg/patch-1
Fix vertical sash in replit theme
2025-01-27 19:05:30 +00:00
Milo Mighdoll
dc6bfc67e3
Fix vertical sash in replit theme 2025-01-20 12:42:31 -05:00
mathuo
5ca5ffac8d
tmp 2025-01-19 20:00:26 +00:00
mathuo
723272d6de
Merge branch 'master' of https://github.com/mathuo/dockview into 610-feature-request-dropdown-menu-to-handle-overflow-tabs-4 2025-01-14 21:24:37 +00:00
mathuo
6421a72653
chore: use dockview-core package metrics in README
Some checks failed
CI / build (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-01-14 18:01:59 +00:00
Jacek Nowacki
0c1b8ce4d7
Fix provenance npm link 2025-01-12 12:12:26 +01:00
Alex Errant
f40532f272
drop extraneous <BrowserHeader /> 2025-01-11 16:17:34 -06:00
mathuo
6b75b5093c
chore: fix docs 2025-01-11 21:04:44 +00:00
mathuo
c0cbe89835
chore(release): publish v3.0.2 2025-01-11 15:12:57 +00:00
mathuo
b46a473cdf
chore: v3.0.2 release notes 2025-01-11 15:00:00 +00:00
mathuo
239c99ffe5
chore: fix docs 2025-01-11 14:58:06 +00:00
mathuo
6678ae24e0
Merge pull request #825 from mathuo/818-container-with-theme-class-is-created-twice-1
bug: duplicate container HTML Element
2025-01-11 14:57:56 +00:00
mathuo
680eaca96c
Merge pull request #824 from mathuo/797-popout-group-flow-error-1
bug: fix floating->popout->floating group transition
2025-01-11 14:36:14 +00:00
mathuo
3d23a2d352
Merge branch 'master' of https://github.com/mathuo/dockview into 818-container-with-theme-class-is-created-twice-1 2025-01-11 14:34:28 +00:00
mathuo
c7c1ae9238
Merge pull request #816 from zaxer2/Making-React-Dockview-Demo-work-nice-with-Strict-Mode
Fixing duplicate state in React-Dockview-Demo caused by strict mode
2025-01-11 14:33:28 +00:00
mathuo
c122bfd310
fixup demo wrt React.StrictMode 2025-01-10 22:09:24 +00:00
mathuo
1a3c6ea7db
bug: duplicate container HTML Element 2025-01-10 22:08:37 +00:00
mathuo
c6865c691c
bug: fix floating->popout->floating group transition 2025-01-09 20:42:19 +00:00
mathuo
872ec7cba9
chore(release): publish v3.0.1 2025-01-09 20:17:19 +00:00
mathuo
2a3647db28
chore: v3.0.1 docs 2025-01-09 20:16:59 +00:00
mathuo
2f9af48146
Merge pull request #819 from mathuo/818-container-with-theme-class-is-created-twice
bug: duplicate root container
2025-01-09 20:07:39 +00:00
mathuo
04e4ea1a70
tabs overflow menu 2025-01-06 21:11:49 +00:00
mathuo
2d8d38b466
bug: duplicate root container 2025-01-06 20:14:14 +00:00
zaxer2
10fd5778dc
Copying Sandbox change to corresponding Template
see prev. commit
2025-01-03 13:24:28 -05:00
zaxer2
cd91f4a4d3
Updated react dockview-demo CodeSandbox to reset relevant state on subsequent onReady calls
Strict Mode causes the Dockview component to render twice, causing onReady to be called twice, which puts two copies of DefaultLayout in state without resetting between calls.

A most-ideal solution would probably be to expose an onUnload hook or something like that, to be called after one onReady but before the next. Then, the state resets could be called in that hook. but this fix will at least ensure the Dockview-Demo codesandbox behaves nicely for the time being.
2025-01-03 13:22:07 -05:00
mathuo
148a05a201
chore(release): publish v3.0.0 2024-12-29 14:17:33 +00:00
mathuo
8359596be0
chore: v3.0.0 docs 2024-12-29 14:17:13 +00:00
mathuo
cb45889aca
chore: sonar fixes 2024-12-29 13:55:54 +00:00
mathuo
5e116c80b4
Merge pull request #812 from mathuo/809-standardize-framework-components-1
chore: align api with existing components
2024-12-28 14:37:24 +00:00
mathuo
20eb30ed1c
chore: docs config 2024-12-28 14:30:30 +00:00
mathuo
5173922c69
chore: docs 2024-12-28 14:29:48 +00:00
mathuo
0ca56eaa8a
chore: align api with existing components 2024-12-28 14:21:23 +00:00
mathuo
c14b66e5e5
Merge pull request #811 from mathuo/784-class-dv-single-tab-not-updated-on-drag-drop-of-tabs
bug: fix classname enablement
2024-12-27 22:38:23 +00:00
mathuo
cc5037d208
bug: fix classname enablement 2024-12-27 19:52:23 +00:00
mathuo
a53665adc5
chore: autogen docs 2024-12-23 20:04:14 +00:00
mathuo
d799b5bc89
Merge pull request #810 from mathuo/809-standardize-framework-components
feat: migrate to generic construction
2024-12-23 19:58:33 +00:00
mathuo
fade057412
feat: migrate to generic construction 2024-12-23 19:51:51 +00:00
mathuo
f6e7e4e390
Merge pull request #806 from mathuo/805-rename-class-dockview-react-part-to-dv-react-part
feat: rename class
2024-12-23 15:56:06 +00:00
mathuo
d33babd522
Merge pull request #808 from mathuo/807-dockview-vue-vue-peer-dependency
chore: vue3 peerDependency
2024-12-23 15:54:06 +00:00
mathuo
7df9c510bd
chore: clean up project dependencies 2024-12-23 15:53:20 +00:00
mathuo
3479828237
chore: vue3 peerDependency 2024-12-23 15:52:53 +00:00
mathuo
b3ce3e0366
feat: rename class 2024-12-22 19:24:20 +00:00
mathuo
32fa1511d8
chore(release): publish v2.1.4 2024-12-22 19:20:36 +00:00
mathuo
e1304ce694
chore: docs 2024-12-22 19:20:24 +00:00
mathuo
bdf81fd5b5
Merge branch 'master' of https://github.com/mathuo/dockview 2024-12-22 19:20:06 +00:00
mathuo
cd7984cb2b
Merge pull request #804 from mathuo/803-popout-overlay-container-incorrect-for-floating-group-return
bug: fix overlay container for popout to floating
2024-12-22 19:20:01 +00:00
mathuo
491872ea75
chore: v2.1.4 docs 2024-12-22 19:19:38 +00:00
mathuo
296b49180e
bug: fix overlay container for popout to floating 2024-12-22 19:14:14 +00:00
mathuo
487d02bd90
Merge pull request #802 from mathuo/801-disposable-fix
chore: adjust dockview disposable
2024-12-22 18:54:07 +00:00
mathuo
4d01d4e0af
chore: adjust dockview disposable 2024-12-22 18:47:29 +00:00
mathuo
8650de90aa
chore(release): publish v2.1.3 2024-12-22 10:17:37 +00:00
mathuo
e45d9e48e8
Merge pull request #800 from mathuo/799-popout-group-new-group-rendering-bug
bug: popout group to new group rendering bug fix
2024-12-22 10:17:18 +00:00
mathuo
da750e89d7
chore: v2.1.3 docs 2024-12-22 10:13:24 +00:00
mathuo
81a904ccbc
bug: popout group to new group rendering bug fix 2024-12-22 10:10:26 +00:00
mathuo
6016362c5c
chore: docs 2024-12-22 09:41:57 +00:00
mathuo
7fe2e43d43
chore(release): publish v2.1.2 2024-12-21 19:25:31 +00:00
mathuo
56d4dc7c74
chore: v2.1.2 docs 2024-12-21 19:25:06 +00:00
mathuo
6fbe2b4233
Merge pull request #798 from mathuo/797-popout-group-flow-error
bug: fixup popout group flows
2024-12-21 19:24:50 +00:00
mathuo
f749372eb2
chore: memory leak fixes and tests 2024-12-21 14:22:53 +00:00
mathuo
c216d70354
chore: comments 2024-12-21 14:00:57 +00:00
mathuo
0d32d285d2
chore: remove testing code 2024-12-21 13:59:24 +00:00
mathuo
5d868e63ce
feat: additional event sequencing checks 2024-12-21 13:58:11 +00:00
mathuo
7515b9b05d
bug: fixup popout group flows 2024-12-21 13:33:09 +00:00
mathuo
703418e27f
chore: typo 2024-12-20 20:07:58 +00:00
mathuo
53cdf42d84
chore(release): publish v2.1.1 2024-12-20 20:07:17 +00:00
mathuo
140a61f401
chore: v2.1.1 docs 2024-12-20 20:06:49 +00:00
mathuo
ff0bf75632
Merge pull request #796 from mathuo/795-popout-groups-bug
bug: only dispose of ref group if empty
2024-12-20 20:05:02 +00:00
mathuo
dd918294d1
bug: only dispose of ref group if empty 2024-12-19 22:05:59 +00:00
mathuo
684b0593e7
chore: update auto-gen docs 2024-12-19 21:33:55 +00:00
mathuo
13c4a65e18
chore(release): publish v2.1.0 2024-12-17 22:17:55 +00:00
mathuo
755135c5ad
chore: fix demo 2024-12-17 22:10:49 +00:00
mathuo
15bf4bbd1f
chore: v2.1.0 docs 2024-12-17 21:39:01 +00:00
mathuo
429177436e
Merge pull request #789 from mathuo/788-sonar-fixes
chore: adjust sonar parameters
2024-12-10 20:20:29 +00:00
mathuo
cf6891368e
chore: adjust sonar parameters 2024-12-10 19:54:23 +00:00
mathuo
e2302372b1
chore: fix sonar issues 2024-12-10 19:30:32 +00:00
mathuo
f13f009155
chore: adjust sonar parameters 2024-12-10 19:21:16 +00:00
mathuo
c5025424c7
Merge pull request #783 from mathuo/777-keeping-header-actions-even-when-no-tabs-are-open
feat: ensure group always exists
2024-12-10 19:20:31 +00:00
mathuo
a66fdae8a3
Merge pull request #762 from mathuo/494-unable-to-persist-fullscreen-maximized-mode
feat: serialization of maximized views
2024-12-10 19:16:08 +00:00
mathuo
c777ff463d
Merge pull request #787 from mathuo/786-dockview-react-peer-dependencies
feat: specify react peerDependency
2024-12-10 19:15:03 +00:00
mathuo
04327b4613
Merge branch 'master' of https://github.com/mathuo/dockview into 777-keeping-header-actions-even-when-no-tabs-are-open 2024-12-10 19:12:59 +00:00
mathuo
a02241d1a7
feat: ensure group always exists 2024-12-10 19:11:46 +00:00
Daniel Goodwin
615886e666
Do not close group if it's the only group available 2024-12-10 19:11:17 +00:00
mathuo
1b921864fc
chore: fix sonar issue 2024-12-10 19:11:17 +00:00
mathuo
1ce96c8ab8
test: add tests 2024-12-10 19:11:16 +00:00
mathuo
d651c2213e
bug: fix setVisible on floating groups 2024-12-10 19:11:16 +00:00
Jafar Akhondali
1d61a47e7c
Block malicious looking requests to prevent path traversal attacks. 2024-12-10 19:11:16 +00:00
mathuo
aeb0e38249
feat: tab container empty space not focusable 2024-12-10 19:11:16 +00:00
mathuo
df3269d56e
chore: v2.0.0 docs 2024-12-10 19:11:16 +00:00
mathuo
0233e8fa42
chore(release): publish v2.0.0 2024-12-10 19:11:15 +00:00
mathuo
2bcd62412d
feat: specify react peerDependency 2024-12-10 19:01:18 +00:00
mathuo
32e759ef75
Merge pull request #785 from mathuo/782-dockview-fails-or-crashes-when-attempting-to-make-a-floating-group-from-a-popout-group
bug: wrong event ordering
2024-12-09 20:03:36 +00:00
mathuo
ede5a58ec9
Merge pull request #769 from mathuo/705-when-loading-stored-layout-having-popout-grooups-dockview-doesnt-respect-the-popouturl-passed-instead-it-defaults-it-to-popouthtml
feat: persist custom popout group urls
2024-12-09 19:41:44 +00:00
mathuo
56bdec846c
bug: wrong event ordering 2024-12-09 19:40:49 +00:00
mathuo
25489bf48e
Merge pull request #770 from mathuo/689-docs-demos-for-vanilla-js-support
chore: fix files
2024-11-15 21:19:03 +00:00
mathuo
9564319e43
chore: fix files 2024-11-15 21:18:09 +00:00
mathuo
461c30dc5b
feat: persist custom popout group urls 2024-11-15 21:03:38 +00:00
mathuo
be628a5683
Merge pull request #743 from mathuo/689-docs-demos-for-vanilla-js-support
chore: docs
2024-11-15 20:48:58 +00:00
mathuo
19f027a0db
chore: docs 2024-11-15 20:36:51 +00:00
mathuo
e46a586044
chore: remove old docs 2024-11-15 20:36:26 +00:00
mathuo
2f4150013b
feat: serialization of maximized views 2024-11-10 20:19:20 +00:00
mathuo
24cc974a68
Merge pull request #755 from mathuo/665-floating-group-setvisible-will-crash
bug: fix setVisible on floating groups
2024-11-10 14:29:57 +00:00
mathuo
a00875aae0
chore: fix sonar issue 2024-11-10 11:12:01 +00:00
mathuo
02d0ad8634
test: add tests 2024-11-10 10:58:21 +00:00
mathuo
93b29e77c4
Merge pull request #759 from JafarAkhondali/master
Fixing a Path Traversal Vulnerability
2024-11-10 10:42:03 +00:00
mathuo
df0d12128b
Merge pull request #761 from mathuo/760-remove-tabindex-from-empty-tab-space
feat: tab container empty space not focusable
2024-11-07 18:58:54 +00:00
mathuo
986132c602
feat: tab container empty space not focusable 2024-11-07 18:58:25 +00:00
Jafar Akhondali
fe39c475a2 Block malicious looking requests to prevent path traversal attacks. 2024-11-07 17:23:01 +01:00
mathuo
c9e90168a4
bug: fix setVisible on floating groups 2024-11-04 20:28:27 +00:00
mathuo
1a9ee8c34e
chore: v2.0.0 docs 2024-11-03 19:23:16 +00:00
mathuo
8a05aede4c
chore(release): publish v2.0.0 2024-11-03 19:21:57 +00:00
mathuo
83b01a9a66
Merge branch 'master' of https://github.com/mathuo/dockview into 689-docs-demos-for-vanilla-js-support 2024-11-03 19:19:03 +00:00
mathuo
7ebbd437a1
Merge pull request #752 from mathuo/739-sonar-code-cleanup
chore: sonar
2024-11-03 19:15:18 +00:00
mathuo
d8ae04e274
chore: sonar 2024-11-03 19:14:49 +00:00
mathuo
3f5c9743ad
Merge pull request #751 from mathuo/750-add-panel-in-a-specific-index-within-a-group-without-making-it-active
feat: addPanel positional index
2024-11-03 19:05:35 +00:00
mathuo
3d1fa25677
Merge pull request #744 from mathuo/717-setconstraints-no-longer-works
bug: fix constraints
2024-11-03 19:05:19 +00:00
mathuo
108bc49401
Merge pull request #748 from mathuo/747-fromjson-error-while-the-json-include-popoutgroups
bug: duplicate .close() call
2024-11-03 19:05:00 +00:00
mathuo
291e76afdb
feat: addPanel positional index 2024-11-03 15:12:23 +00:00
mathuo
2dc4f81b20
bug: fix constraints 2024-11-03 14:56:58 +00:00
mathuo
ca6f46ac8e
Merge pull request #749 from mathuo/716-css-variable-dv-background-color-is-always-black-which-breaks-theming-of-auxilliary-components
feat: remove --dv-background-color
2024-11-03 14:19:38 +00:00
mathuo
dad9eba474
feat: remove --dv-background-color 2024-11-02 21:11:21 +00:00
mathuo
adaeb16b98
bug: duplicate .close() call 2024-11-02 20:18:11 +00:00
mathuo
ee7cf637bb
Merge pull request #746 from mathuo/739-sonar-code-cleanup
chore: fix theme
2024-10-31 19:20:12 +00:00
mathuo
e70755e4fc
chore: update auto-gen api 2024-10-29 20:25:09 +00:00
mathuo
112dc2e2a9
Merge branch 'master' of https://github.com/mathuo/dockview into 739-sonar-code-cleanup 2024-10-29 20:24:16 +00:00
mathuo
5cf3cfb616
chore: docs 2024-10-29 20:22:40 +00:00
mathuo
f6f248c63a
chore: fix theme 2024-10-29 20:06:03 +00:00
mathuo
4bf3fc4820
Merge pull request #740 from mathuo/739-sonar-code-cleanup
chore: code cleanup
2024-10-27 20:09:46 +00:00
mathuo
40c1a26ead
chore: fix 2024-10-27 20:00:54 +00:00
mathuo
3f3ed52007
Merge pull request #742 from mathuo/741-remove-depreciated-method
chore: remove depricated method
2024-10-27 19:54:18 +00:00
mathuo
b812cf9117
chore: code cleanup 2024-10-27 19:53:29 +00:00
mathuo
210ff38cfa
chore: remove depricated method 2024-10-27 19:47:30 +00:00
mathuo
3ffb5034c6
Merge pull request #738 from mathuo/726-__dockview_internal_drag_event__-appearing-in-editor-text-after-dragdrop-of-panel
feat: use empty string to fill dataTransfer field
2024-10-27 10:37:26 +00:00
mathuo
2657c79620
feat: use empty string to fill dataTransfer field 2024-10-27 10:20:54 +00:00
mathuo
cd4cd0f0ce
Merge pull request #673 from mathuo/672-vu3-injectprovides-not-propagated-to-panels
bug: maintain inject/provide context
2024-10-27 09:48:55 +00:00
mathuo
408db93917
Merge pull request #737 from mathuo/736-bug-set-initial-size-and-use-proper-window-on-event
736 bug set initial size and use proper window on event
2024-10-27 09:40:35 +00:00
mathuo
f557e9080a
test: add tests 2024-10-26 22:05:07 +01:00
mathuo
e0744531ca
Merge pull request #688 from mathuo/687-css-class-prefixing
chore: rename classes to start with dv-
2024-10-26 21:57:54 +01:00
mathuo
3c680b251f
Merge pull request #727 from iammola/popout-group-sizing-event
🐛 fix: set initial size and use proper window on event
2024-10-26 21:52:43 +01:00
mathuo
c086cabd39
Merge branch 'master' of https://github.com/mathuo/dockview into 687-css-class-prefixing 2024-10-12 15:22:08 +01:00
mathuo
a202a49182
Merge pull request #731 from mathuo/710-reloading-of-popout-window-is-displaying-blank-page
bug: close popout window if unloaded
2024-10-12 15:20:51 +01:00
mathuo
efd77413da
chore: docs typo 2024-10-12 15:20:03 +01:00
mathuo
0097da7fde
chore(release): publish v1.17.2 2024-10-12 15:18:12 +01:00
mathuo
8c7369ccf0
chore: 1.17.2 release notes 2024-10-12 15:17:49 +01:00
mathuo
8015860bde
Merge pull request #730 from mathuo/720-allow-customization-of-the-default_overlay_z_index-value
720 allow customization of the default overlay z index value
2024-10-09 22:23:04 +01:00
mathuo
14382aa0fd
Merge pull request #715 from NotWearingPants/patch-2
docs: add the splash-screen to the dockview package
2024-10-09 22:16:57 +01:00
mathuo
7ddb63383f
bug: close popout window if unloaded 2024-10-09 22:16:27 +01:00
mathuo
ac56a1f250
feat: fixup z-index overlay override 2024-10-09 22:14:06 +01:00
mathuo
12bb6f6d20
Merge pull request #724 from amirmamaghani/master
Added support for custom overlay z-index
2024-10-09 21:53:27 +01:00
mathuo
bc455265cd
Merge pull request #714 from NotWearingPants/patch-1
docs: fix typo in core/panels/tabs
2024-10-04 20:09:27 +01:00
Ademola Adedeji
6881daa593 🐛 fix: set initial size and use proper window on event 2024-10-03 20:17:17 -03:00
Amir
3c90e9d196 Added support for custom overlay z-index 2024-10-01 08:48:32 +02:00
NotWearingPants
c55257ac56
docs: add the splash-screen to the dockview package 2024-09-18 02:10:39 +03:00
NotWearingPants
1b7f29e800
docs: fix typo in core/panels/tabs 2024-09-18 01:41:51 +03:00
mathuo
693a3cd6ef
chore: docs 2024-09-05 20:26:17 +01:00
mathuo
d1d8c8083e
chore(release): publish v1.17.1 2024-09-05 20:18:53 +01:00
mathuo
fd29ea570f
Merge branch 'master' of https://github.com/mathuo/dockview 2024-09-05 20:18:41 +01:00
mathuo
1acdd969a0
chore: v1.17.1 docs 2024-09-05 20:18:08 +01:00
mathuo
aeda5f4ca1
Merge pull request #709 from mathuo/696-touch-support-for-dragging-dockview-panels
bug: fix mouseup->pointerup
2024-09-05 20:16:36 +01:00
mathuo
5df4c5cce1
bug: fix mouseup->pointerup 2024-09-05 20:16:01 +01:00
mathuo
62fee4e7ec
chore(release): publish v1.17.0 2024-09-05 19:49:21 +01:00
mathuo
b24470fa42
chore: v1.17.0 release notes 2024-09-05 19:48:56 +01:00
mathuo
0c602afab5
Merge pull request #703 from mathuo/702-documentation
chore: docs
2024-09-05 19:33:13 +01:00
mathuo
ab5a11f13b
chore: docs 2024-09-05 19:20:37 +01:00
mathuo
fc7b9fb2e2
Merge pull request #698 from mathuo/696-touch-support-for-dragging-dockview-panels
feat: ensure use of pointerevents for touch support
2024-08-28 20:32:07 +01:00
mathuo
d519c239e3
Merge pull request #690 from mathuo/640-panel-and-group-default-sizes-and-bounding-dimensions
640 panel and group default sizes and bounding dimensions
2024-08-28 20:31:42 +01:00
mathuo
2b36844110
Merge pull request #693 from mathuo/640-panel-and-group-default-sizes-and-bounding-dimensions-1
feat: retain size when moving groups
2024-08-28 20:31:25 +01:00
mathuo
e2e91834ff
feat: retain size when moving groups 2024-08-27 21:16:14 +01:00
mathuo
72f457ab9d
feat: panel initial sizing 2024-08-27 20:59:38 +01:00
mathuo
1033b80783
feat: ensure use of pointerevents for touch support 2024-08-27 20:54:27 +01:00
mathuo
7adabec088
Merge pull request #694 from OneAndOnlyFinbar/master
Fix typo in replit theme
2024-08-24 21:40:33 +01:00
OneAndOnlyFinbar
4841d95573
Fix typo in replit theme
Remove duplicated pound sign.
2024-08-24 15:02:18 -04:00
mathuo
2dc0dafa5a
feat: addGroup initial sizes 2024-08-20 22:00:21 +01:00
mathuo
9d4f4cb534
feat: panel constraints 2024-08-20 21:52:11 +01:00
mathuo
05084030db
Merge pull request #686 from mathuo/651-offical-support-for-vanilla-typescript-1
chore: updateOptions logic
2024-08-16 19:16:42 +01:00
mathuo
4d71775804
chore: fix test 2024-08-16 18:54:33 +01:00
mathuo
77bccb3f69
chore: remove method 2024-08-15 21:39:45 +01:00
mathuo
affb8590dc
chore: rename classes to start with dv- 2024-08-15 20:52:53 +01:00
mathuo
6c39c448bb
chore: updateOptions logic 2024-08-15 20:43:00 +01:00
mathuo
a6a2d048c7
chore(release): publish v1.16.1 2024-08-13 19:45:23 +01:00
mathuo
464e255d56
chore: 1.16.1 docs 2024-08-13 19:44:51 +01:00
mathuo
0e8139217b
Merge pull request #685 from mathuo/684-multiple-classes-on-dockviewreact-element-doesnt-work-in-1160
bug: multiple classnames
2024-08-13 19:43:29 +01:00
mathuo
1876511c70
bug: multiple classnames 2024-08-13 19:37:24 +01:00
mathuo
56182aa60f
chore(release): publish v1.16.0 2024-08-11 21:48:35 +01:00
mathuo
bf35b265db
chore: 1.16.0 docs 2024-08-11 21:47:42 +01:00
mathuo
520aa39724
Merge pull request #683 from mathuo/656-renderer-always-causes-floating-group-z-index-bug
feat: correct z-index level for floating always rendered panel
2024-08-11 20:41:15 +01:00
mathuo
59acd94bc6
feat: correct z-index level for floating always rendered panel 2024-08-11 20:34:49 +01:00
mathuo
619b0b3276
Merge pull request #679 from mathuo/656-renderer-always-causes-floating-group-z-index-bug
656 renderer always causes floating group z index bug
2024-08-11 11:04:33 +01:00
mathuo
d686b2c2c7
chore: refactor and fix tests 2024-08-11 10:45:47 +01:00
mathuo
479d4bb322
Merge pull request #682 from mathuo/681-resize-handlers-position-is-wrong-for-more-than-2-panels-in-one-branch
681 resize handlers position is wrong for more than 2 panels in one branch
2024-08-11 10:33:17 +01:00
mathuo
49b1c5a174
bug: gap sizing fixes 2024-08-11 10:26:04 +01:00
mathuo
35b3ee2b0f
Merge pull request #667 from RayJason/fix/layout-view-size-calc
fix: splitview layout view size with hidden view
2024-08-10 12:37:25 +01:00
mathuo
a443bb5e41
Merge branch 'master' of https://github.com/mathuo/dockview into 656-renderer-always-causes-floating-group-z-index-bug 2024-08-06 21:43:40 +01:00
mathuo
a05d2af417
chore: fix test 2024-08-06 21:37:07 +01:00
mathuo
1efa5c0ef6
bug: always rendered floating groups z-index 2024-08-06 21:33:20 +01:00
mathuo
6bf0263797
Merge branch 'master' of https://github.com/mathuo/dockview into 656-renderer-always-causes-floating-group-z-index-bug 2024-08-05 21:35:44 +01:00
mathuo
ed3fd57d6a
Merge pull request #678 from mathuo/677-floating-panel-documentation-example-not-working
chore: fix documentation
2024-08-05 21:28:53 +01:00
mathuo
5a80b3a58d
Merge pull request #662 from mathuo/660-dragging-last-panel-from-popout-group-breaks-layout
feat: moving single panel out of popout group
2024-08-05 21:23:37 +01:00
mathuo
f022f3cb26
Merge pull request #652 from mathuo/651-offical-support-for-vanilla-typescript
feat: offical support for vanilla typescript
2024-08-05 21:23:10 +01:00
mathuo
af5ce5bbfd
chore: fix documentation 2024-08-05 21:18:57 +01:00
mathuo
ea5b94ad90
bug: maintain inject/provide context 2024-08-01 20:47:07 +01:00
mathuo
e5e9603221
chore(release): publish v1.15.3 2024-08-01 20:07:13 +01:00
mathuo
cc18f5055c
chore: v1.15.3 docs 2024-08-01 20:06:34 +01:00
mathuo
fafc18b419
Merge pull request #670 from mathuo/668-load-layout-will-not-keep-the-size-of-floating-group
bug: floating-groups deserialization
2024-08-01 20:06:19 +01:00
mathuo
bb312d871c
Merge pull request #666 from RayJason/fix/style-typo
fix: default tab ellipsis typo
2024-08-01 20:01:35 +01:00
mathuo
49561d14cc
bug: floating-groups deserialization 2024-08-01 20:00:11 +01:00
RayJason
1cd4251083 fix: mistake of sashes offset when custom gap 2024-07-30 23:10:22 +08:00
RayJason
ee74785d7e fix: calc views style 2024-07-30 21:11:36 +08:00
RayJason
eda3ea1210 fix: splitview layout view size with hidden view 2024-07-30 15:44:38 +08:00
RayJason
f689e38e59 fix: default tab ellipsis typo 2024-07-30 15:41:01 +08:00
mathuo
f765dd52fc
bug: render mode z-index fixes 2024-07-27 20:34:06 +01:00
mathuo
04d54e1dc5
feat: moving single panel out of popout group 2024-07-27 20:24:48 +01:00
mathuo
e93a008fe4
chore: docs 2024-07-23 20:42:09 +01:00
mathuo
5b9dbdf57e
chore(release): publish v1.15.2 2024-07-17 22:34:33 +01:00
mathuo
0e77bdf4d7
chore: v1.15.2 release notes 2024-07-17 22:34:13 +01:00
mathuo
0211148b66
Merge pull request #655 from mathuo/654-resize
bug: fix layout force flag
2024-07-17 22:33:50 +01:00
mathuo
973ecff0be
bug: fix layout force flag 2024-07-17 22:28:31 +01:00
mathuo
bdf286103f
bug: continue to check size change when force= 2024-07-17 19:53:55 +01:00
mathuo
e86155adf4
feat: offical support for vanilla typescript 2024-07-17 19:41:57 +01:00
mathuo
374bd2adff
chore(release): publish v1.15.1 2024-07-16 20:25:01 +01:00
mathuo
8618ed3aad
chore: v1.15.1 release notes 2024-07-16 20:23:19 +01:00
mathuo
aa39b55563
Merge pull request #648 from mathuo/647-115-removed-types-that-are-still-valid
bug: fixup types
2024-07-16 20:22:38 +01:00
mathuo
24e8c99dea
Merge pull request #645 from timadevelop/fix/popout-single-dockviewpanel
bug: fix passing through options for opening single panel in a popot window
2024-07-16 20:20:30 +01:00
mathuo
eeab0251d2
Merge pull request #649 from mathuo/613-gap-between-panels-leads-to-some-panels-size-overflow-2
bug: fix dockview  option
2024-07-16 18:59:15 +01:00
mathuo
2111e2293c
bug: fix dockview option 2024-07-15 21:24:06 +01:00
mathuo
bd28468bf5
bug: fixup types 2024-07-15 20:44:51 +01:00
Vlad Timofeev
05b1c27320 bug: fix passing through options for opening single panel in a popout window 2024-07-15 15:50:30 +03:00
mathuo
b326291f94
chore(release): publish v1.15.0 2024-07-10 21:41:09 +01:00
mathuo
8a4380ec14
Merge pull request #638 from mathuo/626-docs-1
chore: v1.15.0 docs
2024-07-10 21:09:39 +01:00
mathuo
eb3cac8b1b
chore: improve docs 2024-07-09 21:16:59 +01:00
mathuo
0db4f59990
chore: v1.15.0 docs 2024-07-06 22:09:39 +01:00
mathuo
aed7b97bac
Merge pull request #618 from mathuo/613-gap-between-panels-leads-to-some-panels-size-overflow-1
bug: panel gap styling
2024-07-06 22:07:21 +01:00
mathuo
9e4b3bbdd6
Merge pull request #616 from mathuo/582-expose-event-for-moving-tab-within-same-panel
feat: move panel events
2024-07-06 22:07:03 +01:00
mathuo
cd59248f34
Merge branch 'master' of https://github.com/mathuo/dockview into 582-expose-event-for-moving-tab-within-same-panel 2024-07-06 21:59:08 +01:00
mathuo
876b107284
feat: move panel events 2024-07-06 21:58:38 +01:00
mathuo
ea9dc13992
bug: panel gap styling 2024-07-06 21:22:34 +01:00
mathuo
f20b5285da
Merge pull request #628 from mathuo/544-floatinggroups-support-for-css-absolute-box-attributes-bottom-and-right
544 floatinggroups support for css absolute box attributes bottom and right
2024-07-03 22:18:34 +01:00
mathuo
9b46c1e7d9
chore: addFlotaingGroup method changes 2024-07-01 22:18:27 +01:00
mathuo
d7e1fd4bb2
Merge pull request #633 from mathuo/630-after-all-the-groups-in-the-branch-are-hidden-an-error-is-reported-when-calling-fromjson
feat: setVisible enhancements
2024-07-01 22:15:48 +01:00
mathuo
ae88703a8b
feat: setVisible enhancements 2024-07-01 20:29:03 +01:00
mathuo
cf8f18dbbd
test: fix tests 2024-06-19 22:17:59 +01:00
mathuo
b96ccf00ce
Merge branch 'master' of https://github.com/mathuo/dockview into 544-floatinggroups-support-for-css-absolute-box-attributes-bottom-and-right 2024-06-14 17:16:21 +01:00
mathuo
5285baebdb
Merge pull request #621 from crubier/master
Allow right and bottom anchored floating groups
2024-06-14 17:14:58 +01:00
mathuo
3652505a08
Merge pull request #627 from mathuo/626-docs
chore: docs
2024-06-08 23:51:13 +03:00
mathuo
c5f94906d4
chore: docs 2024-06-08 21:39:43 +01:00
mathuo
e3fb689d27
chore(release): publish v1.14.2 2024-06-08 20:01:33 +01:00
mathuo
93e5ecec4c
chore: 1.14.2 release notes 2024-06-08 20:00:47 +01:00
mathuo
82dbdb29c3
Merge pull request #625 from mathuo/624-dockview-vue-error-cannot-read-properties-of-null-reading-appcontext
bug: vue getInstance
2024-06-08 19:15:19 +03:00
mathuo
476b0a1d42
Merge pull request #623 from mathuo/622-settitle-issues
bug: fix setTitle issues
2024-06-08 19:15:08 +03:00
mathuo
e7622f6c2b
bug: vue getInstance 2024-06-08 16:55:15 +01:00
mathuo
cd6604d28c
bug: fix setTitle issues 2024-06-04 22:56:27 +03:00
Vincent Lecrubier
cffd742b7b Docs 2024-06-03 11:41:07 +01:00
Vincent Lecrubier
bbdb99dbb5 Support bottom and right anchor for Overlay 2024-06-03 11:25:38 +01:00
mathuo
ce381f8ce9
Merge pull request #620 from mathuo/562-dockview-framework-wrappers-vuejs-angular-javascript-etc-3
chore: vue docs
2024-05-30 21:06:37 +01:00
mathuo
1bd2bff8f8
chore: vue docs 2024-05-30 20:59:38 +01:00
mathuo
943c2dc425
Merge pull request #617 from mathuo/562-dockview-framework-wrappers-vuejs-angular-javascript-etc-3
chore: vue3 docs
2024-05-30 20:44:23 +01:00
mathuo
bc278f2beb
chore: vue3 docs 2024-05-30 20:37:57 +01:00
mathuo
b81afd45e8
chore(release): publish v1.14.1 2024-05-28 21:47:50 +01:00
mathuo
77b8023b56
Merge branch 'master' of https://github.com/mathuo/dockview 2024-05-28 21:46:51 +01:00
mathuo
963607defe
chore: 1.14.1 release notes 2024-05-28 21:46:46 +01:00
mathuo
4a0b328622
chore: update README.md files 2024-05-28 21:46:32 +01:00
mathuo
5762faac9d
Merge pull request #614 from mathuo/613-gap-between-panels-leads-to-some-panels-size-overflow
bug: --group-gap-size overflow fix
2024-05-28 21:42:20 +01:00
mathuo
7f750330b1
bug: --group-gap-size overflow fix 2024-05-24 19:50:25 +01:00
mathuo
55d9ca31d4
Merge pull request #612 from mathuo/611-sonar-suggestions
chore: sonar suggested fixes
2024-05-24 18:33:43 +01:00
mathuo
fde6764377
chore: sonar suggested fixes 2024-05-23 22:38:38 +01:00
mathuo
9c6dae30ca
chore(release): publish v1.14.0 2024-05-23 22:01:14 +01:00
mathuo
acadeedaf6
chore: v1.14.0 release notes 2024-05-23 22:00:04 +01:00
mathuo
fc84942b9b
Merge pull request #606 from mathuo/562-dockview-framework-wrappers-vuejs-angular-javascript-etc-2
562 dockview framework wrappers vuejs angular javascript etc 2
2024-05-14 21:03:21 +01:00
mathuo
e3eb851ab7
chore: configure dockview-react package 2024-05-14 20:42:33 +01:00
mathuo
84d02b9925
Merge pull request #605 from mathuo/603-sonarcloud-cleanups
chore: cleanup code
2024-05-14 20:39:57 +01:00
mathuo
3fcbf6515e
chore: configure dockview-vue package 2024-05-14 20:37:45 +01:00
mathuo
9306d9fcdc
chore: configure dockview-react package 2024-05-14 20:36:35 +01:00
mathuo
d29052e606
chore: cleanup code 2024-05-14 20:33:14 +01:00
mathuo
9ee2b821ff
Merge pull request #604 from mathuo/603-sonarcloud-cleanups
chore: sonarcloud requests
2024-05-13 21:33:27 +01:00
mathuo
c4ab6ac562
chore: sonarcloud requests 2024-05-13 20:50:05 +01:00
mathuo
35630e4f1c
Merge pull request #594 from mathuo/562-dockview-framework-wrappers-vuejs-angular-javascript-etc-1
feat: vue3
2024-05-13 19:42:45 +01:00
mathuo
07b548c6b6
Merge branch 'master' of https://github.com/mathuo/dockview into 562-dockview-framework-wrappers-vuejs-angular-javascript-etc-1 2024-05-13 19:34:34 +01:00
mathuo
7eff8a8642
Merge pull request #602 from mathuo/601-internal-clean-up-update-function
chore: cleanup internals
2024-05-13 19:34:02 +01:00
mathuo
abaad37edd
chore: cleanup internals 2024-05-13 18:11:24 +01:00
mathuo
b68d8df9ac
chore: clean code 2024-05-11 10:42:44 +01:00
mathuo
07d67b2246
chore: remove unused imports 2024-05-10 22:59:13 +01:00
mathuo
0ae16cf444
feat: vue3 fixes 2024-05-10 22:44:24 +01:00
mathuo
26cd1cc1cc
chore: configure dockview-vue package 2024-05-09 21:04:45 +01:00
mathuo
56457fe269
Merge branch 'master' of https://github.com/mathuo/dockview into 562-dockview-framework-wrappers-vuejs-angular-javascript-etc-1 2024-05-09 20:48:58 +01:00
mathuo
688abef81d
chore(release): publish v1.13.1 2024-05-05 19:59:28 +01:00
mathuo
4803109203
chore: 1.13.1 docs 2024-05-05 19:59:04 +01:00
mathuo
4d878a375c
Merge pull request #600 from mathuo/597-ondidlayoutchange-callback-catches-panels-init
597 ondidlayoutchange callback catches panels init
2024-05-05 19:50:39 +01:00
mathuo
0de2e57a1d
Merge branch 'master' of https://github.com/mathuo/dockview into 597-ondidlayoutchange-callback-catches-panels-init 2024-05-05 19:46:28 +01:00
mathuo
87d42c96e2
Merge pull request #598 from mathuo/596-moving-a-group-leaves-ghost-groups-that-cant-be-removed
bug: duplicate group added when added group with absolute position
2024-05-05 19:44:55 +01:00
mathuo
61abf780d1
feat: improve onDidLayoutChange events 2024-05-05 14:05:46 +01:00
mathuo
ffe7d59b74
bug/feat: use a better implementation of 'asap scheduled' (and buffered per event-cycle) events 2024-05-03 20:07:55 +01:00
mathuo
3dc590351c
bug: duplicate group added when added group with absolute position 2024-05-03 19:27:21 +01:00
mathuo
5d6055c4d2
feat: vue3 2024-05-01 20:35:07 +01:00
mathuo
d0eda81a9e
chore: docs 2024-04-28 14:34:21 +01:00
mathuo
db9703a150
chore: fix docs build 2024-04-27 13:48:18 +01:00
mathuo
f6e816fa10
chore: fix docs build 2024-04-27 13:37:55 +01:00
mathuo
9c091f5126
chore(release): publish v1.13.0 2024-04-27 13:19:52 +01:00
mathuo
c6403ff6e8
chore: 1.13.0 docs 2024-04-27 13:19:25 +01:00
mathuo
24817976f6
Merge pull request #588 from mathuo/562-dockview-framework-wrappers-vuejs-angular-javascript-etc
562 dockview framework wrappers vuejs angular javascript etc
2024-04-27 13:13:03 +01:00
mathuo
459963c13b
chore: fix error message 2024-04-27 13:02:35 +01:00
mathuo
eaabf7968c
Merge branch '562-dockview-framework-wrappers-vuejs-angular-javascript-etc' of https://github.com/mathuo/dockview into 562-dockview-framework-wrappers-vuejs-angular-javascript-etc 2024-04-27 13:02:07 +01:00
mathuo
521d2a0e6d
chore: docs 2024-04-26 22:13:37 +01:00
mathuo
2a3f623c30
Merge pull request #586 from mathuo/585-add-panel-event-unintentially-called-when-adding-floating-panel
bug: prevent unintential add_panel event
2024-04-25 23:12:29 +01:00
mathuo
33d8a7a026
bug: prevent unintential add_panel event 2024-04-25 23:06:56 +01:00
mathuo
dc684d05a7
Merge pull request #576 from mathuo/562-dockview-framework-wrappers-vuejs-angular-javascript-etc
feat: vue3 prep
2024-04-25 23:04:36 +01:00
mathuo
36527910f3
chore: docs 2024-04-25 22:14:39 +01:00
mathuo
4b1b4bab32
chore: fix tests 2024-04-25 21:10:07 +01:00
mathuo
2f0e34c99f
chore: fix import 2024-04-25 21:01:54 +01:00
mathuo
67fee68ebe
Merge branch 'master' of https://github.com/mathuo/dockview into 562-dockview-framework-wrappers-vuejs-angular-javascript-etc 2024-04-25 20:59:24 +01:00
mathuo
7eff1da988
feat: vue3 work 2024-04-25 20:58:51 +01:00
mathuo
ffbd7bcf04
Merge pull request #573 from mathuo/572-opening-a-new-tab-without-activating-it
572 opening a new tab without activating it
2024-04-25 20:00:31 +01:00
mathuo
dbe97b086c
Merge branch 'master' of https://github.com/mathuo/dockview into 562-dockview-framework-wrappers-vuejs-angular-javascript-etc 2024-04-23 21:45:01 +01:00
mathuo
b0ed0a99ff
feat: vue3 prep 2024-04-21 21:31:04 +01:00
mathuo
3c45c962b2
Merge pull request #578 from qcz/td/swap-width-height
Set width and height correctly when calling layout for floating panels
2024-04-19 19:28:48 +01:00
mathuo
f124414594
Merge pull request #577 from mathuo/imgbot
[ImgBot] Optimize images
2024-04-19 19:24:34 +01:00
Tar Dániel
715cd1e03d Set width and height correctly when calling layout for floating panels 2024-04-18 11:41:00 +02:00
ImgBotApp
d00c2207d9
[ImgBot] Optimize images
*Total -- 809.66kb -> 649.59kb (19.77%)

/packages/docs/static/img/Animation.gif -- 628.83kb -> 472.85kb (24.8%)
/packages/docs/static/img/react-icon.svg -- 0.36kb -> 0.33kb (9.02%)
/packages/docs/static/img/undraw_docusaurus_mountain.svg -- 30.75kb -> 29.20kb (5.04%)
/packages/docs/static/img/undraw_docusaurus_react.svg -- 35.16kb -> 33.60kb (4.43%)
/packages/docs/static/img/add_to_group.svg -- 1.99kb -> 1.91kb (3.93%)
/packages/docs/static/img/dockview_grid_4.svg -- 6.61kb -> 6.40kb (3.25%)
/packages/docs/static/img/add_to_tab.svg -- 1.60kb -> 1.56kb (2.38%)
/packages/docs/static/img/magnet_drop_positions.svg -- 5.03kb -> 4.93kb (1.98%)
/packages/docs/static/img/dockview_logo.svg -- 3.92kb -> 3.84kb (1.97%)
/packages/docs/static/img/drop_positions.svg -- 4.88kb -> 4.79kb (1.82%)
/packages/docs/static/img/undraw_docusaurus_tree.svg -- 11.61kb -> 11.42kb (1.62%)
/packages/docs/static/img/float_move.svg -- 5.47kb -> 5.42kb (0.93%)
/packages/docs/static/img/dockview_grid_3.svg -- 18.01kb -> 17.90kb (0.61%)
/packages/docs/static/img/js-icon.svg -- 0.67kb -> 0.67kb (0.44%)
/packages/docs/static/img/codesandbox_hint.svg -- 54.78kb -> 54.77kb (0.02%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2024-04-17 20:33:48 +00:00
mathuo
6ca39ba58b
Merge pull request #541 from mathuo/538-interested-in-porting-dockview-to-vuejs
feat: Vue 3 Support
2024-04-17 20:34:20 +01:00
mathuo
4a05fd0aed
feat: provide 'inactive' property to addPanel 2024-04-17 20:29:28 +01:00
mathuo
bc8fa59557
chore: fix jest debugging experience 2024-04-17 20:26:50 +01:00
mathuo
936499aa1e
chore: fix config 2024-04-17 19:49:11 +01:00
mathuo
9642dd844d
Merge branch 'master' of https://github.com/mathuo/dockview into 538-interested-in-porting-dockview-to-vuejs 2024-04-15 21:20:46 +01:00
mathuo
40e5c8053d
Merge branch '538-interested-in-porting-dockview-to-vuejs' of https://github.com/mathuo/dockview into 538-interested-in-porting-dockview-to-vuejs 2024-04-15 21:19:37 +01:00
mathuo
b0d98102fc
feat: multiple framework support work 2024-04-15 21:18:21 +01:00
mathuo
ec5b681196
feat: multiple framework support work 2024-04-15 21:09:33 +01:00
mathuo
5a1e41775f
chore(release): publish v1.12.0 2024-04-15 21:06:18 +01:00
mathuo
95685efbbd
chore: prepare v1.12.0 2024-04-15 21:05:30 +01:00
mathuo
ce3706d1b3
Merge pull request #570 from mathuo/569-expose-panel-component-id-via-the-panelapi
feat: expose api.(tab)component
2024-04-15 21:00:24 +01:00
mathuo
bb09f2ccf0
feat: expose api.(tab)component 2024-04-15 20:55:10 +01:00
mathuo
da0270066b
Merge pull request #566 from mathuo/564-add-groupdata-fields-to-onwillshowoverlays-event
feat: enhance onWillShowOverlay event
2024-04-14 15:37:37 +01:00
mathuo
baef193d78
feat: enhance onWillShowOverlay event 2024-04-14 15:24:27 +01:00
mathuo
61f3c252d4
Merge branch 'master' of https://github.com/mathuo/dockview into 538-interested-in-porting-dockview-to-vuejs 2024-03-27 21:32:22 +00:00
mathuo
b0e4c6d926
feat: multiple framework support work 2024-03-27 21:32:05 +00:00
mathuo
eb7d6b87d6
Merge pull request #559 from mathuo/531-fixtypo-onlywhenvisibile-onlywhenvisible
531 fixtypo onlywhenvisibile onlywhenvisible
2024-03-27 21:31:35 +00:00
mathuo
78a5534bab
Merge branch 'master' of https://github.com/madcodelife/dockview into 531-fixtypo-onlywhenvisibile-onlywhenvisible 2024-03-27 21:26:37 +00:00
mathuo
f79b7296e6
Merge branch 'master' of https://github.com/mathuo/dockview into 538-interested-in-porting-dockview-to-vuejs 2024-03-17 14:25:19 +00:00
mathuo
deb59eb94a
chore(release): publish v1.11.0 2024-03-17 14:23:08 +00:00
mathuo
331be77b08
chore: release notes 1.11.0 2024-03-17 14:22:51 +00:00
mathuo
b09ab48cd8
Merge pull request #556 from mathuo/555-import-react-vs-import-as-react
chore: adjust react import
2024-03-16 21:28:36 +00:00
mathuo
61eaae30c8
chore: adjust react import 2024-03-16 21:18:12 +00:00
mathuo
362cce88eb
Merge branch 'master' of https://github.com/mathuo/dockview into 538-interested-in-porting-dockview-to-vuejs 2024-03-15 20:47:49 +00:00
mathuo
09edbddf72
Merge pull request #553 from mathuo/552-css-should-only-be-in-dockview-core-package
chore: css exported only from core package
2024-03-15 20:36:18 +00:00
mathuo
4e54915a93
chore(release): publish v1.10.2 2024-03-15 20:26:50 +00:00
mathuo
4a9a791047
chore: css exported only from core package 2024-03-13 23:13:57 +00:00
mathuo
db747f1ead
feat: framework support 2024-03-11 22:00:02 +00:00
mathuo
e147255fcd
Merge branch '538-interested-in-porting-dockview-to-vuejs' of https://github.com/mathuo/dockview into 538-interested-in-porting-dockview-to-vuejs 2024-03-11 21:56:27 +00:00
mathuo
0b459800cc
Merge branch 'master' of https://github.com/mathuo/dockview into 538-interested-in-porting-dockview-to-vuejs 2024-03-11 21:55:44 +00:00
mathuo
c5770d4381
feat: Vue 3 Support 2024-03-11 21:54:30 +00:00
mathuo
ddcb42fe53
chore: update api docs 2024-03-11 21:37:38 +00:00
mathuo
5bd1f1e1c6
Merge branch 'master' of https://github.com/mathuo/dockview 2024-03-11 21:35:58 +00:00
mathuo
ba0b5f3985
chore: v1.10.2 docs 2024-03-11 21:35:52 +00:00
mathuo
451a8579d9
Merge pull request #549 from mathuo/542-access-panel-contentcomponent-via-the-panel-api
feat: expose component string
2024-03-11 21:18:50 +00:00
mathuo
da6704b517
Merge pull request #548 from mathuo/504-bug-updating-panel-parameters-does-not-trigger-ondidlayoutchange
504 bug updating panel parameters does not trigger ondidlayoutchange
2024-03-11 21:15:53 +00:00
mathuo
68f0319e1b
feat: expose component string 2024-03-11 21:12:00 +00:00
mathuo
c55c3e4a2f
feat: title and parameter events 2024-03-11 20:49:01 +00:00
mathuo
f93e5b613e
tmp 2024-03-11 20:03:29 +00:00
mathuo
1c33ee46a2
Merge pull request #530 from MichailShcherbakov/master
fix(panels): improve the reactivity of panel parameters and titles
2024-03-11 20:02:46 +00:00
mathuo
258be8594d
Merge branch '504-bug-updating-panel-parameters-does-not-trigger-ondidlayoutchange' into master 2024-03-11 20:02:08 +00:00
mathuo
b25264e190
feat: Vue 3 Support 2024-03-08 21:41:48 +00:00
mathuo
45919ff397
chore(release): publish v1.10.1 2024-03-02 22:31:11 +00:00
mathuo
c584f24a02
chore: docs 2024-03-02 22:23:34 +00:00
mathuo
9a9dc3500e
chore: docs 2024-03-02 22:13:25 +00:00
mathuo
0634f4fe52
chore: docs (#527)
* chore: docs
2024-03-02 21:49:09 +00:00
mathuo
f00b3d88e1
Merge pull request #535 from mathuo/534-setvisible-is-missing-from-splitviewpanelapiimpl
feat: cleanup setVisible api
2024-03-02 20:49:14 +00:00
mathuo
d3b22d83cf
feat: cleanup setVisible api 2024-03-02 16:24:24 +00:00
mathuo
2ee9b46eb9
Merge pull request #533 from mathuo/imgbot
[ImgBot] Optimize images
2024-02-29 22:05:06 +00:00
ImgBotApp
2f1f35bd31
[ImgBot] Optimize images
*Total -- 857.56kb -> 678.27kb (20.91%)

/packages/docs/src/misc/math/visual_1.jpg -- 34.74kb -> 25.91kb (25.42%)
/packages/docs/src/misc/math/constraints.jpg -- 134.20kb -> 103.55kb (22.84%)
/packages/docs/static/img/splashscreen.gif -- 654.69kb -> 515.26kb (21.3%)
/packages/docs/static/img/dockview_grid.svg -- 2.75kb -> 2.68kb (2.73%)
/packages/docs/static/img/dockview_splash_2.svg -- 3.59kb -> 3.50kb (2.67%)
/packages/docs/static/img/dockview_grid_2.svg -- 3.57kb -> 3.49kb (2.46%)
/packages/docs/static/img/add_to_empty_space.svg -- 1.62kb -> 1.58kb (2.36%)
/packages/docs/static/img/float_add.svg -- 4.58kb -> 4.54kb (0.77%)
/packages/docs/static/img/float_group.svg -- 17.81kb -> 17.77kb (0.24%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2024-02-29 20:35:03 +00:00
Sherbakov Mikhail
465141c097
fix(panels): improve the reactivity of panel parameters and titles 2024-02-27 19:57:31 +03:00
Floyd Wang
0d34d7285e fix(typo): onlyWhenVisibile -> onlyWhenVisible 2024-02-27 19:29:54 +08:00
mathuo
9ee00ab423
Merge pull request #526 from mathuo/525-onwillshowoverlay-to-handle-edge-drops
handle edge drops for onWillShowOverlay
2024-02-25 16:55:57 +00:00
mathuo
12c873c7cf
replace DockviewDropTragets with DockviewGroupDropLocation 2024-02-25 16:12:49 +00:00
mathuo
5a635336b3
handle edge drops for onWillShowOverlay 2024-02-25 16:09:48 +00:00
mathuo
60f49ce449
chore: docs 2024-02-25 16:08:26 +00:00
mathuo
50608f4520
chore: update 1.10.0 release notes date 2024-02-25 15:54:38 +00:00
mathuo
566fc0a7c8
chore(release): publish v1.10.0 2024-02-25 15:44:12 +00:00
mathuo
d2baa1371c
Merge pull request #524 from mathuo/475-improved-docs-1
chore: fix docs
2024-02-25 15:25:52 +00:00
mathuo
814a8e4dae
chore: fix docs 2024-02-25 15:25:21 +00:00
mathuo
e85863b65f
475 improved docs (#484)
* chore: docs
2024-02-25 12:32:58 +00:00
mathuo
cc50199cff
Merge pull request #522 from mathuo/395-showdndoverlay-is-not-called-always-2
feat: simplify event
2024-02-24 21:30:44 +00:00
mathuo
8533a18fea
feat: simplify event 2024-02-24 21:19:17 +00:00
mathuo
fd2460416a
Merge pull request #518 from mathuo/479-is-there-any-way-to-focus-the-panel-content
feat: events
2024-02-19 20:57:42 +00:00
mathuo
889e4d200c
feat: events 2024-02-19 20:57:15 +00:00
mathuo
14b7e8de80
Merge pull request #517 from mathuo/479-is-there-any-way-to-focus-the-panel-content
479 is there any way to focus the panel content
2024-02-19 20:55:31 +00:00
mathuo
5fddff72e7
Merge branch '479-is-there-any-way-to-focus-the-panel-content' of https://github.com/mathuo/dockview into 479-is-there-any-way-to-focus-the-panel-content 2024-02-19 20:51:57 +00:00
mathuo
775145f176
feat: events 2024-02-19 20:51:50 +00:00
mathuo
5418538874
Merge pull request #514 from mathuo/509-source-map-parsing-fails-when-used-in-create-react-app-typescript-project
chore: remove source maps
2024-02-19 20:46:20 +00:00
mathuo
05ff795188
Merge pull request #516 from mathuo/447-feature-request-adding-a-gap-around-panels
feat: correct contain size
2024-02-19 20:45:58 +00:00
mathuo
5c945e97d6
feat: correct contain size 2024-02-19 20:20:24 +00:00
mathuo
68d36612dc
chore: remove source maps 2024-02-18 09:29:25 +00:00
mathuo
70014a8c10
Merge pull request #513 from mathuo/512-expose-close-on-group-api
feat: group.api.close
2024-02-16 22:51:02 +00:00
mathuo
bb40c45a29
Merge pull request #511 from mathuo/448-drop-target-selection-transformation-scales-borders-and-outlines
feat: dnd overlay fix
2024-02-16 22:50:53 +00:00
mathuo
a84bdb9182
Merge pull request #510 from mathuo/479-is-there-any-way-to-focus-the-panel-content
feat: fix rendering
2024-02-16 22:49:01 +00:00
mathuo
d379b111ee
feat: group.api.close 2024-02-16 22:45:49 +00:00
mathuo
157ea8a3d1
feat: dnd overlay fix 2024-02-16 22:44:39 +00:00
mathuo
9523e6fb48
feat: fix rendering 2024-02-16 22:43:46 +00:00
mathuo
7b0cc492e7
Merge pull request #508 from mathuo/479-is-there-any-way-to-focus-the-panel-content
feat: fixes
2024-02-13 20:31:19 +00:00
mathuo
ed40e224e7
feat: fixes 2024-02-13 20:24:56 +00:00
mathuo
53de69a61a
Merge pull request #502 from mathuo/501-ensure-group-is-active-after-being-maximized
bug: maximized group must be active
2024-02-09 21:09:14 +00:00
mathuo
6b2ed70a7c
bug: maximized group must be active 2024-02-09 21:02:39 +00:00
mathuo
de93234632
Merge pull request #499 from mathuo/498-dockview-does-not-support-reactmemo-for-components
feat: accept a wider range of React components
2024-02-09 20:28:08 +00:00
mathuo
024c2d5349
Merge pull request #500 from mathuo/495-api-ondidmaximizedgroupchange-misspelled-as-ondidmaxmizedgroupchange
chore: fix typos
2024-02-09 20:28:00 +00:00
mathuo
e7ab35a30e
chore: fix typos 2024-02-09 20:21:45 +00:00
mathuo
874da1c7d9
feat: accept a wider range of React components 2024-02-09 20:19:55 +00:00
mathuo
30c7fa2c6a
Merge pull request #497 from mathuo/479-is-there-any-way-to-focus-the-panel-content
feat: events
2024-02-08 21:35:30 +00:00
mathuo
d2b9493fb4
feat: events 2024-02-08 21:24:13 +00:00
mathuo
8d56a2ec3a
Merge pull request #482 from mathuo/479-is-there-any-way-to-focus-the-panel-content
feat: align focus/active methods
2024-02-08 20:18:56 +00:00
mathuo
284fb1440b
feat: events cleanup 2024-02-08 19:38:52 +00:00
mathuo
807ccf80de
feat: events cleanup 2024-02-07 21:55:57 +00:00
mathuo
89fe866ac5
Merge branch 'master' of https://github.com/mathuo/dockview into 479-is-there-any-way-to-focus-the-panel-content 2024-02-03 14:02:21 +00:00
mathuo
cb69d47926
Merge pull request #490 from mathuo/469-add-window-lifecycle-callbacks-1
feat: popout persistance logic
2024-02-03 13:49:59 +00:00
mathuo
9e57a8691a
feat: popout persistance logic 2024-02-01 19:51:07 +00:00
mathuo
f7b173f7df
Merge pull request #488 from mathuo/469-add-window-lifecycle-callbacks-1
feat: fix popout group persistance
2024-01-31 21:44:54 +00:00
mathuo
c4f46a190a
feat: fix popout group persistance 2024-01-31 21:39:16 +00:00
mathuo
d488e8c3d9
Merge pull request #461 from mathuo/460-locked-mode-prevent-all-mouse-resizing
feat: locked mode
2024-01-30 19:58:31 +00:00
mathuo
c70327e268
Merge pull request #485 from mathuo/448-drop-target-selection-transformation-scales-borders-and-outlines
feat: replace transform with top,left,width,height
2024-01-30 19:58:20 +00:00
mathuo
d8cb5ac3f8
Merge branch 'master' of https://github.com/mathuo/dockview 2024-01-30 19:57:58 +00:00
mathuo
9b641f64be
chore: migrate to codeql v3 2024-01-30 19:57:46 +00:00
mathuo
aeccce639c
chore: migrate to v4 build actions 2024-01-30 19:56:40 +00:00
mathuo
1d7ab22027
Merge branch 'master' of https://github.com/mathuo/dockview into 460-locked-mode-prevent-all-mouse-resizing 2024-01-30 19:51:56 +00:00
mathuo
0b1a09d910
feat: replace transform with top,left,width,height 2024-01-30 19:50:40 +00:00
mathuo
5fc8a6a4d3
Merge pull request #487 from mathuo/469-add-window-lifecycle-callbacks-1
feat: popout group enhancements
2024-01-30 19:45:19 +00:00
mathuo
66d96cf6ae
chore: upgrade builds to node v20 2024-01-30 19:43:27 +00:00
mathuo
926b688267
chore: stop nightly docs job 2024-01-30 19:41:05 +00:00
mathuo
0bca63b550
feat: popout group enhancements 2024-01-30 17:41:04 +00:00
mathuo
c2791c6124
Merge pull request #453 from mathuo/395-showdndoverlay-is-not-called-always
work-in-progress
2024-01-29 22:42:22 +00:00
mathuo
e1849f72a5
chore: internals renaming 2024-01-29 22:40:43 +00:00
mathuo
fb6aba1d7f
Merge branch 'master' of https://github.com/mathuo/dockview into 395-showdndoverlay-is-not-called-always 2024-01-29 22:29:21 +00:00
mathuo
e28c76626f
Merge branch 'master' of https://github.com/mathuo/dockview into 460-locked-mode-prevent-all-mouse-resizing 2024-01-29 22:28:42 +00:00
mathuo
aa8e7e09e0
Merge pull request #481 from mathuo/469-add-window-lifecycle-callbacks-1
feat: provide means to obtain popoutWindow document
2024-01-29 22:27:45 +00:00
mathuo
47b691b6b4
Merge pull request #454 from mathuo/447-feature-request-adding-a-gap-around-panels
work-in-progress: group gaps
2024-01-29 22:22:28 +00:00
mathuo
20c1a66d20
feat: popout group enhancements 2024-01-29 22:21:52 +00:00
mathuo
8f9d225c61
feat: window popout enhancements 2024-01-29 20:57:37 +00:00
mathuo
0127857544
feat: group gaps 2024-01-28 13:57:13 +00:00
mathuo
6274708acb
feat: align event names 2024-01-28 13:43:09 +00:00
mathuo
a8472e8b71
Merge branch '469-add-window-lifecycle-callbacks' of https://github.com/mathuo/dockview into 469-add-window-lifecycle-callbacks-1 2024-01-28 13:38:02 +00:00
mathuo
586400019f
feat: align focus/active methods 2024-01-27 20:26:00 +00:00
mathuo
0fd3a669c7
test 2024-01-27 19:21:09 +00:00
mathuo
a24dd21ca2
feat: provide means to obtain popoutWindow document 2024-01-27 14:21:56 +00:00
mathuo
b901f4de0c
Merge pull request #477 from mathuo/madcodelife-master
chore: typos
2024-01-23 19:43:45 +00:00
mathuo
c7eb2dc105
Merge branch 'master' of https://github.com/madcodelife/dockview into madcodelife-master 2024-01-23 18:47:45 +00:00
mathuo
c11d65bef7
chore(release): publish v1.9.2 2024-01-23 17:37:01 +00:00
mathuo
f4866c9f89
chore: docs 2024-01-23 17:36:51 +00:00
mathuo
e9cf1bfd9f
chore: 1.9.2 docs 2024-01-23 17:33:53 +00:00
mathuo
e47ccdea6a
Merge pull request #474 from mathuo/472-panel-loses-content
bug: fix rendering issue
2024-01-23 17:31:26 +00:00
mathuo
a855c55132
bug: fix rendering issue 2024-01-23 17:25:06 +00:00
Floyd Wang
8f24d6b366 fix: typo 2024-01-23 19:21:17 +08:00
mathuo
75066f2420
Merge pull request #468 from mathuo/465-adding-group-doesnt-allow-for-group-creation-options
465 adding group doesnt allow for group creation options
2024-01-22 22:34:53 +00:00
mathuo
1cc340f3f1
Merge pull request #470 from sachnk/469-add-window-lifecycle-callbacks
Add window-lifecycle callbacks
2024-01-22 20:12:37 +00:00
sachnk
23b1edb003
add window-lifecycle callbacks 2024-01-22 19:02:53 +00:00
mathuo
87e52f88d0
Merge pull request #466 from NaNgets/465-add-group-with-options
Add group with options.
2024-01-22 18:48:58 +00:00
NaNgets
6499e78544
Fix dockview component interface addGroup. 2024-01-21 22:52:44 +02:00
NaNgets
02af66a10f
PR comments. 2024-01-21 22:51:19 +02:00
NaNgets
a6a6ffc058
Fix indentation. 2024-01-21 15:02:45 +02:00
NaNgets
03a39507dc
Add group with options. 2024-01-21 15:00:35 +02:00
mathuo
502a984d2b
chore(release): publish v1.9.1 2024-01-20 15:05:40 +00:00
mathuo
1762389406
Merge pull request #462 from mathuo/432-dockview-191-iteration-plan
chore: 1.9.1 docs
2024-01-20 13:54:15 +00:00
mathuo
87b999fe46
chore: 1.9.1 docs 2024-01-20 13:51:31 +00:00
mathuo
b0a5e66cff
Merge pull request #464 from mathuo/451-provide-drop-target-dropzone-quadrant-class
feat: rename class
2024-01-20 10:02:37 +00:00
mathuo
16e0971275
feat: rename class 2024-01-20 10:02:18 +00:00
mathuo
cae8f3664e
Merge pull request #463 from mathuo/451-provide-drop-target-dropzone-quadrant-class
feat: drop target classnames
2024-01-19 15:16:42 +00:00
mathuo
9ccc7add41
feat: drop target classnames 2024-01-19 15:16:14 +00:00
mathuo
22ba48e81e
feat: locked mode 2024-01-19 14:23:52 +00:00
mathuo
6a1f47d4da
feat: expose onWillDrop 2024-01-19 13:35:46 +00:00
mathuo
8da26f1484
Merge branch 'master' of https://github.com/mathuo/dockview into 395-showdndoverlay-is-not-called-always 2024-01-18 19:33:07 +00:00
mathuo
512a8d2c72
feat: dnd control changes 2024-01-18 19:32:48 +00:00
mathuo
6c670c1fbb
Merge pull request #431 from mathuo/430-support-droptarget-size-options
feat: expose dockview root droptarget overlay options (work-in-progress)
2024-01-18 19:32:27 +00:00
mathuo
cc88096413
feat: expose dockview root droptarget overlay options 2024-01-18 16:08:38 +00:00
mathuo
e5334f0fd7
Merge pull request #458 from mathuo/455-floating-groups-lose-size-if-instance-toggled-with-display-none
feat: short-circuit resize calls when display:none
2024-01-18 16:02:30 +00:00
mathuo
a6bafcc063
feat: short-circuit resize calls when display:none 2024-01-18 15:30:26 +00:00
mathuo
c61cb1a6e8
Merge pull request #449 from mathuo/446-content-container-does-not-scroll
feat: scrollbar as default
2024-01-17 22:54:51 +00:00
mathuo
c49fcd4d9d
Merge pull request #452 from mathuo/451-provide-drop-target-dropzone-quadrant-class
feat: provide classnames for droptarget overlays
2024-01-17 22:43:50 +00:00
mathuo
e3e87f4133
feat: provide classnames for droptarget overlays 2024-01-17 22:19:43 +00:00
mathuo
0df4062de4
feat: scrollbar as default 2024-01-17 19:57:24 +00:00
mathuo
8a67f60747
chore(release): publish v1.9.0 2024-01-15 20:24:39 +00:00
mathuo
90690fa8ea
Revert "chore: 1.9.0"
This reverts commit 9e245e6f5e.
2024-01-15 20:20:05 +00:00
mathuo
9e245e6f5e
chore: 1.9.0 2024-01-15 20:11:40 +00:00
mathuo
ffddc95602
chore: update docs (#429)
* chore: 1.9.0 docs
2024-01-15 20:03:20 +00:00
mathuo
5ca1cc5e62
Merge pull request #442 from mathuo/360-investigate-opening-tabs-in-new-browser-window
feat: update watermark for popout windows
2024-01-11 15:21:43 +00:00
mathuo
9c44a08a31
feat: update watermark for popout windows 2024-01-11 15:21:26 +00:00
mathuo
1d9b285bf4
Merge pull request #441 from mathuo/397-gready-rendering-mode
bug: destroy overlay render when detached
2024-01-11 11:33:30 +00:00
mathuo
31596772a7
test: render overlay container 2024-01-11 11:26:49 +00:00
mathuo
0380b4fda0
bug: destroy overlay render when detached 2024-01-11 11:03:41 +00:00
mathuo
c2ae86de3a
Merge pull request #439 from mathuo/397-gready-rendering-mode
feat: render mode
2024-01-10 23:14:13 +00:00
mathuo
718e3344ca
feat: render mode 2024-01-10 23:08:44 +00:00
mathuo
ccef43cb82
Merge branch 'master' of https://github.com/mathuo/dockview into 397-gready-rendering-mode 2024-01-10 22:34:10 +00:00
mathuo
d0d6508ae3
feat: render mode 2024-01-10 22:23:35 +00:00
mathuo
b084ac6e00
Merge pull request #438 from mathuo/360-investigate-opening-tabs-in-new-browser-window
feat: popout window unique id
2024-01-10 20:01:25 +00:00
mathuo
dbe0fe88ad
feat: popout window unique id 2024-01-10 20:00:50 +00:00
mathuo
3ec3c8358a
Merge pull request #437 from mathuo/360-investigate-opening-tabs-in-new-browser-window
feat: popout window position account for window.screen position
2024-01-10 15:42:57 +00:00
mathuo
312aeded25
feat: popout window position account for window.screen position 2024-01-10 15:42:27 +00:00
mathuo
16a0bc5b3e
Merge pull request #436 from mathuo/361-full-screen-mode-for-a-single-tab
test: maximize view cleanup
2024-01-10 13:33:03 +00:00
mathuo
c7d7749308
test: maximize view cleanup 2024-01-10 13:31:45 +00:00
mathuo
8ca80b542d
Merge pull request #435 from mathuo/397-gready-rendering-mode
chore: remove unused code
2024-01-10 13:18:39 +00:00
mathuo
e686ecb9d5
chore: remove unused code 2024-01-10 13:18:22 +00:00
mathuo
0b67d8e50b
Merge pull request #434 from mathuo/361-full-screen-mode-for-a-single-tab
feat: do not persist maximized view state
2024-01-10 13:10:13 +00:00
mathuo
da269aa71b
feat: do not persist maximized view state 2024-01-10 13:09:48 +00:00
mathuo
4c9caadcf5
Merge pull request #433 from mathuo/361-full-screen-mode-for-a-single-tab
feat: set minimum panel size > 0
2024-01-10 11:04:10 +00:00
mathuo
3df1642d91
feat: set minimum panel size > 0 2024-01-10 11:00:07 +00:00
mathuo
2c77b7e208
chore: fix demo 2024-01-10 09:49:16 +00:00
mathuo
64076b2bdd
Merge pull request #426 from mathuo/425-dockview-inside-shadowdom
bug: resizable should work within shadow dom
2024-01-09 21:49:51 +00:00
mathuo
5e45b9a469
bug: resizable should work within shadow dom 2024-01-09 21:42:04 +00:00
mathuo
b78a7a6207
Merge pull request #422 from mathuo/360-investigate-opening-tabs-in-new-browser-window
test: add tests
2024-01-08 22:03:44 +00:00
mathuo
105be3661f
feat: popout groups 2024-01-08 21:54:33 +00:00
mathuo
aa6eb27d7d
test: add tests 2024-01-07 16:18:07 +00:00
mathuo
22d5067395
chore: docs 2024-01-05 16:12:01 +00:00
mathuo
32d668f69d
chore: docs 2024-01-05 15:39:20 +00:00
mathuo
4b616c5578
Merge pull request #421 from mathuo/397-gready-rendering-mode
feat: gready rendering
2024-01-04 19:53:32 +00:00
mathuo
10256672b4
test: add tests 2024-01-03 20:29:58 +00:00
mathuo
43548618ba
Merge branch 'master' of https://github.com/mathuo/dockview into 397-gready-rendering-mode 2024-01-03 19:08:37 +00:00
mathuo
9b1c366ce5
feat: gready rendering 2024-01-03 16:25:35 +00:00
mathuo
5a2d7394be
Merge pull request #419 from mathuo/361-full-screen-mode-for-a-single-tab
test: gridview tests
2024-01-03 13:56:47 +00:00
mathuo
b222de86f7
feat: enhance maximize group api methods 2024-01-03 13:51:43 +00:00
mathuo
3e9f96bdbf
test: gridview tests 2024-01-03 13:09:55 +00:00
mathuo
4045689bd0
chore: fix sonar issues 2024-01-02 15:22:55 +00:00
mathuo
ecf12b175e
chore: allow manual docs build 2024-01-02 15:02:42 +00:00
mathuo
740ded45f4
Merge pull request #362 from mathuo/361-full-screen-mode-for-a-single-tab
wip: experiment with fullscreen tabs
2024-01-02 15:00:19 +00:00
mathuo
bb1a7934c0
Merge pull request #418 from mathuo/417-upgrade-docasaurus
chore: upgrade deps
2024-01-02 14:49:43 +00:00
mathuo
ea51b5c883
chore: upgrade deps 2024-01-02 14:43:32 +00:00
mathuo
5ec847ec77
chore: fix docs build 2024-01-02 14:28:32 +00:00
mathuo
a18b95f9ad
feat: full-screen-groups 2024-01-01 22:49:23 +00:00
mathuo
cb203bdf1e
Merge branch 'master' of https://github.com/mathuo/dockview into 361-full-screen-mode-for-a-single-tab 2024-01-01 22:47:08 +00:00
mathuo
cf6ee14f3d
Merge pull request #416 from mathuo/397-gready-rendering-mode
bug: fix title renderer
2024-01-01 22:46:56 +00:00
mathuo
6a620d4088
bug: fix title renderer 2024-01-01 22:46:15 +00:00
mathuo
bd2d8d7bf6
feat: maximized groups 2024-01-01 22:33:34 +00:00
mathuo
c195fa19bf
Merge branch 'master' of https://github.com/mathuo/dockview into 361-full-screen-mode-for-a-single-tab 2024-01-01 22:30:21 +00:00
mathuo
eb0172f06c
Merge pull request #364 from mathuo/360-investigate-opening-tabs-in-new-browser-window
work-in-progress: popout windows
2024-01-01 22:28:00 +00:00
mathuo
fa25c55655
Merge pull request #415 from mathuo/414-css-styles-for-focused-tab
feat: indicate focused tab with css style
2024-01-01 21:45:27 +00:00
mathuo
406af8a87f
feat: popout windows 2024-01-01 21:43:07 +00:00
mathuo
ff6d40a545
feat: indicate focused tab with css style 2024-01-01 21:40:53 +00:00
mathuo
ca12d7d920
Merge branch 'master' of https://github.com/mathuo/dockview into 361-full-screen-mode-for-a-single-tab 2023-12-26 21:59:12 +00:00
mathuo
73cd0dba4e
feat: fullscreen panels 2023-12-26 21:56:33 +00:00
mathuo
584795b099
Merge branch 'master' of https://github.com/mathuo/dockview into 360-investigate-opening-tabs-in-new-browser-window 2023-12-26 20:23:29 +00:00
mathuo
343d42cb86
Merge pull request #409 from mathuo/397-gready-rendering-mode
feat: rename rendering modes
2023-12-26 20:22:32 +00:00
mathuo
36dd190ddb
feat: rename rendering modes 2023-12-26 20:14:57 +00:00
mathuo
f7fc6235e5
Merge branch 'master' of https://github.com/mathuo/dockview into 360-investigate-opening-tabs-in-new-browser-window 2023-12-26 20:06:44 +00:00
mathuo
5799549901
feat: popout windows 2023-12-26 19:58:21 +00:00
mathuo
450e37f973
Merge pull request #398 from mathuo/397-gready-rendering-mode
feat: gready render mode
2023-12-26 19:57:46 +00:00
mathuo
a2a4e68166
feat: gready render mode 2023-12-25 21:39:46 +00:00
mathuo
ef70ea236d
Merge pull request #392 from cemalgnlts/patch-1
Remove the hover effect for inactive sash classes.
2023-11-23 22:27:17 +00:00
Cemal Gönültaş
de88179b26
Remove the hover effect for inactive sash classes. 2023-11-16 16:48:00 +03:00
mathuo
b17aa24637
chore: rename action 2023-10-29 13:22:11 +00:00
mathuo
a1b3a94c42
chore(release): publish v1.8.5 2023-10-29 13:09:21 +00:00
mathuo
8df1de888a
chore: v1.8.5 release notes 2023-10-29 13:05:58 +00:00
mathuo
53c0a39d7a
Merge pull request #381 from mathuo/380-complete-set-of-events
feat: events system
2023-10-29 13:02:08 +00:00
mathuo
3c747aa4a7
feat: events system 2023-10-22 20:01:55 +01:00
mathuo
8aa0d0192e
Merge pull request #379 from mathuo/373-fix-sonar-issues
chore: fix sonar issues
2023-10-22 19:59:33 +01:00
mathuo
331e190bf8
chore: fix sonar issues 2023-10-22 19:12:46 +01:00
mathuo
ed466f758f
Merge pull request #378 from mathuo/371-update-dependencies
chore: update engine to node18
2023-10-22 16:16:23 +01:00
mathuo
7ca2997970
chore: update engine to node18 2023-10-22 16:16:03 +01:00
mathuo
26805d023a
Merge pull request #377 from mathuo/376-disable-autoresizing-flag
bug: fix disableAutoResizing flag
2023-10-22 16:14:41 +01:00
mathuo
126d01ede0
bug: fix disableAutoResizing flag 2023-10-22 16:10:28 +01:00
mathuo
d953cf9e4e
Merge pull request #372 from mathuo/371-update-dependencies
chore: update dependencies
2023-10-22 15:57:44 +01:00
mathuo
9fbb1ee479
Merge pull request #374 from mathuo/373-fix-sonar-issues
chore: fix sonar issues
2023-10-22 15:50:08 +01:00
mathuo
8e6f7bf808
chore: update dependencies 2023-10-22 15:48:28 +01:00
mathuo
882c1353c7
chore: fix sonar issues 2023-10-22 15:40:11 +01:00
mathuo
318cbd6854
Merge pull request #370 from mathuo/366-move-npm-publishing-to-github-actions
366 move npm publishing to GitHub actions
2023-10-22 14:57:56 +01:00
mathuo
329ed77115
chore: configure publish action 2023-10-22 13:44:42 +01:00
mathuo
c104503d62
chore: use node 18 2023-10-22 13:35:35 +01:00
mathuo
c6753f176a
Merge pull request #369 from mathuo/366-move-npm-publishing-to-github-actions
chore: Github Publish Actions
2023-10-22 13:20:09 +01:00
mathuo
dc50d4e2ac
chore: setup npm provenance attestation 2023-10-22 12:15:07 +01:00
mathuo
0f555075ae
chore: Github Publish Actions 2023-10-22 12:01:05 +01:00
mathuo
b4ff626b74
Merge pull request #368 from mathuo/366-move-npm-publishing-to-github-actions
chore: Github Publish Actions
2023-10-22 11:35:04 +01:00
mathuo
eeddad542b
chore: Github Publish Actions 2023-10-22 11:28:42 +01:00
mathuo
b07961df3a
Merge pull request #367 from mathuo/366-move-npm-publishing-to-github-actions
chore: Github Action npm experimental publish
2023-10-22 11:21:26 +01:00
mathuo
d4d79b220f
chore: Github Action npm experimental publish 2023-10-22 11:14:30 +01:00
mathuo
e3ed4251ed
chore: revert 2023-10-09 20:48:42 +01:00
mathuo
58e9f9b355
chore: improve docs 2023-10-09 20:29:57 +01:00
mathuo
47f78ad649
chore(release): publish v1.8.4 2023-10-06 20:14:56 +01:00
mathuo
a3b20deee9
chore: v1.8.4 docs 2023-10-06 20:05:16 +01:00
mathuo
3803bfa13a
Merge pull request #353 from mathuo/352-documentation-improvements
352 documentation improvements
2023-10-06 20:02:52 +01:00
mathuo
03235172a8
chore: docs 2023-10-06 19:56:42 +01:00
mathuo
859723a890
Merge pull request #359 from mathuo/358-splitview-separator-style-on-fromjson
fix: apply splitview styles on deserialize
2023-10-04 21:08:12 +01:00
mathuo
b5c3bcc86a
fix: apply splitview styles on deserialize 2023-10-04 21:00:17 +01:00
mathuo
e726b4ab02
Merge branch 'master' of https://github.com/mathuo/dockview into 352-documentation-improvements 2023-10-04 20:11:26 +01:00
mathuo
1661ff77d2
Merge pull request #357 from mathuo/356-nested-gridview-bug
bug: fix incorrect view disposal
2023-10-03 22:09:07 +01:00
mathuo
4300b1f89c
bug: remove views from branchnode before .dispose 2023-10-03 21:47:42 +01:00
mathuo
42a4d50c47
Merge branch 'master' of https://github.com/mathuo/dockview into 352-documentation-improvements 2023-10-03 20:02:47 +01:00
mathuo
76c3fb6fa0
Merge pull request #355 from mathuo/341-dockview-stuck-in-broken-state-after-apifromjson-fails-due-to-invalid-component-name
feat: fromJSON error handling and cleanup
2023-10-03 20:02:03 +01:00
mathuo
0ee10912a2
feat: fromJSON error handling and cleanup 2023-10-03 19:50:46 +01:00
mathuo
82b83e7ed3
chore: docs 2023-10-02 19:42:46 +01:00
mathuo
46bceea217
Merge branch '352-documentation-improvements' of https://github.com/mathuo/dockview into 352-documentation-improvements 2023-10-02 19:36:26 +01:00
mathuo
5a3f0b1d4a
Merge branch 'master' of https://github.com/mathuo/dockview into 352-documentation-improvements 2023-10-01 22:26:07 +01:00
mathuo
faeb8241a5
Merge pull request #351 from mathuo/350-provide-examples-of-complex-scenarios
chore: provide additional examples
2023-10-01 22:25:06 +01:00
mathuo
e71fd7b153
chore: auto-gen type docs 2023-10-01 21:55:15 +01:00
mathuo
5af4aa2da9
chore: auto-gen type docs 2023-10-01 21:53:59 +01:00
mathuo
4ee0617014
Merge branch 'master' into 350-provide-examples-of-complex-scenarios 2023-10-01 21:50:23 +01:00
mathuo
717247bbec
Merge pull request #348 from mathuo/347-move-between-tabs
chore: navigation examples
2023-10-01 20:46:22 +01:00
mathuo
2337373a6f
Merge pull request #340 from mathuo/338-options-before-tabs-start
feat: pre-tab actions
2023-10-01 20:45:40 +01:00
mathuo
a96400d970
Merge pull request #346 from mathuo/344-floating-tab-groups-move-in-bested-dockview
bug: skip resize for unmounted elements
2023-10-01 20:45:31 +01:00
mathuo
9006fb9917
Merge pull request #342 from mathuo/341-dockview-stuck-in-broken-state-after-apifromjson-fails-due-to-invalid-component-name
bug: unable to recover from loading a corrupted layout
2023-10-01 20:45:22 +01:00
mathuo
fdb148bc85
chore: update dependencies 2023-10-01 12:07:48 +01:00
mathuo
bd1a8f1d44
feat: prefix header actions 2023-09-30 12:08:45 +01:00
mathuo
fc1c747bed
test: add tests 2023-09-30 11:47:36 +01:00
mathuo
4c142b9632
chore: update examples 2023-09-30 11:31:01 +01:00
mathuo
08ec3fd3a5
feat: ensure view priority used for viewChange events 2023-09-30 11:29:55 +01:00
mathuo
206255b411
chore: provide additional examples 2023-09-30 11:23:51 +01:00
mathuo
ffbcaa6259
Merge pull request #336 from mathuo/335-actions-in-paneview
chore: sandbox paneview examples
2023-09-30 10:27:51 +01:00
mathuo
a15e895337
chore: navigation examples 2023-09-29 21:31:48 +01:00
mathuo
19595f3b12
bug: skip resize for unmounted elements 2023-09-29 20:55:14 +01:00
mathuo
1ccd75b2b3
bug: unable to recover from loading a corrupted layout 2023-09-28 21:39:24 +01:00
mathuo
4d1952387c
chore: sandbox paneview examples 2023-09-19 21:26:46 +01:00
mathuo
4ad5b0ffe4
chore(release): publish v1.8.3 2023-09-17 14:16:37 +01:00
mathuo
739976ea28
chore: v1.8.3 docs 2023-09-16 21:13:35 +01:00
mathuo
3ea9f21e90
chore: v1.8.3 docs 2023-09-16 21:13:08 +01:00
mathuo
bac862a5fc
Merge pull request #327 from mathuo/326-possible-to-stop-floating-windows-being-dragged-outside-visible-dockview-area
feat: floating group viewport rules
2023-09-12 22:35:59 +01:00
mathuo
78e0b89a78
feat: fix sonar issues 2023-09-12 22:26:26 +01:00
mathuo
16a49c7b27
Merge branch 'master' of https://github.com/mathuo/dockview into 326-possible-to-stop-floating-windows-being-dragged-outside-visible-dockview-area 2023-09-09 22:18:24 +01:00
mathuo
292e25935f
feat: floating group overlay rules 2023-09-04 20:10:52 +01:00
mathuo
0b39e84f86
Merge pull request #322 from mathuo/299-access-to-datatransfer-object-on-drag-events
feat: expose dragstart event on custom handlers
2023-09-04 19:43:36 +01:00
mathuo
a1cd01380b
chore: tests and docs 2023-09-04 19:32:42 +01:00
mathuo
f6cfb4418c
Merge branch 'master' of https://github.com/mathuo/dockview into 326-possible-to-stop-floating-windows-being-dragged-outside-visible-dockview-area 2023-09-03 16:26:41 +01:00
mathuo
65a7da6a36
Merge pull request #334 from mathuo/321-add-hideclose-to-dockviewdefaulttab-component
feat: override close option
2023-09-03 16:25:12 +01:00
mathuo
4aa707132f
feat: override close option 2023-09-03 16:01:13 +01:00
mathuo
63ca9053a5
Merge pull request #331 from mathuo/330-preventdefault-on-dockviewdefaulttab-close-btn
bug: preventDefault on close btn mouseDown
2023-08-29 21:59:40 +01:00
mathuo
bfa4f60288
feat : drag intercept examples 2023-08-29 20:22:23 +01:00
mathuo
493f843987
Merge pull request #328 from mathuo/321-add-hideclose-to-dockviewdefaulttab-component
docs: add note
2023-08-29 20:10:01 +01:00
mathuo
e64d96cef7
bug: preventDefault on close btn mouseDown 2023-08-29 20:09:43 +01:00
mathuo
3832222fef
Merge pull request #329 from mathuo/316-locked-group-drop-targets
tests: test locked behaviours
2023-08-28 20:18:35 +01:00
mathuo
007b2a8fea
tests: test locked behaviours 2023-08-28 20:04:19 +01:00
mathuo
2ce175dbf3
docs: add note 2023-08-27 21:59:53 +01:00
mathuo
6c2d511827
Merge branch 'master' of https://github.com/mathuo/dockview into 299-access-to-datatransfer-object-on-drag-events 2023-08-27 21:51:31 +01:00
mathuo
715281b804
feat: floating group viewport rules 2023-08-27 21:50:23 +01:00
mathuo
a1b2b9b1da
Merge pull request #323 from mathuo/321-add-hideclose-to-dockviewdefaulttab-component
feat: hideClose on DockviewDefaultTab
2023-08-26 12:54:15 +01:00
mathuo
5ac2a9ffdb
feat: hideClose on DockviewDefaultTab 2023-08-22 20:32:58 +01:00
mathuo
821c2f5c44
add drag intercept events 2023-08-22 20:17:53 +01:00
mathuo
be0fea7104
Merge pull request #320 from Imbozter/master
Remove supressClosable option from docs
2023-08-19 15:29:28 +01:00
Imbozter
ffc19f891d
Merge pull request #1 from Imbozter/Remove-suppressClosable-option-from-docs
Update dockview.mdx
2023-08-16 23:54:37 +10:00
Imbozter
3ef9f4ed2d
Update dockview.mdx 2023-08-16 23:53:50 +10:00
mathuo
3fb2800db4
Merge pull request #315 from techbech/locked-groups
feat: added the option for 'no-drop-target' for locked on dockview groups
2023-07-30 16:48:37 +01:00
Peter Bech
d577f0238c
Fix wrong quotation sign in docs 2023-07-27 10:28:24 +02:00
Peter Bech
b551d93dac
added lockedgroup-dockview to the codesandbox ci configuration 2023-07-26 22:03:51 +02:00
Peter Bech
55802a7a16
patch: renamed lockedgroup-dockview sandbox package name to match directory name 2023-07-26 21:52:29 +02:00
Peter Bech
17c0f6e575
feat: added the option for 'no-drop-target' for locked on dockview groups 2023-07-26 21:40:13 +02:00
mathuo
7701625b6f
Merge pull request #283 from mathuo/281-type-hints-for-panel-parameters
281 type hints for panel parameters
2023-07-25 22:47:32 +01:00
mathuo
141b2beaf3
Merge branch 'master' of https://github.com/mathuo/dockview into 281-type-hints-for-panel-parameters 2023-07-25 22:34:40 +01:00
mathuo
bae45867fa
chore(release): publish v1.8.2 2023-07-24 21:04:39 +01:00
mathuo
8ae5a9fc94
chore: v1.8.2 docs 2023-07-24 21:01:55 +01:00
mathuo
76d1f9ab70
Merge pull request #313 from mathuo/311-dnd-v180-bug
feat: external-dnd center drop when dockview is empty
2023-07-24 20:59:45 +01:00
mathuo
3ab3467547
feat: external-dnd center drop when dockview is empty 2023-07-24 20:52:22 +01:00
mathuo
175e8caa67
chore(release): publish v1.8.1 2023-07-24 20:30:50 +01:00
mathuo
b36fd3f312
chore: v1.8.1 docs 2023-07-24 20:27:08 +01:00
mathuo
dfdda78ab4
Merge pull request #312 from mathuo/311-dnd-v180-bug
bug: external-dnd event regression
2023-07-24 20:26:11 +01:00
mathuo
a8e243b3fd
bug: external-dnd event regression
external-dnd events should not trigger the top-level center drop target
2023-07-24 20:14:02 +01:00
mathuo
2aa700bca0
work in progress 2023-07-24 19:47:04 +01:00
mathuo
8833a973b9
feat: expose dragstart event on custom handlers 2023-07-23 21:24:56 +01:00
mathuo
a7dafbf301
chore: v.1.8.0 docs 2023-07-23 20:16:51 +01:00
mathuo
4b49c66283
chore: v1.8.0 release notes 2023-07-23 14:36:13 +01:00
mathuo
6665c61a95
feat: addPanel generics 2023-06-13 20:08:51 +01:00
mathuo
bfc4faeed2
Merge pull request #282 from mpearson/mpearson/component-params-typing
feat: add  optional type parameter for addPanel() params
2023-06-13 19:56:31 +01:00
mathuo
853a5be569
Merge branch '281-type-hints-for-panel-parameters' into mpearson/component-params-typing 2023-06-13 19:53:08 +01:00
Matthew Pearson
e6cae5f90d added an optional type parameter for addPanel() so the params prop can be strictly typed 2023-05-13 15:14:00 -07:00
758 changed files with 94271 additions and 18040 deletions

View File

@ -1,6 +1,8 @@
{
"packages": [
"packages/dockview-core",
"packages/dockview-vue",
"packages/dockview-react",
"packages/dockview"
],
"sandboxes": [
@ -9,18 +11,29 @@
"/packages/docs/sandboxes/demo-dockview",
"/packages/docs/sandboxes/dnd-dockview",
"/packages/docs/sandboxes/dockview-app",
"/packages/docs/sandboxes/editor-gridview",
"/packages/docs/sandboxes/events-dockview",
"/packages/docs/sandboxes/externaldnd-dockview",
"/packages/docs/sandboxes/floatinggroup-dockview",
"/packages/docs/sandboxes/fullwidthtab-dockview",
"/packages/docs/sandboxes/headeractions-dockview",
"/packages/docs/sandboxes/groupcontol-dockview",
"/packages/docs/sandboxes/iframe-dockview",
"/packages/docs/sandboxes/keyboard-dockview",
"/packages/docs/sandboxes/layout-dockview",
"/packages/docs/sandboxes/lockedgroup-dockview",
"/packages/docs/sandboxes/maximizegroup-dockview",
"/packages/docs/sandboxes/nativeapp-dockview",
"/packages/docs/sandboxes/nested-dockview",
"/packages/docs/sandboxes/popoutgroup-dockview",
"/packages/docs/sandboxes/rendering-dockview",
"/packages/docs/sandboxes/rendermode-dockview",
"/packages/docs/sandboxes/resize-dockview",
"/packages/docs/sandboxes/resizecontainer-dockview",
"/packages/docs/sandboxes/scrollbars-dockview",
"/packages/docs/sandboxes/simple-dockview",
"/packages/docs/sandboxes/simple-gridview",
"/packages/docs/sandboxes/simple-paneview",
"/packages/docs/sandboxes/tabheight-dockview",
"/packages/docs/sandboxes/updatetitle-dockview",
"/packages/docs/sandboxes/watermark-dockview",
@ -29,5 +42,5 @@
"/packages/docs/sandboxes/javascript/tabheight-dockview",
"/packages/docs/sandboxes/javascript/vanilla-dockview"
],
"node": "16"
}
"node": "18"
}

View File

@ -30,11 +30,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -45,7 +45,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -59,4 +59,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3

View File

@ -1,21 +1,20 @@
name: Deploy Docs
on:
schedule:
- cron: '0 3 * * *' # every day at 3 am UTC
workflow_dispatch:
jobs:
deploy-nightly-demo-app:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: '16.x'
node-version: '20.x'
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@ -27,6 +26,10 @@ jobs:
working-directory: packages/dockview-core
- run: npm run build
working-directory: packages/dockview
- run: npm run build
working-directory: packages/dockview-vue
- run: npm run build
working-directory: packages/dockview-react
- run: npm run build
working-directory: packages/docs
- run: npm run docs

View File

@ -7,16 +7,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# might be required for sonar to work correctly
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Use Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: '16.x'
node-version: '20.x'
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@ -24,11 +24,10 @@ jobs:
${{ runner.os }}-node-
- run: yarn
- run: npm run bootstrap
- run: npm run build
- run: npm run test:cov
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
uses: sonarsource/sonarqube-scan-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

78
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: Publish to npm
env:
NPM_CONFIG_PROVENANCE: true
on:
workflow_dispatch:
release:
types: [published]
jobs:
publish:
if: github.event_name == 'release'
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-
- run: yarn
- name: Publish dockview-core
run: npm publish --provenance
working-directory: packages/dockview-core
- name: Publish dockview
run: npm publish --provenance
working-directory: packages/dockview
- name: Publish dockview-vue
run: npm publish --provenance
working-directory: packages/dockview-vue
- name: Publish dockview-react
run: npm publish --provenance
working-directory: packages/dockview-react
publish-experimental:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-
- run: yarn
- run: npm run set-experimental-versions
- name: Publish dockview-core
run: npm publish --provenance --tag experimental
working-directory: packages/dockview-core
- name: Publish dockview
run: npm publish --provenance --tag experimental
working-directory: packages/dockview
- name: Publish dockview-vue
run: npm publish --provenance --tag experimental
working-directory: packages/dockview-vue
- name: Publish dockview-react
run: npm publish --provenance --tag experimental
working-directory: packages/dockview-react

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ test-report.xml
yarn-error.log
/build
/docs/
/generated/

View File

@ -7,7 +7,8 @@
"esbenp.prettier-vscode",
"redhat.vscode-yaml",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"vue.volar"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []

View File

@ -1,17 +1,18 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support written in TypeScript</p>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
</div>
---
[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview)
[![npm version](https://badge.fury.io/js/dockview-core.svg)](https://www.npmjs.com/package/dockview-core)
[![npm](https://img.shields.io/npm/dm/dockview-core)](https://www.npmjs.com/package/dockview-core)
[![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview)](https://bundlephobia.com/result?p=dockview)
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview-core)](https://bundlephobia.com/result?p=dockview-core)
##
@ -21,33 +22,17 @@ Please see the website: https://dockview.dev
## Features
- Simple splitviews, nested splitviews (i.e. gridviews) supporting full layout managment with
dockable and tabular views
- Extensive API support at the component level and view level
- Themable and customizable
- Serialization / deserialization support
- Tabular docking and Drag and Drop support
- Floating groups, customized header bars and tab
- Documentation and examples
- Serialization / deserialization with full layout management
- Support for split-views, grid-views and 'dockable' views
- Themeable and customizable
- Tab and Group docking / Drag n' Drop
- Popout Windows
- Floating Groups
- Extensive API
- Supports Shadow DOMs
- High test coverage
- Documentation website with live examples
- Transparent builds and Code Analysis
- Security at mind - verifed publishing and builds through GitHub Actions
Want to inspect the latest deployment? Go to https://unpkg.com/browse/dockview@latest/
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#user-content-provenance).

8
SECURITY.md Normal file
View File

@ -0,0 +1,8 @@
# Reporting a Vulnerability
- Dockview is an entirely open source project.
- All build and publication scripts use public Github Action files found [here](https://github.com/mathuo/dockview/tree/master/.github/workflows).
- All npm publications are verified through the use of [provenance statements](https://docs.npmjs.com/generating-provenance-statements/).
- All builds are scanned with SonarCube and outputs can be found [here](https://sonarcloud.io/summary/overall?id=mathuo_dockview).
If you believe you have found a security or vulnerability issue please send a complete example to github.mathuo@gmail.com where it will be investigated.

1
jest-setup.ts Normal file
View File

@ -0,0 +1 @@
import '@testing-library/jest-dom';

View File

@ -8,7 +8,7 @@ const config: JestConfigWithTsJest = {
collectCoverageFrom: ['<rootDir>/packages/*/src/**/*.{js,jsx,ts,tsx}'],
coveragePathIgnorePatterns: [
'/node_modules/',
'<rootDir>packages/*/src/__tests__/',
'<rootDir>/packages/*/src/__tests__/',
],
coverageDirectory: 'coverage',
testResultsProcessor: 'jest-sonar-reporter',

View File

@ -2,12 +2,11 @@
"packages": [
"packages/*"
],
"useWorkspaces": true,
"version": "1.8.0",
"version": "4.2.5",
"npmClient": "yarn",
"command": {
"publish": {
"message": "chore(release): publish %s"
}
}
}
}

View File

@ -1,71 +1,81 @@
{
"name": "dockview-monorepo-root",
"private": true,
"workspaces": [
"packages/*"
],
"nohoist": [
"**/babel-jest",
"**/babel-jest/**"
],
"description": "Monorepo for https://github.com/mathuo/dockview",
"scripts": {
"test": "jest",
"lint": "eslint packages/**/src/** --ext .ts,.tsx,.js,.jsx",
"package": "node scripts/package.js",
"package-all": "lerna run docs --scope '{dockview-core,dockview}' && node scripts/package.js",
"build": "lerna run build --scope '{dockview-core,dockview}'",
"clean": "lerna run clean",
"bootstrap": "lerna bootstrap",
"test:cov": "jest --coverage",
"version-beta-build": "lerna version prerelease --preid beta",
"publish-app": "lerna publish",
"docs": "typedoc",
"package-docs": "node scripts/package-docs.js"
"homepage": "https://github.com/mathuo/dockview#readme",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mathuo/dockview.git"
},
"author": "https://github.com/mathuo",
"license": "MIT",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
"author": "https://github.com/mathuo",
"workspaces": [
"packages/*"
],
"scripts": {
"build": "lerna run build --scope '{dockview-core,dockview,dockview-vue,dockview-react}'",
"clean": "lerna run clean",
"docs": "typedoc",
"generate-docs": "node scripts/docs.mjs",
"lint": "eslint packages/**/src/** --ext .ts,.tsx,.js,.jsx",
"package": "node scripts/package.js",
"package-docs": "node scripts/package-docs.js",
"set-experimental-versions": "node scripts/set-experimental-versions",
"test": "jest",
"test:cov": "jest --coverage",
"version": "lerna version"
},
"resolutions": {
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.18"
},
"homepage": "https://github.com/mathuo/dockview#readme",
"devDependencies": {
"@testing-library/dom": "^8.20.0",
"@testing-library/jest-dom": "^5.16.5",
"@types/jest": "^29.4.0",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"@typescript-eslint/parser": "^5.52.0",
"codecov": "^3.8.3",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.6",
"@testing-library/react": "^14.1.2",
"@total-typescript/shoehorn": "^0.1.1",
"@types/jest": "^29.5.11",
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.18",
"@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/tsconfig": "^0.5.1",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"css-loader": "^6.7.3",
"eslint": "^8.34.0",
"fs-extra": "^11.1.0",
"eslint": "^8.56.0",
"fs-extra": "^11.2.0",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-dart-sass": "^1.0.2",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.4.3",
"gulp-dart-sass": "^1.1.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-sonar-reporter": "^2.0.0",
"jsdom": "^21.1.0",
"lerna": "^6.5.1",
"merge2": "^1.4.1",
"rimraf": "^4.1.2",
"sass": "^1.58.1",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"ts-jest": "^29.0.5",
"ts-loader": "^9.4.2",
"ts-node": "^10.9.1",
"tslib": "^2.5.0",
"typedoc": "^0.24.7",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
"jsdom": "^23.0.1",
"lerna": "^8.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rimraf": "^5.0.5",
"rollup": "^4.9.2",
"rollup-plugin-postcss": "^4.0.2",
"ts-jest": "^29.1.1",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tslib": "^2.6.2",
"typedoc": "^0.25.6",
"typescript": "^5.3.3",
"vite": "^5.1.5",
"vue": "^3.4.21",
"vue-sfc-loader": "^0.1.0",
"vue-tsc": "^2.0.5"
},
"dependencies": {}
"engines": {
"node": ">=18.0"
}
}

View File

@ -0,0 +1,56 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
</div>
---
[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview)
[![npm](https://img.shields.io/npm/dm/dockview)](https://www.npmjs.com/package/dockview)
[![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview)](https://bundlephobia.com/result?p=dockview)
##
Please see the website: https://dockview.dev
## Features
- Serialization / deserialization with full layout management
- Support for split-views, grid-views and 'dockable' views
- Themeable and customizable
- Tab and Group docking / Drag n' Drop
- Popout Windows
- Floating Groups
- Extensive API
- Supports Shadow DOMs
- High test coverage
- Documentation website with live examples
- Transparent builds and Code Analysis
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

@ -0,0 +1,34 @@
import { JestConfigWithTsJest } from 'ts-jest';
const config: JestConfigWithTsJest = {
preset: 'ts-jest',
roots: ['<rootDir>/packages/dockview-angular'],
modulePaths: ['<rootDir>/packages/dockview-angular/src'],
displayName: { name: 'dockview-angular', color: 'blue' },
rootDir: '../../',
collectCoverageFrom: [
'<rootDir>/packages/dockview-angular/src/**/*.{js,jsx,ts,tsx}',
],
setupFiles: [
// '<rootDir>/packages/dockview-angular/src/__tests__/__mocks__/resizeObserver.js',
],
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
coveragePathIgnorePatterns: ['/node_modules/'],
modulePathIgnorePatterns: [
// '<rootDir>/packages/dockview-angular/src/__tests__/__mocks__',
// '<rootDir>/packages/dockview-angular/src/__tests__/__test_utils__',
],
coverageDirectory: '<rootDir>/packages/dockview-angular/coverage/',
testResultsProcessor: 'jest-sonar-reporter',
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.test.json',
},
],
},
};
export default config;

View File

@ -0,0 +1,59 @@
{
"name": "dockview-angular",
"version": "4.2.5",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
"split-view",
"gridview",
"grid-view",
"dockview",
"dock-view",
"grid",
"tabs",
"layout",
"layout manager",
"dock layout",
"dock",
"docking",
"splitter",
"drag-and-drop",
"drag",
"drop",
"react",
"react-component"
],
"homepage": "https://github.com/mathuo/dockview",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/mathuo/dockview.git"
},
"license": "MIT",
"author": "https://github.com/mathuo",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/cjs/index.d.ts",
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "npm run build:package && npm run build:bundles",
"build:bundles": "rollup -c",
"build:cjs": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.json --verbose --extendedDiagnostics",
"build:css": "gulp sass",
"build:esm": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.esm.json --verbose --extendedDiagnostics",
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run test",
"rebuild": "npm run clean && npm run build",
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
},
"dependencies": {
"dockview-core": "^4.2.5"
}
}

View File

@ -0,0 +1,113 @@
/* eslint-disable */
const { join } = require('path');
const typescript = require('@rollup/plugin-typescript');
const terser = require('@rollup/plugin-terser');
const postcss = require('rollup-plugin-postcss');
const nodeResolve = require('@rollup/plugin-node-resolve');
const { name, version, homepage, license } = require('./package.json');
const main = join(__dirname, './scripts/rollupEntryTarget.ts');
const mainNoStyles = join(__dirname, './src/index.ts');
const outputDir = join(__dirname, 'dist');
function outputFile(format, isMinified, withStyles) {
let filename = join(outputDir, name);
if (format !== 'umd') {
filename += `.${format}`;
}
if (isMinified) {
filename += '.min';
}
if (!withStyles) {
filename += '.noStyle';
}
return `${filename}.js`;
}
function getInput(options) {
const { withStyles } = options;
if (withStyles) {
return main;
}
return mainNoStyles;
}
function createBundle(format, options) {
const { withStyles, isMinified } = options;
const input = getInput(options);
const file = outputFile(format, isMinified, withStyles);
const external = [];
const output = {
file,
format,
sourcemap: true,
globals: {},
banner: [
`/**`,
` * ${name}`,
` * @version ${version}`,
` * @link ${homepage}`,
` * @license ${license}`,
` */`,
].join('\n'),
};
const plugins = [
nodeResolve({
include: ['node_modules/dockview-core/**'],
}),
typescript({
tsconfig: 'tsconfig.esm.json',
}),
];
if (isMinified) {
plugins.push(terser());
}
if (withStyles) {
plugins.push(postcss());
}
if (format === 'umd') {
output['name'] = name;
}
external.push('react', 'react-dom');
if (format === 'umd') {
output.globals['react'] = 'React';
output.globals['react-dom'] = 'ReactDOM';
}
return {
input,
output,
plugins,
external,
};
}
module.exports = [
// amd
createBundle('amd', { withStyles: false, isMinified: false }),
createBundle('amd', { withStyles: true, isMinified: false }),
createBundle('amd', { withStyles: false, isMinified: true }),
createBundle('amd', { withStyles: true, isMinified: true }),
// umd
createBundle('umd', { withStyles: false, isMinified: false }),
createBundle('umd', { withStyles: true, isMinified: false }),
createBundle('umd', { withStyles: false, isMinified: true }),
createBundle('umd', { withStyles: true, isMinified: true }),
// cjs
createBundle('cjs', { withStyles: true, isMinified: false }),
// esm
createBundle('esm', { withStyles: true, isMinified: false }),
createBundle('esm', { withStyles: true, isMinified: true }),
];

View File

@ -0,0 +1,2 @@
import '../dist/styles/dockview.css';
export * from '../src/index';

View File

@ -0,0 +1,5 @@
describe('empty', () => {
test('that passes', () => {
expect(true).toBeTruthy();
});
});

View File

@ -0,0 +1 @@
export * from 'dockview-core';

View File

@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ES2020",
"moduleResolution": "node",
"target": "es6",
"outDir": "dist/esm",
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.esm",
"jsx": "react",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["**/node_modules", "src/__tests__"]
}

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist/cjs",
"tsBuildInfoFile": ".build/tsconfig.tsbuildinfo.cjs",
"jsx": "react",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["**/node_modules", "src/__tests__"]
}

View File

@ -0,0 +1,5 @@
{
"extends": ["../../typedoc.base.json"],
"entryPoints": ["src/index.ts"],
"exclude": ["**/dist/**"]
}

View File

@ -1,13 +1,14 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews written in TypeScript</p>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
</div>
---
[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview)
[![npm](https://img.shields.io/npm/dm/dockview)](https://www.npmjs.com/package/dockview)
[![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
@ -15,37 +16,23 @@
##
![](packages/docs/static/img/splashscreen.gif)
Please see the website: https://dockview.dev
## Features
- Simple splitviews, nested splitviews (i.e. gridviews) supporting full layout managment with
dockable and tabular views
- Extensive API support at the component level and view level
- Themable and customizable
- Serialization / deserialization support
- Tabular docking and Drag and Drop support
- Floating groups, customized header bars and tab
- Documentation and examples
- Serialization / deserialization with full layout management
- Support for split-views, grid-views and 'dockable' views
- Themeable and customizable
- Tab and Group docking / Drag n' Drop
- Popout Windows
- Floating Groups
- Extensive API
- Supports Shadow DOMs
- High test coverage
- Documentation website with live examples
- Transparent builds and Code Analysis
- Security at mind - verifed publishing and builds through GitHub Actions
Want to inspect the latest deployment? Go to https://unpkg.com/browse/dockview-core@latest/
## Quick start
You can install dockview-core from [npm](https://www.npmjs.com/package/dockview-core).
```
npm install --save dockview-core
```
Within your project you must import or reference the stylesheet at `dockview-core/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview-core/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).

View File

@ -1,6 +1,13 @@
const gulp = require('gulp');
const buildfile = require('../../scripts/build');
const gulpSass = require('gulp-dart-sass');
const concat = require('gulp-concat');
buildfile.init();
gulp.task('sass', () => {
return gulp
.src('./src/**/*.scss')
.pipe(gulpSass().on('error', gulpSass.logError))
.pipe(concat('dockview.css'))
.pipe(gulp.dest('./dist/styles/'));
});
gulp.task('run', gulp.series(['sass']));

View File

@ -12,6 +12,7 @@ const config: JestConfigWithTsJest = {
setupFiles: [
'<rootDir>/packages/dockview-core/src/__tests__/__mocks__/resizeObserver.js',
],
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
coveragePathIgnorePatterns: ['/node_modules/'],
modulePathIgnorePatterns: [
'<rootDir>/packages/dockview-core/src/__tests__/__mocks__',

View File

@ -1,37 +1,7 @@
{
"name": "dockview-core",
"version": "1.8.0",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
"main": "./dist/cjs/index.js",
"types": "./dist/cjs/index.d.ts",
"module": "./dist/esm/index.js",
"repository": {
"type": "git",
"url": "https://github.com/mathuo/dockview.git"
},
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"homepage": "https://github.com/mathuo/dockview",
"scripts": {
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
"build:cjs": "cross-env ../../node_modules/.bin/tsc --project ./tsconfig.json --extendedDiagnostics",
"build:css": "gulp sass",
"build:esm": "cross-env ../../node_modules/.bin/tsc --project ./tsconfig.esm.json --extendedDiagnostics",
"build:bundles": "rollup -c",
"build": "npm run build:package && npm run build:bundles",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run test",
"docs": "typedoc",
"rebuild": "npm run clean && npm run build",
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core --coverage",
"dev-publish": "node ./scripts/publishExperimental.js"
},
"files": [
"dist",
"README.md"
],
"version": "4.2.5",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
"split-view",
@ -53,16 +23,34 @@
"react",
"react-component"
],
"author": "https://github.com/mathuo",
"homepage": "https://github.com/mathuo/dockview",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/mathuo/dockview.git"
},
"license": "MIT",
"devDependencies": {
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-typescript": "^11.0.0",
"cross-env": "^7.0.3",
"postcss": "^8.4.21",
"rimraf": "^4.1.2",
"rollup": "^3.15.0",
"rollup-plugin-postcss": "^4.0.2",
"typedoc": "^0.23.25"
"author": "https://github.com/mathuo",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/cjs/index.d.ts",
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "npm run build:package && npm run build:bundles",
"build:bundles": "rollup -c",
"build:cjs": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.json --verbose --extendedDiagnostics",
"build:css": "gulp sass",
"build:esm": "cross-env ../../node_modules/.bin/tsc --build ./tsconfig.esm.json --verbose --extendedDiagnostics",
"build:package": "npm run build:cjs && npm run build:esm && npm run build:css",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run test",
"rebuild": "npm run clean && npm run build",
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core --coverage"
}
}

View File

@ -1,63 +0,0 @@
const cp = require('child_process');
const fs = require('fs-extra');
const path = require('path');
const rootDir = path.join(__dirname, '..');
const publishDir = path.join(rootDir, '__publish__');
cp.execSync('npm run clean', { cwd: rootDir, stdio: 'inherit' });
cp.execSync('npm run test', { cwd: __dirname, stdio: 'inherit' });
cp.execSync('npm run build', { cwd: rootDir, stdio: 'inherit' });
if (fs.existsSync(publishDir)) {
fs.removeSync(publishDir);
}
fs.mkdirSync(publishDir);
if (!fs.existsSync(path.join(publishDir, 'dist'))) {
fs.mkdirSync(path.join(publishDir, 'dist'));
}
const package = JSON.parse(
fs.readFileSync(path.join(rootDir, 'package.json')).toString()
);
for (const file of package.files) {
fs.copySync(path.join(rootDir, file), path.join(publishDir, file));
}
const result = cp
.execSync('git rev-parse --short HEAD', {
cwd: rootDir,
})
.toString()
.replace(/\n/g, '');
function formatDate() {
const date = new Date();
function pad(value) {
if (value.toString().length === 1) {
return `0${value}`;
}
return value;
}
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(
date.getDate()
)}`;
}
package.version = `0.0.0-experimental-${result}-${formatDate()}`;
package.scripts = {};
fs.writeFileSync(
path.join(publishDir, 'package.json'),
JSON.stringify(package, null, 4)
);
const command = 'npm publish --tag experimental';
cp.execSync(command, { cwd: publishDir, stdio: 'inherit' });
fs.removeSync(publishDir);

View File

@ -1,23 +1,28 @@
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import {
GroupPanelPartInitParameters,
TabPartInitParameters,
IContentRenderer,
ITabRenderer,
} from '../../dockview/types';
import { PanelUpdateEvent } from '../../panel/types';
import { TabLocation } from '../../dockview/framework';
export class DockviewPanelModelMock implements IDockviewPanelModel {
constructor(
readonly contentComponent: string,
readonly content: IContentRenderer,
readonly tabComponent?: string,
readonly tab?: ITabRenderer
readonly tabComponent: string,
readonly tab: ITabRenderer
) {
//
}
init(params: GroupPanelPartInitParameters): void {
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
return this.tab;
}
init(params: TabPartInitParameters): void {
//
}

View File

@ -0,0 +1,45 @@
import { fromPartial } from '@total-typescript/shoehorn';
export function setupMockWindow() {
const listeners: Record<string, (() => void)[]> = {};
let width = 1000;
let height = 2000;
return fromPartial<Window>({
addEventListener: (type: string, listener: () => void) => {
if (!listeners[type]) {
listeners[type] = [];
}
listeners[type].push(listener);
if (type === 'load') {
listener();
}
},
removeEventListener: (type: string, listener: () => void) => {
if (listeners[type]) {
const index = listeners[type].indexOf(listener);
if (index > -1) {
listeners[type].splice(index, 1);
}
}
},
dispatchEvent: (event: Event) => {
const items = listeners[event.type];
if (!items) {
return;
}
items.forEach((item) => item());
},
document: document,
close: () => {
listeners['beforeunload']?.forEach((f) => f());
},
get innerWidth() {
return width++;
},
get innerHeight() {
return height++;
},
});
}

View File

@ -1,4 +1,13 @@
import * as React from 'react';
import React from 'react';
/**
* useful utility type to erase readonly signatures for testing purposes
*
* @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#readonly-mapped-type-modifiers-and-readonly-arrays
*/
export type Writable<T> = T extends object
? { -readonly [K in keyof T]: Writable<T[K]> }
: T;
export function setMockRefElement(node: Partial<HTMLElement>): void {
const mockRef = {
@ -12,3 +21,53 @@ export function setMockRefElement(node: Partial<HTMLElement>): void {
jest.spyOn(React, 'useRef').mockReturnValueOnce(mockRef);
}
export function createOffsetDragOverEvent(params: {
clientX: number;
clientY: number;
}): Event {
const event = new Event('dragover', {
bubbles: true,
cancelable: true,
});
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
return event;
}
/**
* `jest.runAllTicks` doesn't seem to exhaust all events in the micro-task queue so
* as a **hacky** alternative we'll wait for an empty Promise to complete which runs
* on the micro-task queue so will force a run-to-completion emptying the queue
* of any pending micro-task
*/
export function exhaustMicrotaskQueue(): Promise<void> {
return new Promise<void>((resolve) => resolve());
}
export const mockGetBoundingClientRect = ({
left,
top,
height,
width,
}: {
left: number;
top: number;
height: number;
width: number;
}) => {
const result = {
left,
top,
height,
width,
right: left + width,
bottom: top + height,
x: left,
y: top,
};
return {
...result,
toJSON: () => result,
};
};

View File

@ -5,7 +5,7 @@ describe('api', () => {
let api: PanelApiImpl;
beforeEach(() => {
api = new PanelApiImpl('dummy_id');
api = new PanelApiImpl('dummy_id', 'fake-component');
});
test('updateParameters', () => {

View File

@ -1,15 +1,17 @@
import { DockviewPanelApiImpl } from '../../api/dockviewPanelApi';
import { DockviewComponent } from '../../dockview/dockviewComponent';
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
import { DockviewPanel } from '../../dockview/dockviewPanel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { fromPartial } from '@total-typescript/shoehorn';
describe('groupPanelApi', () => {
test('title', () => {
const accessor: Partial<DockviewComponent> = {
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
onDidOptionsChange: jest.fn(),
});
const panelMock = jest.fn<DockviewPanel, []>(() => {
return {
@ -17,17 +19,21 @@ describe('groupPanelApi', () => {
setTitle: jest.fn(),
} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panel = new panelMock();
const group = new groupMock();
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
},
});
const cut = new DockviewPanelApiImpl(
panel,
group,
<DockviewComponent>accessor
<DockviewComponent>accessor,
'fake-component'
);
cut.setTitle('test_title');
@ -36,16 +42,18 @@ describe('groupPanelApi', () => {
});
test('updateParameters', () => {
const groupPanel: Partial<IDockviewPanel> = {
const groupPanel: Partial<DockviewPanel> = {
id: 'test_id',
update: jest.fn(),
};
const accessor: Partial<DockviewComponent> = {
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
onDidOptionsChange: jest.fn(),
});
const groupViewPanel = new DockviewGroupPanel(
<DockviewComponent>accessor,
'',
@ -53,9 +61,10 @@ describe('groupPanelApi', () => {
);
const cut = new DockviewPanelApiImpl(
<IDockviewPanel>groupPanel,
<DockviewPanel>groupPanel,
<DockviewGroupPanel>groupViewPanel,
<DockviewComponent>accessor
<DockviewComponent>accessor,
'fake-component'
);
cut.updateParameters({ keyA: 'valueA' });
@ -67,15 +76,17 @@ describe('groupPanelApi', () => {
});
test('onDidGroupChange', () => {
const groupPanel: Partial<IDockviewPanel> = {
const groupPanel: Partial<DockviewPanel> = {
id: 'test_id',
};
const accessor: Partial<DockviewComponent> = {
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
onDidOptionsChange: jest.fn(),
});
const groupViewPanel = new DockviewGroupPanel(
<DockviewComponent>accessor,
'',
@ -83,9 +94,10 @@ describe('groupPanelApi', () => {
);
const cut = new DockviewPanelApiImpl(
<IDockviewPanel>groupPanel,
<DockviewPanel>groupPanel,
<DockviewGroupPanel>groupViewPanel,
<DockviewComponent>accessor
<DockviewComponent>accessor,
'fake-component'
);
let events = 0;

View File

@ -70,8 +70,8 @@ describe('abstractDragHandler', () => {
expect(span.style.pointerEvents).toBeFalsy();
fireEvent.dragEnd(element);
expect(iframe.style.pointerEvents).toBe('auto');
expect(webview.style.pointerEvents).toBe('auto');
expect(iframe.style.pointerEvents).toBe('');
expect(webview.style.pointerEvents).toBe('');
expect(span.style.pointerEvents).toBeFalsy();
handler.dispose();
@ -114,8 +114,8 @@ describe('abstractDragHandler', () => {
expect(span.style.pointerEvents).toBeFalsy();
handler.dispose();
expect(iframe.style.pointerEvents).toBe('auto');
expect(webview.style.pointerEvents).toBe('auto');
expect(iframe.style.pointerEvents).toBe('');
expect(webview.style.pointerEvents).toBe('');
expect(span.style.pointerEvents).toBeFalsy();
});
@ -172,7 +172,7 @@ describe('abstractDragHandler', () => {
const event = new Event('dragstart');
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(0);
expect(spy).toHaveBeenCalledTimes(0);
handler.dispose();
});

View File

@ -7,19 +7,7 @@ import {
positionToDirection,
} from '../../dnd/droptarget';
import { fireEvent } from '@testing-library/dom';
function createOffsetDragOverEvent(params: {
clientX: number;
clientY: number;
}): Event {
const event = new Event('dragover', {
bubbles: true,
cancelable: true,
});
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
return event;
}
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
describe('droptarget', () => {
let element: HTMLElement;
@ -28,10 +16,10 @@ describe('droptarget', () => {
beforeEach(() => {
element = document.createElement('div');
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 200);
});
test('that dragover events are marked', () => {
@ -65,7 +53,7 @@ describe('droptarget', () => {
fireEvent.dragOver(element);
const target = element.querySelector(
'.drop-target-dropzone'
'.dv-drop-target-dropzone'
) as HTMLElement;
fireEvent.drop(target);
expect(position).toBe('center');
@ -73,7 +61,7 @@ describe('droptarget', () => {
const event = new Event('dragover');
(event as any)['__dockview_droptarget_event_is_used__'] = true;
fireEvent(element, event);
expect(element.querySelector('.drop-target-dropzone')).toBeNull();
expect(element.querySelector('.dv-drop-target-dropzone')).toBeNull();
});
test('directionToPosition', () => {
@ -114,7 +102,7 @@ describe('droptarget', () => {
fireEvent.dragOver(element);
const target = element.querySelector(
'.drop-target-dropzone'
'.dv-drop-target-dropzone'
) as HTMLElement;
fireEvent.drop(target);
expect(position).toBe('center');
@ -136,7 +124,7 @@ describe('droptarget', () => {
fireEvent.dragOver(element);
const target = element.querySelector(
'.drop-target-dropzone'
'.dv-drop-target-dropzone'
) as HTMLElement;
jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100);
@ -167,12 +155,12 @@ describe('droptarget', () => {
fireEvent.dragOver(element);
let viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection'
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
);
expect(viewQuery.length).toBe(1);
const target = element.querySelector(
'.drop-target-dropzone'
'.dv-drop-target-dropzone'
) as HTMLElement;
jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100);
@ -183,18 +171,37 @@ describe('droptarget', () => {
createOffsetDragOverEvent({ clientX: 19, clientY: 0 })
);
function check(
element: HTMLElement,
box: {
left: string;
top: string;
width: string;
height: string;
}
) {
expect(element.style.top).toBe(box.top);
expect(element.style.left).toBe(box.left);
expect(element.style.width).toBe(box.width);
expect(element.style.height).toBe(box.height);
}
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection'
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('left');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateX(-25%) scaleX(0.5)');
check(
element
.getElementsByClassName('dv-drop-target-selection')
.item(0) as HTMLDivElement,
{
top: '0px',
left: '0px',
width: '50%',
height: '100%',
}
);
fireEvent(
target,
@ -202,17 +209,21 @@ describe('droptarget', () => {
);
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection'
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('top');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateY(-25%) scaleY(0.5)');
check(
element
.getElementsByClassName('dv-drop-target-selection')
.item(0) as HTMLDivElement,
{
top: '0px',
left: '0px',
width: '100%',
height: '50%',
}
);
fireEvent(
target,
@ -220,17 +231,21 @@ describe('droptarget', () => {
);
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection'
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('bottom');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateY(25%) scaleY(0.5)');
check(
element
.getElementsByClassName('dv-drop-target-selection')
.item(0) as HTMLDivElement,
{
top: '50%',
left: '0px',
width: '100%',
height: '50%',
}
);
fireEvent(
target,
@ -238,18 +253,21 @@ describe('droptarget', () => {
);
viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection'
'.dv-drop-target > .dv-drop-target-dropzone > .dv-drop-target-selection'
);
expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('right');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateX(25%) scaleX(0.5)');
check(
element
.getElementsByClassName('dv-drop-target-selection')
.item(0) as HTMLDivElement,
{
top: '0px',
left: '50%',
width: '50%',
height: '100%',
}
);
fireEvent(
target,
createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
@ -258,14 +276,14 @@ describe('droptarget', () => {
expect(
(
element
.getElementsByClassName('drop-target-selection')
.getElementsByClassName('dv-drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('');
fireEvent.dragLeave(target);
expect(droptarget.state).toBe('center');
viewQuery = element.querySelectorAll('.drop-target');
viewQuery = element.querySelectorAll('.dv-drop-target');
expect(viewQuery.length).toBe(0);
});

View File

@ -2,6 +2,7 @@ import { fireEvent } from '@testing-library/dom';
import { GroupDragHandler } from '../../dnd/groupDragHandler';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
import { DockviewComponent } from '../../dockview/dockviewComponent';
describe('groupDragHandler', () => {
test('that the dnd transfer object is setup and torndown', () => {
@ -10,13 +11,17 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
id: 'test_group_id',
api: { isFloating: false } as any,
api: { location: { type: 'grid' } } as any,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(element, 'test_accessor_id', group);
const cut = new GroupDragHandler(
element,
{ id: 'test_accessor_id' } as DockviewComponent,
group
);
fireEvent.dragStart(element, new Event('dragstart'));
@ -43,18 +48,22 @@ describe('groupDragHandler', () => {
cut.dispose();
});
test('that the event is cancelled when isFloating and shiftKey=true', () => {
test('that the event is cancelled when floating and shiftKey=true', () => {
const element = document.createElement('div');
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
api: { isFloating: true } as any,
api: { location: { type: 'floating' } } as any,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(element, 'accessor_id', group);
const cut = new GroupDragHandler(
element,
{ id: 'accessor_id' } as DockviewComponent,
group
);
const event = new KeyboardEvent('dragstart', { shiftKey: false });
@ -76,13 +85,17 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
api: { isFloating: false } as any,
api: { location: { type: 'grid' } } as any,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(element, 'accessor_id', group);
const cut = new GroupDragHandler(
element,
{ id: 'accessor_id' } as DockviewComponent,
group
);
const event = new KeyboardEvent('dragstart', { shiftKey: false });

View File

@ -1,156 +0,0 @@
import { Overlay } from '../../dnd/overlay';
describe('overlay', () => {
test('toJSON', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
left: 10,
top: 20,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return { left: 80, top: 100, width: 40, height: 50 } as any;
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 20, top: 30, width: 100, height: 100 } as any;
}
);
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
width: 40,
height: 50,
});
});
test('that out-of-bounds dimensions are fixed', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
left: -1000,
top: -1000,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return { left: 80, top: 100, width: 40, height: 50 } as any;
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 20, top: 30, width: 100, height: 100 } as any;
}
);
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
width: 40,
height: 50,
});
});
test('setBounds', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 1000,
width: 1000,
left: 0,
top: 0,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
const element: HTMLElement = container.querySelector(
'.dv-resize-container'
)!;
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return { left: 300, top: 400, width: 1000, height: 1000 } as any;
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 0, top: 0, width: 1000, height: 1000 } as any;
}
);
cut.setBounds({ height: 100, width: 200, left: 300, top: 400 });
expect(element.style.height).toBe('100px');
expect(element.style.width).toBe('200px');
expect(element.style.left).toBe('300px');
expect(element.style.top).toBe('400px');
});
test('that the resize handles are added', () => {
const container = document.createElement('div');
const content = document.createElement('div');
const cut = new Overlay({
height: 500,
width: 500,
left: 100,
top: 200,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
expect(container.querySelector('.dv-resize-handle-top')).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottom')
).toBeTruthy();
expect(container.querySelector('.dv-resize-handle-left')).toBeTruthy();
expect(container.querySelector('.dv-resize-handle-right')).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-topleft')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-topright')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottomleft')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottomright')
).toBeTruthy();
cut.dispose();
});
});

View File

@ -1,14 +1,17 @@
import { fireEvent } from '@testing-library/dom';
import { Emitter, Event } from '../../../../events';
import { ContentContainer } from '../../../../dockview/components/panel/content';
import {
GroupPanelContentPartInitParameters,
GroupPanelPartInitParameters,
IContentRenderer,
} from '../../../../dockview/types';
import { CompositeDisposable } from '../../../../lifecycle';
import { PanelUpdateEvent } from '../../../../panel/types';
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel';
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
import { fromPartial } from '@total-typescript/shoehorn';
import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel';
import { OverlayRenderContainer } from '../../../../overlay/overlayRenderContainer';
class TestContentRenderer
extends CompositeDisposable
@ -16,17 +19,13 @@ class TestContentRenderer
{
readonly element: HTMLElement;
readonly _onDidFocus = new Emitter<void>();
readonly _onDidBlur = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event;
readonly onDidBlur: Event<void> = this._onDidBlur.event;
constructor(public id: string) {
super();
this.element = document.createElement('div');
this.element.id = id;
}
init(parameters: GroupPanelContentPartInitParameters): void {
init(parameters: GroupPanelPartInitParameters): void {
//
}
@ -56,7 +55,21 @@ describe('contentContainer', () => {
let blur = 0;
const disposable = new CompositeDisposable();
const cut = new ContentContainer();
const overlayRenderContainer = new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
);
const cut = new ContentContainer(
fromPartial<DockviewComponent>({
renderer: 'onlyWhenVisible',
overlayRenderContainer,
}),
fromPartial<DockviewGroupPanelModel>({
renderContainer: overlayRenderContainer,
})
);
disposable.addDisposables(
cut.onDidFocus(() => {
@ -69,11 +82,12 @@ describe('contentContainer', () => {
const contentRenderer = new TestContentRenderer('id-1');
const panel = {
const panel = fromPartial<IDockviewPanel>({
view: {
content: contentRenderer,
} as Partial<IDockviewPanelModel>,
} as Partial<IDockviewPanel>;
},
api: { renderer: 'onlyWhenVisible' },
});
cut.openPanel(panel as IDockviewPanel);
@ -91,45 +105,70 @@ describe('contentContainer', () => {
expect(focus).toBe(1);
expect(blur).toBe(1);
// renderer explicitly asks for focus
contentRenderer._onDidFocus.fire();
expect(focus).toBe(2);
expect(blur).toBe(1);
// renderer explicitly looses focus
contentRenderer._onDidBlur.fire();
expect(focus).toBe(2);
expect(blur).toBe(2);
const contentRenderer2 = new TestContentRenderer('id-2');
const panel2 = {
view: {
content: contentRenderer2,
} as Partial<IDockviewPanelModel>,
api: { renderer: 'onlyWhenVisible' },
} as Partial<IDockviewPanel>;
cut.openPanel(panel2 as IDockviewPanel);
expect(focus).toBe(2);
expect(blur).toBe(2);
// previous renderer events should no longer be attached to container
contentRenderer._onDidFocus.fire();
contentRenderer._onDidBlur.fire();
expect(focus).toBe(2);
expect(blur).toBe(2);
// expect(focus).toBe(2);
// expect(blur).toBe(1);
// new panel recieves focus
fireEvent.focus(contentRenderer2.element);
expect(focus).toBe(3);
expect(blur).toBe(2);
expect(focus).toBe(2);
expect(blur).toBe(1);
// new panel looses focus
fireEvent.blur(contentRenderer2.element);
jest.runAllTimers();
expect(focus).toBe(3);
expect(blur).toBe(3);
expect(focus).toBe(2);
expect(blur).toBe(2);
disposable.dispose();
});
test("that panels renderered as 'onlyWhenVisible' are removed when closed", () => {
const overlayRenderContainer = fromPartial<OverlayRenderContainer>({
detatch: jest.fn(),
});
const cut = new ContentContainer(
fromPartial<DockviewComponent>({
overlayRenderContainer,
}),
fromPartial<DockviewGroupPanelModel>({
renderContainer: overlayRenderContainer,
})
);
const panel1 = fromPartial<IDockviewPanel>({
api: {
renderer: 'onlyWhenVisible',
},
view: { content: new TestContentRenderer('panel_1') },
});
const panel2 = fromPartial<IDockviewPanel>({
api: {
renderer: 'onlyWhenVisible',
},
view: { content: new TestContentRenderer('panel_2') },
});
cut.openPanel(panel1);
expect(panel1.view.content.element.parentElement).toBe(cut.element);
expect(cut.element.childNodes.length).toBe(1);
cut.openPanel(panel2);
expect(panel1.view.content.element.parentElement).toBeNull();
expect(panel2.view.content.element.parentElement).toBe(cut.element);
expect(cut.element.childNodes.length).toBe(1);
});
});

View File

@ -1,31 +1,44 @@
import { fireEvent } from '@testing-library/dom';
import { LocalSelectionTransfer, PanelTransfer } from '../../../dnd/dataTransfer';
import {
LocalSelectionTransfer,
PanelTransfer,
} from '../../../dnd/dataTransfer';
import { DockviewComponent } from '../../../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel';
import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel';
import { Tab } from '../../../dockview/components/tab/tab';
import { IDockviewPanel } from '../../../dockview/dockviewPanel';
import { fromPartial } from '@total-typescript/shoehorn';
describe('tab', () => {
test('that empty tab has inactive-tab class', () => {
const accessorMock = jest.fn();
const groupMock = jest.fn();
const cut = new Tab('panelId', new accessorMock(), new groupMock());
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
new accessorMock(),
new groupMock()
);
expect(cut.element.className).toBe('tab inactive-tab');
expect(cut.element.className).toBe('dv-tab dv-inactive-tab');
});
test('that active tab has active-tab class', () => {
const accessorMock = jest.fn();
const groupMock = jest.fn();
const cut = new Tab('panelId', new accessorMock(), new groupMock());
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
new accessorMock(),
new groupMock()
);
cut.setActive(true);
expect(cut.element.className).toBe('tab active-tab');
expect(cut.element.className).toBe('dv-tab dv-active-tab');
cut.setActive(false);
expect(cut.element.className).toBe('tab inactive-tab');
expect(cut.element.className).toBe('dv-tab dv-inactive-tab');
});
test('that an external event does not render a drop target and calls through to the group model', () => {
@ -34,15 +47,10 @@ describe('tab', () => {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
@ -54,40 +62,39 @@ describe('tab', () => {
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panelId', accessor, groupPanel);
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalled();
expect(groupView.canDisplayOverlay).toHaveBeenCalled();
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
test('that if you drag over yourself no drop target is shown', () => {
test('that if you drag over yourself a drop target is shown', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
};
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
@ -99,12 +106,16 @@ describe('tab', () => {
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -116,11 +127,11 @@ describe('tab', () => {
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
test('that if you drag over another tab a drop target is shown', () => {
@ -149,12 +160,16 @@ describe('tab', () => {
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -169,7 +184,7 @@ describe('tab', () => {
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
@ -199,12 +214,16 @@ describe('tab', () => {
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -225,7 +244,7 @@ describe('tab', () => {
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
@ -255,12 +274,16 @@ describe('tab', () => {
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -281,7 +304,7 @@ describe('tab', () => {
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
});

View File

@ -0,0 +1,63 @@
import { DockviewApi } from '../../../../api/component.api';
import { DockviewPanelApi, TitleEvent } from '../../../../api/dockviewPanelApi';
import { DefaultTab } from '../../../../dockview/components/tab/defaultTab';
import { fromPartial } from '@total-typescript/shoehorn';
import { Emitter } from '../../../../events';
import { fireEvent } from '@testing-library/dom';
describe('defaultTab', () => {
test('that title updates', () => {
const cut = new DefaultTab();
let el = cut.element.querySelector('.dv-default-tab-content');
expect(el).toBeTruthy();
expect(el!.textContent).toBe('');
const onDidTitleChange = new Emitter<TitleEvent>();
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: onDidTitleChange.event,
});
const containerApi = fromPartial<DockviewApi>({});
cut.init({
api,
containerApi,
params: {},
title: 'title_abc',
});
el = cut.element.querySelector('.dv-default-tab-content');
expect(el).toBeTruthy();
expect(el!.textContent).toBe('title_abc');
onDidTitleChange.fire({ title: 'title_def' });
expect(el!.textContent).toBe('title_def');
});
test('that click closes tab', () => {
const cut = new DefaultTab();
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn(),
close: jest.fn(),
});
const containerApi = fromPartial<DockviewApi>({});
cut.init({
api,
containerApi,
params: {},
title: 'title_abc',
});
let el = cut.element.querySelector('.dv-default-tab-action');
fireEvent.pointerDown(el!);
expect(api.close).toHaveBeenCalledTimes(0);
fireEvent.click(el!);
expect(api.close).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,66 @@
import { Tabs } from '../../../../dockview/components/titlebar/tabs';
import { fromPartial } from '@total-typescript/shoehorn';
import { DockviewGroupPanel } from '../../../../dockview/dockviewGroupPanel';
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
describe('tabs', () => {
describe('disableCustomScrollbars', () => {
test('enabled by default', () => {
const cut = new Tabs(
fromPartial<DockviewGroupPanel>({}),
fromPartial<DockviewComponent>({
options: {},
}),
{
showTabsOverflowControl: true,
}
);
expect(
cut.element.querySelectorAll(
'.dv-scrollable > .dv-tabs-container'
).length
).toBe(1);
});
test('enabled when disabled flag is false', () => {
const cut = new Tabs(
fromPartial<DockviewGroupPanel>({}),
fromPartial<DockviewComponent>({
options: {
scrollbars: 'custom',
},
}),
{
showTabsOverflowControl: true,
}
);
expect(
cut.element.querySelectorAll(
'.dv-scrollable > .dv-tabs-container'
).length
).toBe(1);
});
test('disabled when disabled flag is true', () => {
const cut = new Tabs(
fromPartial<DockviewGroupPanel>({}),
fromPartial<DockviewComponent>({
options: {
scrollbars: 'native',
},
}),
{
showTabsOverflowControl: true,
}
);
expect(
cut.element.querySelectorAll(
'.dv-scrollable > .dv-tabs-container'
).length
).toBe(0);
});
});
});

View File

@ -9,16 +9,18 @@ import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanel
import { fireEvent } from '@testing-library/dom';
import { TestPanel } from '../../dockviewGroupPanelModel.spec';
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
import { fromPartial } from '@total-typescript/shoehorn';
import { DockviewPanelApi } from '../../../../api/dockviewPanelApi';
describe('tabsContainer', () => {
test('that an external event does not render a drop target and calls through to the group mode', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
@ -35,54 +37,52 @@ describe('tabsContainer', () => {
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
.getElementsByClassName('dv-void-container')
.item(0) as HTMLElement;
if (!emptySpace) {
if (!emptySpace!) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
() => 100
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
fireEvent.dragEnter(emptySpace!);
fireEvent.dragOver(emptySpace!);
expect(groupView.canDisplayOverlay).toBeCalled();
expect(groupView.canDisplayOverlay).toHaveBeenCalled();
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
test('that a drag over event from another tab should render a drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const dropTargetContainer = document.createElement('div');
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
// dropTargetContainer: new DropTargetAnchorContainer(
// dropTargetContainer
// ),
});
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
@ -92,23 +92,22 @@ describe('tabsContainer', () => {
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
.getElementsByClassName('dv-void-container')
.item(0) as HTMLElement;
if (!emptySpace) {
if (!emptySpace!) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -123,25 +122,29 @@ describe('tabsContainer', () => {
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
fireEvent.dragEnter(emptySpace!);
fireEvent.dragOver(emptySpace!);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
// expect(
// dropTargetContainer.getElementsByClassName('dv-drop-target-anchor')
// .length
// ).toBe(1);
});
test('that dropping over the empty space should render a drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
@ -160,7 +163,6 @@ describe('tabsContainer', () => {
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
@ -169,17 +171,17 @@ describe('tabsContainer', () => {
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
.getElementsByClassName('dv-void-container')
.item(0) as HTMLElement;
if (!emptySpace) {
if (!emptySpace!) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -188,25 +190,25 @@ describe('tabsContainer', () => {
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
fireEvent.dragEnter(emptySpace!);
fireEvent.dragOver(emptySpace!);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
test('that dropping the first tab should render a drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
@ -225,7 +227,6 @@ describe('tabsContainer', () => {
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
@ -234,17 +235,17 @@ describe('tabsContainer', () => {
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
.getElementsByClassName('dv-void-container')
.item(0) as HTMLElement;
if (!emptySpace) {
if (!emptySpace!) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -253,25 +254,25 @@ describe('tabsContainer', () => {
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
fireEvent.dragEnter(emptySpace!);
fireEvent.dragOver(emptySpace!);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
test('that dropping a tab from another component should not render a drop target', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
@ -289,7 +290,6 @@ describe('tabsContainer', () => {
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
@ -298,17 +298,17 @@ describe('tabsContainer', () => {
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
.getElementsByClassName('dv-void-container')
.item(0) as HTMLElement;
if (!emptySpace) {
if (!emptySpace!) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
() => 100
);
@ -323,36 +323,35 @@ describe('tabsContainer', () => {
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
fireEvent.dragEnter(emptySpace!);
fireEvent.dragOver(emptySpace!);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
test('left actions', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}) as DockviewComponent;
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
let query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
'.dv-tabs-and-actions-container > .dv-left-actions-container'
);
expect(query.length).toBe(1);
@ -365,7 +364,7 @@ describe('tabsContainer', () => {
cut.setLeftActionsElement(left);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
'.dv-tabs-and-actions-container > .dv-left-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
@ -380,7 +379,7 @@ describe('tabsContainer', () => {
cut.setLeftActionsElement(left2);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
'.dv-tabs-and-actions-container > .dv-left-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
@ -392,7 +391,7 @@ describe('tabsContainer', () => {
cut.setLeftActionsElement(undefined);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
'.dv-tabs-and-actions-container > .dv-left-actions-container'
);
expect(query.length).toBe(1);
@ -400,25 +399,24 @@ describe('tabsContainer', () => {
});
test('right actions', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}) as DockviewComponent;
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
let query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
'.dv-tabs-and-actions-container > .dv-right-actions-container'
);
expect(query.length).toBe(1);
@ -431,7 +429,7 @@ describe('tabsContainer', () => {
cut.setRightActionsElement(right);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
'.dv-tabs-and-actions-container > .dv-right-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
@ -446,7 +444,7 @@ describe('tabsContainer', () => {
cut.setRightActionsElement(right2);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
'.dv-tabs-and-actions-container > .dv-right-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
@ -458,7 +456,7 @@ describe('tabsContainer', () => {
cut.setRightActionsElement(undefined);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
'.dv-tabs-and-actions-container > .dv-right-actions-container'
);
expect(query.length).toBe(1);
@ -466,28 +464,27 @@ describe('tabsContainer', () => {
});
test('that a tab will become floating when clicked if not floating and shift is selected', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
}) as DockviewComponent;
const accessor = fromPartial<DockviewComponent>({
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
doSetGroupActive: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { isFloating: false } as any,
api: { location: { type: 'grid' } } as any,
}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const container = cut.element.querySelector('.void-container')!;
const container = cut.element.querySelector('.dv-void-container')!;
expect(container).toBeTruthy();
jest.spyOn(cut.element, 'getBoundingClientRect').mockImplementation(
@ -502,52 +499,49 @@ describe('tabsContainer', () => {
return { top: 10, left: 20, width: 0, height: 0 } as any;
});
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.addFloatingGroup).toBeCalledWith(
groupPanel,
{
x: 100,
y: 60,
},
{ inDragMode: true }
);
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
expect(eventPreventDefaultSpy).toBeCalledTimes(1);
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(groupPanel);
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(groupPanel, {
x: 100,
y: 60,
inDragMode: true,
});
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
const event2 = new KeyboardEvent('pointerdown', { shiftKey: false });
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(container, event2);
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
expect(eventPreventDefaultSpy2).toBeCalledTimes(0);
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
expect(eventPreventDefaultSpy2).toHaveBeenCalledTimes(0);
});
test('that a tab that is already floating cannot be floated again', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
}) as DockviewComponent;
const accessor = fromPartial<DockviewComponent>({
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
doSetGroupActive: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { isFloating: true } as any,
api: { location: { type: 'floating' } } as any,
}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const container = cut.element.querySelector('.void-container')!;
const container = cut.element.querySelector('.dv-void-container')!;
expect(container).toBeTruthy();
jest.spyOn(cut.element, 'getBoundingClientRect').mockImplementation(
@ -562,41 +556,97 @@ describe('tabsContainer', () => {
return { top: 10, left: 20, width: 0, height: 0 } as any;
});
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
expect(eventPreventDefaultSpy).toBeCalledTimes(0);
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(groupPanel);
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(0);
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
const event2 = new KeyboardEvent('pointerdown', { shiftKey: false });
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(container, event2);
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
expect(eventPreventDefaultSpy2).toBeCalledTimes(0);
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
expect(eventPreventDefaultSpy2).toHaveBeenCalledTimes(0);
});
test('that selecting a tab with shift down will move that tab into a new floating group', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
}) as DockviewComponent;
const accessor = fromPartial<DockviewComponent>({
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { isFloating: true } as any,
api: { location: { type: 'floating' } } as any,
model: {} as any,
}) as DockviewGroupPanel;
});
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const createPanel = (id: string) =>
fromPartial<IDockviewPanel>({
id,
view: {
tab: {
element: document.createElement('div'),
},
content: {
element: document.createElement('div'),
},
},
});
const panel = createPanel('test_id');
cut.openPanel(panel);
const el = cut.element.querySelector('.dv-tab')!;
expect(el).toBeTruthy();
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
const preventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(el, event);
// a floating group with a single tab shouldn't be eligible
expect(preventDefaultSpy).toHaveBeenCalledTimes(0);
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
const panel2 = createPanel('test_id_2');
cut.openPanel(panel2);
fireEvent(el, event);
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
});
test('pre header actions', () => {
const accessor = fromPartial<DockviewComponent>({
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { location: { type: 'grid' } } as any,
model: {} as any,
}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
@ -620,22 +670,198 @@ describe('tabsContainer', () => {
const panel = new panelMock('test_id');
cut.openPanel(panel);
const el = cut.element.querySelector('.tab')!;
expect(el).toBeTruthy();
let result = cut.element.querySelector('.dv-pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const preventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(el, event);
const actions = document.createElement('div');
cut.setPrefixActionsElement(actions);
// a floating group with a single tab shouldn't be eligible
expect(preventDefaultSpy).toBeCalledTimes(0);
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
result = cut.element.querySelector('.dv-pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(actions);
const panel2 = new panelMock('test_id_2');
const updatedActions = document.createElement('div');
cut.setPrefixActionsElement(updatedActions);
result = cut.element.querySelector('.dv-pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(updatedActions);
cut.setPrefixActionsElement(undefined);
result = cut.element.querySelector('.dv-pre-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
});
test('left header actions', () => {
const accessor = fromPartial<DockviewComponent>({
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { location: { type: 'grid' } } as any,
model: {} as any,
}) as DockviewGroupPanel;
});
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
const partial: Partial<IDockviewPanel> = {
id,
view: {
tab: {
element: document.createElement('div'),
} as any,
content: {
element: document.createElement('div'),
} as any,
} as any,
};
return partial as IDockviewPanel;
});
const panel = new panelMock('test_id');
cut.openPanel(panel);
let result = cut.element.querySelector('.dv-left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
const actions = document.createElement('div');
cut.setLeftActionsElement(actions);
result = cut.element.querySelector('.dv-left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(actions);
const updatedActions = document.createElement('div');
cut.setLeftActionsElement(updatedActions);
result = cut.element.querySelector('.dv-left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(updatedActions);
cut.setLeftActionsElement(undefined);
result = cut.element.querySelector('.dv-left-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
});
test('right header actions', () => {
const accessor = fromPartial<DockviewComponent>({
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
api: { location: { type: 'grid' } } as any,
model: {} as any,
}) as DockviewGroupPanel;
});
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
const partial: Partial<IDockviewPanel> = {
id,
view: {
tab: {
element: document.createElement('div'),
} as any,
content: {
element: document.createElement('div'),
} as any,
} as any,
};
return partial as IDockviewPanel;
});
const panel = new panelMock('test_id');
cut.openPanel(panel);
let result = cut.element.querySelector('.dv-right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
const actions = document.createElement('div');
cut.setRightActionsElement(actions);
result = cut.element.querySelector('.dv-right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(actions);
const updatedActions = document.createElement('div');
cut.setRightActionsElement(updatedActions);
result = cut.element.querySelector('.dv-right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(1);
expect(result!.childNodes.item(0)).toBe(updatedActions);
cut.setRightActionsElement(undefined);
result = cut.element.querySelector('.dv-right-actions-container');
expect(result).toBeTruthy();
expect(result!.childNodes.length).toBe(0);
});
test('class dv-single-tab is present when only one tab exists`', () => {
const cut = new TabsContainer(
fromPartial<DockviewComponent>({
options: {},
onDidOptionsChange: jest.fn(),
}),
fromPartial<DockviewGroupPanel>({})
);
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
const panel1 = new TestPanel(
'panel_1',
fromPartial<DockviewPanelApi>({})
);
cut.openPanel(panel1);
expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy();
const panel2 = new TestPanel(
'panel_2',
fromPartial<DockviewPanelApi>({})
);
cut.openPanel(panel2);
fireEvent(el, event);
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
expect(preventDefaultSpy).toBeCalledTimes(1);
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
cut.closePanel(panel1);
expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy();
cut.closePanel(panel2);
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
});
});

View File

@ -0,0 +1,20 @@
import { VoidContainer } from '../../../../dockview/components/titlebar/voidContainer';
import { fromPartial } from '@total-typescript/shoehorn';
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../../../../dockview/dockviewGroupPanel';
import { fireEvent } from '@testing-library/dom';
describe('voidContainer', () => {
test('that `pointerDown` triggers activation', () => {
const accessor = fromPartial<DockviewComponent>({
doSetGroupActive: jest.fn(),
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(accessor.doSetGroupActive).not.toHaveBeenCalled();
fireEvent.pointerDown(cut.element);
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(group);
});
});

View File

@ -1,27 +0,0 @@
import { DockviewApi } from '../../../../api/component.api';
import { Watermark } from '../../../../dockview/components/watermark/watermark';
describe('watermark', () => {
test('that the group is closed when the close button is clicked', () => {
const cut = new Watermark();
const mockApi = jest.fn<Partial<DockviewApi>, any[]>(() => {
return {
removeGroup: jest.fn(),
};
});
const api = <DockviewApi>new mockApi();
const group = jest.fn() as any;
cut.init({ containerApi: api });
cut.updateParentGroup(group, true);
const closeEl = cut.element.querySelector('.close-action')!;
expect(closeEl).toBeTruthy();
closeEl.dispatchEvent(new Event('click'));
expect(api.removeGroup).toHaveBeenCalledWith(group);
});
});

View File

@ -0,0 +1,190 @@
import { DockviewComponent } from '../../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { fromPartial } from '@total-typescript/shoehorn';
import { GroupOptions } from '../../dockview/dockviewGroupPanelModel';
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
import { DockviewPanelModelMock } from '../__mocks__/mockDockviewPanelModel';
import { IContentRenderer, ITabRenderer } from '../../dockview/types';
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { ContentContainer } from '../../dockview/components/panel/content';
describe('dockviewGroupPanel', () => {
test('default minimum/maximium width/height', () => {
const accessor = fromPartial<DockviewComponent>({
onDidActivePanelChange: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const options = fromPartial<GroupOptions>({});
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
expect(cut.minimumWidth).toBe(100);
expect(cut.minimumHeight).toBe(100);
expect(cut.maximumHeight).toBe(Number.MAX_SAFE_INTEGER);
expect(cut.maximumWidth).toBe(Number.MAX_SAFE_INTEGER);
});
test('that onDidActivePanelChange is configured at inline', () => {
const accessor = fromPartial<DockviewComponent>({
onDidActivePanelChange: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
api: {},
renderer: 'always',
overlayRenderContainer: {
attach: jest.fn(),
detatch: jest.fn(),
},
doSetGroupActive: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const options = fromPartial<GroupOptions>({});
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
let counter = 0;
cut.api.onDidActivePanelChange((event) => {
counter++;
});
cut.model.openPanel(
fromPartial<IDockviewPanel>({
updateParentGroup: jest.fn(),
view: {
tab: { element: document.createElement('div') },
content: new ContentContainer(accessor, cut.model),
},
api: {
renderer: 'onlyWhenVisible',
onDidTitleChange: jest.fn(),
onDidParametersChange: jest.fn(),
},
layout: jest.fn(),
runEvents: jest.fn(),
})
);
expect(counter).toBe(1);
});
test('group constraints', () => {
const accessor = fromPartial<DockviewComponent>({
onDidActivePanelChange: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
doSetGroupActive: jest.fn(),
overlayRenderContainer: fromPartial<OverlayRenderContainer>({
attach: jest.fn(),
detatch: jest.fn(),
}),
options: {},
onDidOptionsChange: jest.fn(),
});
const options = fromPartial<GroupOptions>({});
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
cut.api.setConstraints({
minimumHeight: 10,
maximumHeight: 100,
minimumWidth: 20,
maximumWidth: 200,
});
// initial constraints
expect(cut.minimumWidth).toBe(20);
expect(cut.minimumHeight).toBe(10);
expect(cut.maximumHeight).toBe(100);
expect(cut.maximumWidth).toBe(200);
const panelModel = new DockviewPanelModelMock(
'content_component',
fromPartial<IContentRenderer>({
element: document.createElement('div'),
}),
'tab_component',
fromPartial<ITabRenderer>({
element: document.createElement('div'),
})
);
const panel = new DockviewPanel(
'panel_id',
'component_id',
undefined,
accessor,
accessor.api,
cut,
panelModel,
{
renderer: 'onlyWhenVisible',
minimumWidth: 21,
minimumHeight: 11,
maximumHeight: 101,
maximumWidth: 201,
}
);
cut.model.openPanel(panel);
// active panel constraints
expect(cut.minimumWidth).toBe(21);
expect(cut.minimumHeight).toBe(11);
expect(cut.maximumHeight).toBe(101);
expect(cut.maximumWidth).toBe(201);
const panel2 = new DockviewPanel(
'panel_id',
'component_id',
undefined,
accessor,
accessor.api,
cut,
panelModel,
{
renderer: 'onlyWhenVisible',
minimumWidth: 22,
minimumHeight: 12,
maximumHeight: 102,
maximumWidth: 202,
}
);
cut.model.openPanel(panel2);
// active panel constraints
expect(cut.minimumWidth).toBe(22);
expect(cut.minimumHeight).toBe(12);
expect(cut.maximumHeight).toBe(102);
expect(cut.maximumWidth).toBe(202);
const panel3 = new DockviewPanel(
'panel_id',
'component_id',
undefined,
accessor,
accessor.api,
cut,
panelModel,
{
renderer: 'onlyWhenVisible',
}
);
cut.model.openPanel(panel3);
// active panel without specified constraints so falls back to group constraints
expect(cut.minimumWidth).toBe(20);
expect(cut.minimumHeight).toBe(10);
expect(cut.maximumHeight).toBe(100);
expect(cut.maximumWidth).toBe(200);
});
});

View File

@ -7,7 +7,7 @@ import {
ITabRenderer,
IWatermarkRenderer,
} from '../../dockview/types';
import { PanelUpdateEvent } from '../../panel/types';
import { PanelUpdateEvent, Parameters } from '../../panel/types';
import {
DockviewGroupPanelModel,
GroupOptions,
@ -20,6 +20,11 @@ import { IDockviewPanel } from '../../dockview/dockviewPanel';
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { WatermarkRendererInitParameters } from '../../dockview/types';
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
import { Emitter } from '../../events';
import { fromPartial } from '@total-typescript/shoehorn';
import { TabLocation } from '../../dockview/framework';
enum GroupChangeKind2 {
ADD_PANEL,
@ -32,12 +37,16 @@ class TestModel implements IDockviewPanelModel {
readonly contentComponent: string;
readonly tab: ITabRenderer;
constructor(id: string) {
constructor(readonly id: string) {
this.content = new TestHeaderPart(id);
this.contentComponent = id;
this.tab = new TestContentPart(id);
}
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
return new TestHeaderPart(this.id);
}
update(event: PanelUpdateEvent): void {
//
}
@ -93,10 +102,6 @@ class Watermark implements IWatermarkRenderer {
return {};
}
updateParentGroup() {
//
}
dispose() {
//
}
@ -178,7 +183,7 @@ export class TestPanel implements IDockviewPanel {
return this._group!;
}
get params(): Record<string, any> {
get params(): Parameters {
return {};
}
@ -194,7 +199,11 @@ export class TestPanel implements IDockviewPanel {
this._params = params;
}
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void {
updateParentGroup(group: DockviewGroupPanel): void {
//
}
runEvents(): void {
//
}
@ -226,7 +235,7 @@ export class TestPanel implements IDockviewPanel {
}
}
describe('groupview', () => {
describe('dockviewGroupPanelModel', () => {
let groupview: DockviewGroupPanel;
let dockview: DockviewComponent;
let options: GroupOptions;
@ -234,13 +243,21 @@ describe('groupview', () => {
let removePanelMock: jest.Mock;
let removeGroupMock: jest.Mock;
let panelApi: DockviewPanelApi;
beforeEach(() => {
removePanelMock = jest.fn();
removeGroupMock = jest.fn();
options = {};
dockview = (<Partial<DockviewComponent>>{
panelApi = fromPartial<DockviewPanelApi>({
renderer: 'onlyWhenVisible',
onDidTitleChange: new Emitter().event,
onDidParametersChange: new Emitter().event,
});
dockview = fromPartial<DockviewComponent>({
options: {},
createWatermarkComponent: () => new Watermark(),
doSetGroupActive: jest.fn(),
@ -249,16 +266,21 @@ describe('groupview', () => {
removeGroup: removeGroupMock,
onDidAddPanel: () => ({ dispose: jest.fn() }),
onDidRemovePanel: () => ({ dispose: jest.fn() }),
}) as DockviewComponent;
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
),
onDidOptionsChange: () => ({ dispose: jest.fn() }),
});
groupview = new DockviewGroupPanel(dockview, 'groupview-1', options);
groupview.initialize();
});
test('panel events are captured during de-serialization', () => {
const panel1 = new TestPanel('panel1', jest.fn() as any);
const panel2 = new TestPanel('panel2', jest.fn() as any);
const panel3 = new TestPanel('panel3', jest.fn() as any);
const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', panelApi);
const groupview2 = new DockviewGroupPanel(dockview, 'groupview-2', {
panels: [panel1, panel2, panel3],
@ -342,9 +364,9 @@ describe('groupview', () => {
})
);
const panel1 = new TestPanel('panel1', jest.fn() as any);
const panel2 = new TestPanel('panel2', jest.fn() as any);
const panel3 = new TestPanel('panel3', jest.fn() as any);
const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', panelApi);
expect(events.length).toBe(0);
@ -422,9 +444,9 @@ describe('groupview', () => {
});
test('moveToPrevious and moveToNext', () => {
const panel1 = new TestPanel('panel1', jest.fn() as any);
const panel2 = new TestPanel('panel2', jest.fn() as any);
const panel3 = new TestPanel('panel3', jest.fn() as any);
const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', panelApi);
groupview.model.openPanel(panel1);
groupview.model.openPanel(panel2);
@ -457,20 +479,20 @@ describe('groupview', () => {
test('default', () => {
let viewQuery = groupview.element.querySelectorAll(
'.groupview > .tabs-and-actions-container'
'.dv-groupview > .dv-tabs-and-actions-container'
);
expect(viewQuery).toBeTruthy();
viewQuery = groupview.element.querySelectorAll(
'.groupview > .content-container'
'.dv-groupview > .dv-content-container'
);
expect(viewQuery).toBeTruthy();
});
test('closeAllPanels with panels', () => {
const panel1 = new TestPanel('panel1', jest.fn() as any);
const panel2 = new TestPanel('panel2', jest.fn() as any);
const panel3 = new TestPanel('panel3', jest.fn() as any);
const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', panelApi);
groupview.model.openPanel(panel1);
groupview.model.openPanel(panel2);
@ -478,21 +500,25 @@ describe('groupview', () => {
groupview.model.closeAllPanels();
expect(removePanelMock).toBeCalledWith(panel1);
expect(removePanelMock).toBeCalledWith(panel2);
expect(removePanelMock).toBeCalledWith(panel3);
expect(removePanelMock).toHaveBeenCalledWith(panel1, undefined);
expect(removePanelMock).toHaveBeenCalledWith(panel2, undefined);
expect(removePanelMock).toHaveBeenCalledWith(panel3, undefined);
});
test('closeAllPanels with no panels', () => {
groupview.model.closeAllPanels();
expect(removeGroupMock).toBeCalledWith(groupview);
expect(removeGroupMock).toHaveBeenCalledWith(groupview);
});
test('that group is set on panel during onDidAddPanel event', () => {
const cut = new DockviewComponent({
parentElement: document.createElement('div'),
components: {
component: TestContentPart,
const cut = new DockviewComponent(document.createElement('div'), {
createComponent(options) {
switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
},
});
@ -505,12 +531,19 @@ describe('groupview', () => {
});
test('toJSON() default', () => {
const dockviewComponent = new DockviewComponent({
parentElement: document.createElement('div'),
components: {
component: TestContentPart,
},
});
const dockviewComponent = new DockviewComponent(
document.createElement('div'),
{
createComponent(options) {
switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
},
}
);
const cut = new DockviewGroupPanelModel(
document.createElement('div'),
@ -528,12 +561,19 @@ describe('groupview', () => {
});
test('toJSON() locked and hideHeader', () => {
const dockviewComponent = new DockviewComponent({
parentElement: document.createElement('div'),
components: {
component: TestContentPart,
},
});
const dockviewComponent = new DockviewComponent(
document.createElement('div'),
{
createComponent(options) {
switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
},
}
);
const cut = new DockviewGroupPanelModel(
document.createElement('div'),
@ -556,12 +596,19 @@ describe('groupview', () => {
});
test("that openPanel with skipSetActive doesn't set panel to active", () => {
const dockviewComponent = new DockviewComponent({
parentElement: document.createElement('div'),
components: {
component: TestContentPart,
},
});
const dockviewComponent = new DockviewComponent(
document.createElement('div'),
{
createComponent(options) {
switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
},
}
);
const groupviewContainer = document.createElement('div');
const cut = new DockviewGroupPanelModel(
@ -572,24 +619,24 @@ describe('groupview', () => {
null as any
);
const contentContainer = groupviewContainer
.getElementsByClassName('content-container')
.getElementsByClassName('dv-content-container')
.item(0)!.childNodes;
const panel1 = new TestPanel('id_1', null as any);
const panel1 = new TestPanel('id_1', panelApi);
cut.openPanel(panel1);
expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel1.view.content.element);
const panel2 = new TestPanel('id_2', null as any);
const panel2 = new TestPanel('id_2', panelApi);
cut.openPanel(panel2);
expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel2.view.content.element);
const panel3 = new TestPanel('id_2', null as any);
const panel3 = new TestPanel('id_2', panelApi);
cut.openPanel(panel3, { skipSetPanelActive: true });
cut.openPanel(panel3, { skipSetActive: true });
expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel2.view.content.element);
@ -599,18 +646,15 @@ describe('groupview', () => {
});
test('that should not show drop target is external event', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {},
getPanel: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
@ -639,39 +683,205 @@ describe('groupview', () => {
new groupPanelMock() as DockviewGroupPanel
);
const element = container
.getElementsByClassName('content-container')
.item(0)!;
let counter = 0;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
cut.onUnhandledDragOverEvent(() => {
counter++;
});
const element = container
.getElementsByClassName('dv-content-container')
.item(0)! as HTMLElement;
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
expect(counter).toBe(1);
expect(
element.getElementsByClassName('drop-target-dropzone').length
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
test('that should not show drop target if dropping on self', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
test('that the .locked behaviour is as', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {},
getPanel: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidOptionsChange: jest.fn(),
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const groupPanelMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
id: 'testgroupid',
model: groupView,
};
}
);
const container = document.createElement('div');
const cut = new DockviewGroupPanelModel(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as DockviewGroupPanel
);
cut.onUnhandledDragOverEvent((e) => {
e.accept();
});
const element = container
.getElementsByClassName('dv-content-container')
.item(0)! as HTMLElement;
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
function run(value: number) {
fireEvent.dragEnter(element);
fireEvent(
element,
createOffsetDragOverEvent({ clientX: value, clientY: value })
);
}
// base case - not locked
cut.locked = false;
run(10);
expect(
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
fireEvent.dragEnd(element);
// special case - locked with no possible target
cut.locked = 'no-drop-target';
run(10);
expect(
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
fireEvent.dragEnd(element);
// standard locked - only show if not center target
cut.locked = true;
run(10);
expect(
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
fireEvent.dragEnd(element);
// standard locked but for center target - expect not shown
cut.locked = true;
run(25);
expect(
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
fireEvent.dragEnd(element);
});
test('that should show drop target if dropping on self', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
),
onDidOptionsChange: jest.fn(),
});
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const container = document.createElement('div');
const cut = new DockviewGroupPanelModel(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as DockviewGroupPanel
);
let counter = 0;
cut.onUnhandledDragOverEvent(() => {
counter++;
});
cut.openPanel(new TestPanel('panel1', panelApi));
const element = container
.getElementsByClassName('dv-content-container')
.item(0)! as HTMLElement;
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(counter).toBe(0);
expect(
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
test('that should allow drop when dropping on self for same component id', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
),
onDidOptionsChange: jest.fn(),
});
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
@ -698,16 +908,23 @@ describe('groupview', () => {
new groupPanelMock() as DockviewGroupPanel
);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
let counter = 0;
cut.onUnhandledDragOverEvent(() => {
counter++;
});
cut.openPanel(new TestPanel('panel1', panelApi));
cut.openPanel(new TestPanel('panel2', panelApi));
const element = container
.getElementsByClassName('content-container')
.item(0)!;
.getElementsByClassName('dv-content-container')
.item(0) as HTMLElement;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
@ -717,94 +934,28 @@ describe('groupview', () => {
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
expect(counter).toBe(0);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that should not allow drop when dropping on self for same component id', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const container = document.createElement('div');
const cut = new DockviewGroupPanelModel(
container,
accessor,
'groupviewid',
{},
new groupPanelMock() as DockviewGroupPanel
);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const element = container
.getElementsByClassName('content-container')
.item(0)!;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(0);
expect(
element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
test('that should not allow drop when not dropping for different component id', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
id: 'testcomponentid',
options: {
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
),
onDidOptionsChange: jest.fn(),
});
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
@ -831,17 +982,23 @@ describe('groupview', () => {
new groupPanelMock() as DockviewGroupPanel
);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
let counter = 0;
cut.onUnhandledDragOverEvent(() => {
counter++;
});
cut.openPanel(new TestPanel('panel1', panelApi));
cut.openPanel(new TestPanel('panel2', panelApi));
const element = container
.getElementsByClassName('content-container')
.item(0)!;
.getElementsByClassName('dv-content-container')
.item(0) as HTMLElement;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')],
@ -851,10 +1008,10 @@ describe('groupview', () => {
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1);
expect(counter).toBe(1);
expect(
element.getElementsByClassName('drop-target-dropzone').length
element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
@ -933,17 +1090,17 @@ describe('groupview', () => {
container.getElementsByClassName('watermark-test-container').length
).toBe(1);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel1', panelApi));
expect(
container.getElementsByClassName('watermark-test-container').length
).toBe(0);
expect(
container.getElementsByClassName('tabs-and-actions-container')
container.getElementsByClassName('dv-tabs-and-actions-container')
.length
).toBe(1);
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', panelApi));
expect(
container.getElementsByClassName('watermark-test-container').length
@ -961,7 +1118,7 @@ describe('groupview', () => {
container.getElementsByClassName('watermark-test-container').length
).toBe(1);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel1', panelApi));
expect(
container.getElementsByClassName('watermark-test-container').length

View File

@ -3,33 +3,37 @@ import { DockviewApi } from '../../api/component.api';
import { DockviewPanel } from '../../dockview/dockviewPanel';
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { fromPartial } from '@total-typescript/shoehorn';
describe('dockviewPanel', () => {
test('update title', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {
const api = fromPartial<DockviewApi>({});
const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
} as any;
},
});
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
};
const model = fromPartial<IDockviewPanelModel>({
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
});
const api = new dockviewApiMock();
const accessor = new accessorMock();
const group = new groupMock();
const model = <IDockviewPanelModel>new panelModelMock();
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
const cut = new DockviewPanel(
'fake-id',
'fake-component',
undefined,
accessor,
api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
let latestTitle: string | undefined = undefined;
@ -51,30 +55,32 @@ describe('dockviewPanel', () => {
});
test('that .setTitle updates the title', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {
const api = fromPartial<DockviewApi>({});
const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
} as any;
},
});
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
};
const model = fromPartial<IDockviewPanelModel>({
update: jest.fn(),
init: jest.fn(),
});
const api = new dockviewApiMock();
const accessor = new accessorMock();
const group = new groupMock();
const model = <IDockviewPanelModel>new panelModelMock();
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
const cut = new DockviewPanel(
'fake-id',
'fake-component',
undefined,
accessor,
api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
cut.init({ title: 'myTitle', params: {} });
expect(cut.title).toBe('myTitle');
@ -87,29 +93,39 @@ describe('dockviewPanel', () => {
});
test('dispose cleanup', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {} as any;
const api = fromPartial<DockviewApi>({});
const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest
.fn()
.mockReturnValue({ dispose: jest.fn() }),
onDidLocationChange: jest
.fn()
.mockReturnValue({ dispose: jest.fn() }),
onDidActiveChange: jest
.fn()
.mockReturnValue({ dispose: jest.fn() }),
},
});
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
};
const model = fromPartial<IDockviewPanelModel>({
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
});
const api = new dockviewApiMock();
const accessor = new accessorMock();
const group = new groupMock();
const model = <IDockviewPanelModel>new panelModelMock();
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
const cut = new DockviewPanel(
'fake-id',
'fake-component',
undefined,
accessor,
api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
cut.init({ params: {}, title: 'title' });
@ -119,29 +135,33 @@ describe('dockviewPanel', () => {
});
test('get params', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {} as any;
const api = fromPartial<DockviewApi>({});
const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
},
});
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
};
const model = fromPartial<IDockviewPanelModel>({
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
});
const api = new dockviewApiMock();
const accessor = new accessorMock();
const group = new groupMock();
const model = <IDockviewPanelModel>new panelModelMock();
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
const cut = new DockviewPanel(
'fake-id',
'fake-component',
undefined,
accessor,
api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
expect(cut.params).toEqual(undefined);
@ -151,64 +171,72 @@ describe('dockviewPanel', () => {
});
test('setSize propagates to underlying group', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {} as any;
const api = fromPartial<DockviewApi>({});
const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
setSize: jest.fn(),
},
});
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {
api: {
setSize: jest.fn(),
},
} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
};
const model = fromPartial<IDockviewPanelModel>({
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
});
const api = new dockviewApiMock();
const accessor = new accessorMock();
const group = new groupMock();
const model = <IDockviewPanelModel>new panelModelMock();
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
const cut = new DockviewPanel(
'fake-id',
'fake-component',
undefined,
accessor,
api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
cut.api.setSize({ height: 123, width: 456 });
expect(group.api.setSize).toBeCalledWith({ height: 123, width: 456 });
expect(group.api.setSize).toBeCalledTimes(1);
expect(group.api.setSize).toHaveBeenCalledWith({
height: 123,
width: 456,
});
expect(group.api.setSize).toHaveBeenCalledTimes(1);
});
test('updateParameter', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
return {} as any;
const api = fromPartial<DockviewApi>({});
const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
},
});
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
};
const model = fromPartial<IDockviewPanelModel>({
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
});
const api = new dockviewApiMock();
const accessor = new accessorMock();
const group = new groupMock();
const model = <IDockviewPanelModel>new panelModelMock();
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
const cut = new DockviewPanel(
'fake-id',
'fake-component',
undefined,
accessor,
api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
cut.init({ params: { a: '1', b: '2' }, title: 'A title' });
expect(cut.params).toEqual({ a: '1', b: '2' });
@ -216,6 +244,9 @@ describe('dockviewPanel', () => {
// update 'a' and add 'c'
cut.update({ params: { a: '-1', c: '3' } });
expect(cut.params).toEqual({ a: '-1', b: '2', c: '3' });
expect(model.update).toHaveBeenCalledWith({
params: { a: '-1', b: '2', c: '3' },
});
cut.update({ params: { d: '4', e: '5', f: '6' } });
expect(cut.params).toEqual({
@ -226,6 +257,9 @@ describe('dockviewPanel', () => {
e: '5',
f: '6',
});
expect(model.update).toHaveBeenCalledWith({
params: { a: '-1', b: '2', c: '3', d: '4', e: '5', f: '6' },
});
cut.update({
params: {
@ -246,5 +280,8 @@ describe('dockviewPanel', () => {
g: '',
h: null,
});
expect(model.update).toHaveBeenCalledWith({
params: { a: '-1', b: '2', c: '3', d: '', e: null, g: '', h: null },
});
});
});

View File

@ -1,16 +1,13 @@
import {
DockviewComponent,
IDockviewComponent,
} from '../../dockview/dockviewComponent';
import { DockviewComponent } from '../../dockview/dockviewComponent';
import { DockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { IContentRenderer, ITabRenderer } from '../../dockview/types';
import { GroupPanelFrameworkComponentFactory } from '../../dockview/options';
import { DefaultTab } from '../../dockview/components/tab/defaultTab';
import { fromPartial } from '@total-typescript/shoehorn';
describe('dockviewGroupPanel', () => {
let contentMock: jest.Mock<IContentRenderer>;
let tabMock: jest.Mock<ITabRenderer>;
let accessorMock: jest.Mock<IDockviewComponent>;
let accessorMock: DockviewComponent;
beforeEach(() => {
contentMock = jest.fn<IContentRenderer, []>(() => {
@ -18,8 +15,6 @@ describe('dockviewGroupPanel', () => {
element: document.createElement('div'),
dispose: jest.fn(),
update: jest.fn(),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
};
return partial as IContentRenderer;
});
@ -29,31 +24,35 @@ describe('dockviewGroupPanel', () => {
element: document.createElement('div'),
dispose: jest.fn(),
update: jest.fn(),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
};
return partial as IContentRenderer;
});
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: tabMock,
},
accessorMock = fromPartial<DockviewComponent>({
options: {
createComponent(options) {
switch (options.name) {
case 'contentComponent':
return new contentMock(options.id, options.name);
default:
throw new Error(`unsupported`);
}
},
};
return partial as DockviewComponent;
createTabComponent(options) {
switch (options.name) {
case 'tabComponent':
return new tabMock(options.id, options.name);
default:
throw new Error(`unsupported`);
}
},
},
});
});
test('that dispose is called on content and tab renderers when present', () => {
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
accessorMock,
'id',
'contentComponent',
'tabComponent'
@ -67,7 +66,7 @@ describe('dockviewGroupPanel', () => {
test('that update is called on content and tab renderers when present', () => {
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
accessorMock,
'id',
'contentComponent',
'tabComponent'
@ -81,72 +80,30 @@ describe('dockviewGroupPanel', () => {
expect(cut.tab.update).toHaveBeenCalled();
});
test('that events are fired', () => {
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent',
'tabComponent'
);
const group1 = jest.fn() as any;
const group2 = jest.fn() as any;
cut.updateParentGroup(group1, false);
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1);
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1);
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
1,
false
);
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1);
cut.updateParentGroup(group1, true);
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
2,
true
);
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
cut.updateParentGroup(group2, true);
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2);
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
});
test('that the default tab is created', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: jest
.fn()
.mockImplementation(() => tabMock),
},
accessorMock = fromPartial<DockviewComponent>({
options: {
createComponent(options) {
switch (options.name) {
case 'contentComponent':
return new contentMock(options.id, options.name);
default:
throw new Error(`unsupported`);
}
},
};
return partial as DockviewComponent;
createTabComponent(options) {
switch (options.name) {
case 'tabComponent':
return tabMock;
default:
throw new Error(`unsupported`);
}
},
},
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
accessorMock,
'id',
'contentComponent',
'tabComponent'
@ -156,26 +113,30 @@ describe('dockviewGroupPanel', () => {
});
test('that the provided default tab is chosen when no implementation is provided', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: jest
.fn()
.mockImplementation(() => tabMock),
},
defaultTabComponent: 'tabComponent',
accessorMock = fromPartial<DockviewComponent>({
options: {
defaultTabComponent: 'tabComponent',
createComponent(options) {
switch (options.name) {
case 'contentComponent':
return new contentMock(options.id, options.name);
default:
throw new Error(`unsupported`);
}
},
};
return partial as DockviewComponent;
createTabComponent(options) {
switch (options.name) {
case 'tabComponent':
return tabMock;
default:
throw new Error(`unsupported`);
}
},
},
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
accessorMock,
'id',
'contentComponent'
);
@ -183,56 +144,22 @@ describe('dockviewGroupPanel', () => {
expect(cut.tab).toEqual(tabMock);
});
test('that the framework tab is created when provided tab is a framework tab', () => {
const tab = jest.fn();
const tabFactory = jest.fn().mockImplementation(() => tab);
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
frameworkTabComponents: {
tabComponent: tabMock,
},
frameworkComponentFactory: (<
Partial<GroupPanelFrameworkComponentFactory>
>{
tab: { createComponent: tabFactory },
}) as GroupPanelFrameworkComponentFactory,
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent',
'tabComponent'
);
expect(tabFactory).toHaveBeenCalledWith('id', 'tabComponent', tabMock);
expect(cut.tab).toEqual(tab);
});
test('that is library default tab instance is created when no alternative exists', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: contentMock,
},
accessorMock = fromPartial<DockviewComponent>({
options: {
createComponent(options) {
switch (options.name) {
case 'contentComponent':
return new contentMock(options.id, options.name);
default:
throw new Error(`unsupported`);
}
},
};
return partial as DockviewComponent;
},
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
accessorMock,
'id',
'contentComponent'
);
@ -241,61 +168,33 @@ describe('dockviewGroupPanel', () => {
});
test('that the default content is created', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
components: {
contentComponent: jest.fn().mockImplementation(() => {
accessorMock = fromPartial<DockviewComponent>({
options: {
createComponent(options) {
switch (options.name) {
case 'contentComponent':
return contentMock;
}),
},
default:
throw new Error(`unsupported`);
}
},
};
return partial as DockviewComponent;
createTabComponent(options) {
switch (options.name) {
case 'tabComponent':
return tabMock;
default:
throw new Error(`unsupported`);
}
},
},
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
accessorMock,
'id',
'contentComponent'
);
expect(cut.content).toEqual(contentMock);
});
test('that the framework content is created', () => {
const content = jest.fn();
const contentFactory = jest.fn().mockImplementation(() => content);
accessorMock = jest.fn<DockviewComponent, []>(() => {
const partial: Partial<DockviewComponent> = {
options: {
frameworkComponents: {
contentComponent: contentMock,
},
frameworkComponentFactory: (<
Partial<GroupPanelFrameworkComponentFactory>
>{
content: { createComponent: contentFactory },
}) as GroupPanelFrameworkComponentFactory,
},
};
return partial as DockviewComponent;
});
const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(),
'id',
'contentComponent'
);
expect(contentFactory).toHaveBeenCalledWith(
'id',
'contentComponent',
contentMock
);
expect(cut.content).toEqual(content);
});
});

View File

@ -1,4 +1,9 @@
import { quasiDefaultPrevented, quasiPreventDefault } from '../dom';
import {
disableIframePointEvents,
isInDocument,
quasiDefaultPrevented,
quasiPreventDefault,
} from '../dom';
describe('dom', () => {
test('quasiPreventDefault', () => {
@ -18,4 +23,61 @@ describe('dom', () => {
(event as any)['dv-quasiPreventDefault'] = true;
expect(quasiDefaultPrevented(event)).toBeTruthy();
});
test('isInDocument: DOM element', () => {
const el = document.createElement('div');
expect(isInDocument(el)).toBeFalsy();
document.body.appendChild(el);
expect(isInDocument(el)).toBeTruthy();
});
test('isInDocument: Shadow DOM element', () => {
const el = document.createElement('div');
document.body.appendChild(el);
const shadow = el.attachShadow({ mode: 'open' });
const el2 = document.createElement('div');
expect(isInDocument(el2)).toBeFalsy();
shadow.appendChild(el2);
expect(isInDocument(el2)).toBeTruthy();
});
test('disableIframePointEvents', () => {
const el1 = document.createElement('iframe');
const el2 = document.createElement('iframe');
const el3 = document.createElement('webview');
const el4 = document.createElement('webview');
document.body.appendChild(el1);
document.body.appendChild(el2);
document.body.appendChild(el3);
document.body.appendChild(el4);
el1.style.pointerEvents = 'inherit';
el3.style.pointerEvents = 'inherit';
expect(el1.style.pointerEvents).toBe('inherit');
expect(el2.style.pointerEvents).toBe('');
expect(el3.style.pointerEvents).toBe('inherit');
expect(el4.style.pointerEvents).toBe('');
const f = disableIframePointEvents();
expect(el1.style.pointerEvents).toBe('none');
expect(el2.style.pointerEvents).toBe('none');
expect(el3.style.pointerEvents).toBe('none');
expect(el4.style.pointerEvents).toBe('none');
f.release();
expect(el1.style.pointerEvents).toBe('inherit');
expect(el2.style.pointerEvents).toBe('');
expect(el3.style.pointerEvents).toBe('inherit');
expect(el4.style.pointerEvents).toBe('');
});
});

View File

@ -1,8 +1,8 @@
import {
AsapEvent,
Emitter,
Event,
addDisposableListener,
addDisposableWindowListener,
} from '../events';
describe('events', () => {
@ -82,6 +82,41 @@ describe('events', () => {
});
});
describe('asapEvent', () => {
test('that asapEvents fire once per event-loop-cycle', () => {
jest.useFakeTimers();
const event = new AsapEvent();
let preFireCount = 0;
let postFireCount = 0;
event.onEvent(() => {
preFireCount++;
});
for (let i = 0; i < 100; i++) {
event.fire();
}
/**
* check that subscribing after the events have fired but before the event-loop cycle completes
* results in no event fires.
*/
event.onEvent((e) => {
postFireCount++;
});
expect(preFireCount).toBe(0);
expect(postFireCount).toBe(0);
jest.runAllTimers();
expect(preFireCount).toBe(1);
expect(postFireCount).toBe(0);
});
});
it('should emit a value when any event fires', () => {
const emitter1 = new Emitter<number>();
const emitter2 = new Emitter<number>();
@ -107,73 +142,6 @@ describe('events', () => {
expect(value).toBe(3);
});
it('addDisposableWindowListener with capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const handler = jest.fn();
const disposable = addDisposableWindowListener(
element as any,
'mousedown',
handler,
true
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
handler,
true
);
expect(element.removeEventListener).toBeCalledTimes(0);
disposable.dispose();
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
handler,
true
);
});
it('addDisposableWindowListener without capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const handler = jest.fn();
const disposable = addDisposableWindowListener(
element as any,
'mousedown',
handler
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
handler,
undefined
);
expect(element.removeEventListener).toBeCalledTimes(0);
disposable.dispose();
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
handler,
undefined
);
});
it('addDisposableListener with capture options', () => {
const element = {
addEventListener: jest.fn(),
@ -184,14 +152,14 @@ describe('events', () => {
const disposable = addDisposableListener(
element as any,
'mousedown',
'pointerdown',
handler,
true
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
'pointerdown',
handler,
true
);
@ -202,7 +170,7 @@ describe('events', () => {
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
'pointerdown',
handler,
true
);
@ -218,13 +186,13 @@ describe('events', () => {
const disposable = addDisposableListener(
element as any,
'mousedown',
'pointerdown',
handler
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
'pointerdown',
handler,
undefined
);
@ -235,7 +203,74 @@ describe('events', () => {
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
'pointerdown',
handler,
undefined
);
});
it('addDisposableListener with capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const handler = jest.fn();
const disposable = addDisposableListener(
element as any,
'pointerdown',
handler,
true
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'pointerdown',
handler,
true
);
expect(element.removeEventListener).toBeCalledTimes(0);
disposable.dispose();
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'pointerdown',
handler,
true
);
});
it('addDisposableListener without capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const handler = jest.fn();
const disposable = addDisposableListener(
element as any,
'pointerdown',
handler
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'pointerdown',
handler,
undefined
);
expect(element.removeEventListener).toBeCalledTimes(0);
disposable.dispose();
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'pointerdown',
handler,
undefined
);

View File

@ -17,13 +17,9 @@ class TestPanel implements IGridPanelView {
_onDidChange = new Emitter<IViewSize | undefined>();
readonly onDidChange = this._onDidChange.event;
get isActive(): boolean {
return true;
}
get params(): Record<string, any> {
return {};
}
isVisible: boolean = true;
isActive: boolean = true;
params: Parameters = {};
constructor(
public readonly id: string,
@ -70,8 +66,10 @@ class TestPanel implements IGridPanelView {
}
class ClassUnderTest extends BaseGrid<TestPanel> {
constructor(options: BaseGridOptions) {
super(options);
readonly gridview = this.gridview;
constructor(parentElement: HTMLElement, options: BaseGridOptions) {
super(parentElement, options);
}
doRemoveGroup(
@ -107,24 +105,62 @@ class ClassUnderTest extends BaseGrid<TestPanel> {
}
describe('baseComponentGridview', () => {
test('can add group', () => {
const cut = new ClassUnderTest({
parentElement: document.createElement('div'),
test('that the container is not removed when grid is disposed', () => {
const root = document.createElement('div');
const container = document.createElement('div');
root.appendChild(container);
const cut = new ClassUnderTest(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: true,
});
const events: (TestPanel | undefined)[] = [];
cut.dispose();
expect(container.parentElement).toBe(root);
});
test('that .layout(...) force flag works', () => {
const cut = new ClassUnderTest(document.createElement('div'), {
orientation: Orientation.HORIZONTAL,
proportionalLayout: true,
});
const spy = jest.spyOn(cut.gridview, 'layout');
cut.layout(100, 100);
expect(spy).toHaveBeenCalledTimes(1);
cut.layout(100, 100, false);
expect(spy).toHaveBeenCalledTimes(1);
cut.layout(100, 100, true);
expect(spy).toHaveBeenCalledTimes(2);
cut.layout(150, 150, false);
expect(spy).toHaveBeenCalledTimes(3);
cut.layout(150, 150, true);
expect(spy).toHaveBeenCalledTimes(4);
});
test('can add group', () => {
const cut = new ClassUnderTest(document.createElement('div'), {
orientation: Orientation.HORIZONTAL,
proportionalLayout: true,
});
const events: { type: string; panel: TestPanel | undefined }[] = [];
const disposable = new CompositeDisposable(
cut.onDidAddGroup((event) => {
events.push(event);
cut.onDidAdd((event) => {
events.push({ type: 'add', panel: event });
}),
cut.onDidRemoveGroup((event) => {
events.push(event);
cut.onDidRemove((event) => {
events.push({ type: 'remove', panel: event });
}),
cut.onDidActiveGroupChange((event) => {
events.push(event);
cut.onDidActiveChange((event) => {
events.push({ type: 'active', panel: event });
})
);
@ -141,9 +177,8 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel1);
expect(events.length).toBe(2);
expect(events[0]).toBe(panel1);
expect(events[1]).toBe(panel1);
expect(events.length).toBe(1);
expect(events[0]).toEqual({ type: 'add', panel: panel1 });
const panel2 = new TestPanel(
'id',
@ -158,12 +193,12 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel2);
expect(events.length).toBe(4);
expect(events[2]).toBe(panel2);
expect(events.length).toBe(2);
expect(events[1]).toEqual({ type: 'add', panel: panel2 });
cut.doRemoveGroup(panel1);
expect(events.length).toBe(5);
expect(events[4]).toBe(panel1);
expect(events.length).toBe(3);
expect(events[2]).toEqual({ type: 'remove', panel: panel1 });
disposable.dispose();
cut.dispose();

View File

@ -4,6 +4,8 @@ import {
Gridview,
IGridView,
IViewSize,
SerializedGridview,
getGridLocation,
orthogonal,
} from '../../gridview/gridview';
import { Orientation, Sizing } from '../../splitview/splitview';
@ -17,16 +19,24 @@ class MockGridview implements IGridView {
IViewSize | undefined
>().event;
element: HTMLElement = document.createElement('div');
isVisible: boolean = true;
width: number = 0;
height: number = 0;
constructor() {
constructor(private id?: string) {
this.element.className = 'mock-grid-view';
this.element.id = `${id ?? ''}`;
}
layout(width: number, height: number): void {
//
this.width = width;
this.height = height;
}
toJSON(): object {
if (this.id) {
return { id: this.id };
}
return {};
}
}
@ -723,4 +733,475 @@ describe('gridview', () => {
width: 1000,
});
});
test('re-structuring deep gridivew where a branchnode becomes of length one and is coverted to a leaf node', () => {
const gridview = new Gridview(
false,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
const view4 = new MockGridview('4');
const view5 = new MockGridview('5');
const view6 = new MockGridview('6');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 0]);
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
let el = gridview.element.querySelectorAll('.mock-grid-view');
expect(el.length).toBe(6);
gridview.remove(view2);
el = gridview.element.querySelectorAll('.mock-grid-view');
expect(el.length).toBe(5);
});
test('gridview nested proportional layouts', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
const view4 = new MockGridview('4');
const view5 = new MockGridview('5');
const view6 = new MockGridview('6');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
const views = [view1, view2, view3, view4, view5, view6];
const dimensions = [
{ width: 500, height: 1000 },
{ width: 500, height: 500 },
{ width: 250, height: 500 },
{ width: 250, height: 250 },
{ width: 125, height: 250 },
{ width: 125, height: 250 },
];
expect(
views.map((view) => ({
width: view.width,
height: view.height,
}))
).toEqual(dimensions);
gridview.layout(2000, 1500);
expect(
views.map((view) => ({
width: view.width,
height: view.height,
}))
).toEqual(
dimensions.map(({ width, height }) => ({
width: width * 2,
height: height * 1.5,
}))
);
gridview.layout(200, 2000);
expect(
views.map((view) => ({
width: view.width,
height: view.height,
}))
).toEqual(
dimensions.map(({ width, height }) => ({
width: width * 0.2,
height: height * 2,
}))
);
});
test('that maximizeView retains original dimensions when restored', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
let counter = 0;
const subscription = gridview.onDidMaximizedNodeChange(() => {
counter++;
});
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
const view4 = new MockGridview('4');
const view5 = new MockGridview('5');
const view6 = new MockGridview('6');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
/**
* _____________________________________________
* | | |
* | | 2 |
* | | |
* | 1 |_______________________|
* | | | 4 |
* | | 3 |_____________|
* | | | 5 | 6 |
* |_____________________|_________|______|______|
*/
const views = [view1, view2, view3, view4, view5, view6];
const dimensions = [
{ width: 500, height: 1000 },
{ width: 500, height: 500 },
{ width: 250, height: 500 },
{ width: 250, height: 250 },
{ width: 125, height: 250 },
{ width: 125, height: 250 },
];
function assertLayout() {
expect(
views.map((view) => ({
width: view.width,
height: view.height,
}))
).toEqual(dimensions);
}
// base case assertions
assertLayout();
expect(gridview.hasMaximizedView()).toBeFalsy();
expect(counter).toBe(0);
/**
* maximize each view individually and then return to the standard view
* checking on each iteration that the original layout dimensions
* are restored
*/
for (let i = 0; i < views.length; i++) {
const view = views[i];
gridview.maximizeView(view);
expect(counter).toBe(i * 2 + 1);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.exitMaximizedView();
expect(counter).toBe(i * 2 + 2);
assertLayout();
}
subscription.dispose();
});
test('that maximizedView is exited when a views visibility is changed', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
expect(gridview.hasMaximizedView()).toBeFalsy();
gridview.maximizeView(view2);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.setViewVisible([0], true);
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('that maximizedView is exited when a view is moved', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
const view4 = new MockGridview('4');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
expect(gridview.hasMaximizedView()).toBeFalsy();
gridview.maximizeView(view2);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.moveView([1, 1], 0, 1);
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('that maximizedView is exited when a view is added', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
const view4 = new MockGridview('4');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
expect(gridview.hasMaximizedView()).toBeFalsy();
gridview.maximizeView(view2);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('that maximizedView is exited when a view is removed', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
expect(gridview.hasMaximizedView()).toBeFalsy();
gridview.maximizeView(view2);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.removeView([1, 1]);
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('that maximizedView is cleared when layout is cleared', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
expect(gridview.hasMaximizedView()).toBeFalsy();
gridview.maximizeView(view2);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.clear();
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('that maximizedView is cleared when layout is disposed', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
expect(gridview.hasMaximizedView()).toBeFalsy();
gridview.maximizeView(view2);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.dispose();
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('that maximizedView is cleared when layout is reset', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
expect(gridview.hasMaximizedView()).toBeFalsy();
gridview.maximizeView(view2);
expect(gridview.hasMaximizedView()).toBeTruthy();
gridview.deserialize(
{
height: 1000,
width: 1000,
root: {
type: 'leaf',
data: [],
},
orientation: Orientation.HORIZONTAL,
},
{
fromJSON: (data) => {
return new MockGridview('');
},
}
);
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('visibility check', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
const view4 = new MockGridview('4');
const view5 = new MockGridview('5');
const view6 = new MockGridview('6');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
/**
* _____________________________________________
* | | |
* | | 2 |
* | | |
* | 1 |_______________________|
* | | | 4 |
* | | 3 |_____________|
* | | | 5 | 6 |
* |_____________________|_________|______|______|
*/
function assertVisibility(visibility: boolean[]) {
expect(gridview.isViewVisible(getGridLocation(view1.element))).toBe(
visibility[0]
);
expect(gridview.isViewVisible(getGridLocation(view2.element))).toBe(
visibility[1]
);
expect(gridview.isViewVisible(getGridLocation(view3.element))).toBe(
visibility[2]
);
expect(gridview.isViewVisible(getGridLocation(view4.element))).toBe(
visibility[3]
);
expect(gridview.isViewVisible(getGridLocation(view5.element))).toBe(
visibility[4]
);
expect(gridview.isViewVisible(getGridLocation(view6.element))).toBe(
visibility[5]
);
}
// hide each view one by one
assertVisibility([true, true, true, true, true, true]);
gridview.setViewVisible(getGridLocation(view5.element), false);
assertVisibility([true, true, true, true, false, true]);
gridview.setViewVisible(getGridLocation(view4.element), false);
assertVisibility([true, true, true, false, false, true]);
gridview.setViewVisible(getGridLocation(view1.element), false);
assertVisibility([false, true, true, false, false, true]);
gridview.setViewVisible(getGridLocation(view2.element), false);
assertVisibility([false, false, true, false, false, true]);
gridview.setViewVisible(getGridLocation(view3.element), false);
assertVisibility([false, false, false, false, false, true]);
gridview.setViewVisible(getGridLocation(view6.element), false);
assertVisibility([false, false, false, false, false, false]);
// un-hide each view one by one
gridview.setViewVisible(getGridLocation(view1.element), true);
assertVisibility([true, false, false, false, false, false]);
gridview.setViewVisible(getGridLocation(view5.element), true);
assertVisibility([true, false, false, false, true, false]);
gridview.setViewVisible(getGridLocation(view6.element), true);
assertVisibility([true, false, false, false, true, true]);
gridview.setViewVisible(getGridLocation(view2.element), true);
assertVisibility([true, true, false, false, true, true]);
gridview.setViewVisible(getGridLocation(view3.element), true);
assertVisibility([true, true, true, false, true, true]);
gridview.setViewVisible(getGridLocation(view4.element), true);
assertVisibility([true, true, true, true, true, true]);
});
});

View File

@ -32,12 +32,40 @@ describe('gridview', () => {
container = document.createElement('div');
});
test('added views are visible by default', () => {
const gridview = new GridviewComponent({
parentElement: container,
test('update className', () => {
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
className: 'test-a test-b',
});
expect(gridview.element.className).toBe('test-a test-b');
gridview.updateOptions({ className: 'test-b test-c' });
expect(gridview.element.className).toBe('test-b test-c');
});
test('added views are visible by default', () => {
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -53,11 +81,17 @@ describe('gridview', () => {
});
test('remove panel', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -84,11 +118,17 @@ describe('gridview', () => {
});
test('active panel', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -145,13 +185,21 @@ describe('gridview', () => {
});
test('deserialize and serialize a layout', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
expect(container.querySelectorAll('.dv-grid-view').length).toBe(1);
gridview.layout(800, 400);
gridview.fromJSON({
grid: {
@ -196,6 +244,9 @@ describe('gridview', () => {
},
activePanel: 'panel_1',
});
expect(container.querySelectorAll('.dv-grid-view').length).toBe(1);
gridview.layout(800, 400, true);
const panel1 = gridview.getPanel('panel_1')!;
@ -268,16 +319,22 @@ describe('gridview', () => {
],
},
},
activePanel: 'panel_1',
activePanel: 'panel_2',
});
});
test('toJSON shouldnt fire any layout events', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(1000, 1000);
@ -310,11 +367,17 @@ describe('gridview', () => {
});
test('gridview events', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -434,11 +497,17 @@ describe('gridview', () => {
test('dispose of gridviewComponent', () => {
expect(container.childNodes.length).toBe(0);
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -460,15 +529,21 @@ describe('gridview', () => {
gridview.dispose();
expect(container.childNodes.length).toBe(0);
expect(container.children.length).toBe(0);
});
test('#1/VERTICAL', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -523,11 +598,17 @@ describe('gridview', () => {
});
test('#2/HORIZONTAL', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -582,11 +663,17 @@ describe('gridview', () => {
});
test('#3/HORIZONTAL', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -659,11 +746,17 @@ describe('gridview', () => {
});
test('#4/HORIZONTAL', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -754,11 +847,17 @@ describe('gridview', () => {
});
test('#5/VERTICAL', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -849,11 +948,17 @@ describe('gridview', () => {
});
test('#5/VERTICAL/proportional/false', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -944,11 +1049,17 @@ describe('gridview', () => {
});
test('#6/VERTICAL', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -1069,11 +1180,17 @@ describe('gridview', () => {
});
test('#7/VERTICAL layout first', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -1194,11 +1311,17 @@ describe('gridview', () => {
});
test('#8/VERTICAL layout after', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -1321,11 +1444,17 @@ describe('gridview', () => {
});
test('#9/HORIZONTAL', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -1446,11 +1575,17 @@ describe('gridview', () => {
});
test('#9/HORIZONTAL/proportional/false', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.HORIZONTAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(800, 400);
@ -1571,11 +1706,17 @@ describe('gridview', () => {
});
test('#10/HORIZONTAL scale x:1.5 y:2', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.fromJSON({
@ -1699,11 +1840,17 @@ describe('gridview', () => {
});
test('panel is disposed of when component is disposed', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(1000, 1000);
@ -1730,11 +1877,17 @@ describe('gridview', () => {
});
test('panel is disposed of when removed', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(1000, 1000);
@ -1760,11 +1913,17 @@ describe('gridview', () => {
});
test('panel is disposed of when fromJSON is called', () => {
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: false,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(1000, 1000);
@ -1799,11 +1958,17 @@ describe('gridview', () => {
test('fromJSON events should still fire', () => {
jest.useFakeTimers();
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
let addGroup: GridviewPanel[] = [];
@ -1922,11 +2087,17 @@ describe('gridview', () => {
test('that fromJSON layouts are resized to the current dimensions', async () => {
const container = document.createElement('div');
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(1600, 800);
@ -2049,11 +2220,17 @@ describe('gridview', () => {
test('that a deep HORIZONTAL layout with fromJSON dimensions identical to the current dimensions loads', async () => {
const container = document.createElement('div');
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(6000, 5000);
@ -2325,11 +2502,17 @@ describe('gridview', () => {
test('that a deep VERTICAL layout with fromJSON dimensions identical to the current dimensions loads', async () => {
const container = document.createElement('div');
const gridview = new GridviewComponent({
parentElement: container,
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.VERTICAL,
components: { default: TestGridview },
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
gridview.layout(5000, 6000);
@ -2597,4 +2780,160 @@ describe('gridview', () => {
activePanel: 'panel_1',
});
});
test('that loading a corrupt layout throws an error and leaves a clean gridview behind', () => {
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error(`unsupported panel '${options.name}'`);
}
},
});
let el = gridview.element.querySelector('.dv-view-container');
expect(el).toBeTruthy();
expect(el!.childNodes.length).toBe(0);
expect(() => {
gridview.fromJSON({
grid: {
height: 400,
width: 800,
orientation: Orientation.HORIZONTAL,
root: {
type: 'branch',
size: 400,
data: [
{
type: 'leaf',
size: 200,
data: {
id: 'panel_1',
component: 'default',
snap: false,
},
},
{
type: 'branch',
size: 400,
data: [
{
type: 'leaf',
size: 250,
data: {
id: 'panel_1',
component: 'default',
snap: false,
},
},
{
type: 'leaf',
size: 150,
data: {
id: 'panel_1',
component: 'somethingBad',
snap: false,
},
},
],
},
{
type: 'leaf',
size: 200,
data: {
id: 'panel_1',
component: 'default',
snap: false,
},
},
],
},
},
activePanel: 'panel_1',
});
}).toThrow("unsupported panel 'somethingBad'");
expect(gridview.groups.length).toBe(0);
el = gridview.element.querySelector('.dv-view-container');
expect(el).toBeTruthy();
expect(el!.childNodes.length).toBe(0);
});
test('that disableAutoResizing is false by default', () => {
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
expect(gridview.disableResizing).toBeFalsy();
});
test('that disableAutoResizing can be enabled', () => {
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
expect(gridview.disableResizing).toBeTruthy();
});
test('that setVisible toggles visiblity', () => {
const gridview = new GridviewComponent(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestGridview(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
gridview.layout(1000, 1000);
const panel1 = gridview.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = gridview.addPanel({
id: 'panel2',
component: 'default',
});
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(false);
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(true);
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
});
});

View File

@ -8,6 +8,7 @@ describe('gridviewPanel', () => {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
} as any;
});

View File

@ -1,4 +1,8 @@
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import {
CompositeDisposable,
Disposable,
MutableDisposable,
} from '../lifecycle';
describe('lifecycle', () => {
test('mutable disposable', () => {
@ -64,4 +68,16 @@ describe('lifecycle', () => {
expect(cut.checkIsDisposed()).toBeTruthy();
});
test('Disposable.from(...)', () => {
const func = jest.fn();
const disposable = Disposable.from(func);
expect(func).not.toHaveBeenCalled();
disposable.dispose();
expect(func).toHaveBeenCalledTimes(1);
});
});

View File

@ -8,10 +8,8 @@ describe('math', () => {
expect(clamp(55, 40, 50)).toBe(50);
});
it('should throw an error if min > max', () => {
expect(() => clamp(55, 50, 40)).toThrow(
'50 > 40 is an invalid condition'
);
it('if min > max return min', () => {
expect(clamp(55, 50, 40)).toBe(50);
});
});

View File

@ -0,0 +1,418 @@
import { Overlay } from '../../overlay/overlay';
import { mockGetBoundingClientRect } from '../__test_utils__/utils';
describe('overlay', () => {
test('toJSON, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
left: 10,
top: 20,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
width: 40,
height: 50,
});
cut.dispose();
});
test('toJSON, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
right: 10,
bottom: 20,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
bottom: -20,
right: 0,
width: 40,
height: 50,
});
cut.dispose();
});
test('that out-of-bounds dimensions are fixed, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
left: -1000,
top: -1000,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
width: 40,
height: 50,
});
cut.dispose();
});
test('that out-of-bounds dimensions are fixed, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
bottom: -1000,
right: -1000,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
bottom: -20,
right: 0,
width: 40,
height: 50,
});
cut.dispose();
});
test('setBounds, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 1000,
width: 1000,
left: 0,
top: 0,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
const element: HTMLElement = container.querySelector(
'.dv-resize-container'
)!;
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return mockGetBoundingClientRect({
left: 300,
top: 400,
width: 200,
height: 100,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 0,
top: 0,
width: 1000,
height: 1000,
});
}
);
cut.setBounds({ height: 100, width: 200, left: 300, top: 400 });
expect(element.style.height).toBe('100px');
expect(element.style.width).toBe('200px');
expect(element.style.left).toBe('300px');
expect(element.style.top).toBe('400px');
cut.dispose();
});
test('setBounds, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 1000,
width: 1000,
right: 0,
bottom: 0,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
const element: HTMLElement = container.querySelector(
'.dv-resize-container'
)!;
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return mockGetBoundingClientRect({
left: 500,
top: 500,
width: 200,
height: 100,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 0,
top: 0,
width: 1000,
height: 1000,
});
}
);
cut.setBounds({ height: 100, width: 200, right: 300, bottom: 400 });
expect(element.style.height).toBe('100px');
expect(element.style.width).toBe('200px');
expect(element.style.right).toBe('300px');
expect(element.style.bottom).toBe('400px');
cut.dispose();
});
test('that the resize handles are added', () => {
const container = document.createElement('div');
const content = document.createElement('div');
const cut = new Overlay({
height: 500,
width: 500,
left: 100,
top: 200,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
expect(container.querySelector('.dv-resize-handle-top')).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottom')
).toBeTruthy();
expect(container.querySelector('.dv-resize-handle-left')).toBeTruthy();
expect(container.querySelector('.dv-resize-handle-right')).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-topleft')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-topright')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottomleft')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottomright')
).toBeTruthy();
cut.dispose();
});
test('aria-level attributes and corresponding z-index', () => {
const container = document.createElement('div');
const content = document.createElement('div');
const createOverlay = () =>
new Overlay({
height: 500,
width: 500,
left: 100,
top: 200,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
const overlay1 = createOverlay();
const zIndexValue = (delta: number) =>
`calc(var(--dv-overlay-z-index, 999) + ${delta})`;
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
const overlay2 = createOverlay();
const overlay3 = createOverlay();
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
expect(overlay2.element.getAttribute('aria-level')).toBe('1');
expect(overlay3.element.getAttribute('aria-level')).toBe('2');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
expect(overlay2.element.style.zIndex).toBe(zIndexValue(2));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(4));
overlay2.bringToFront();
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
expect(overlay2.element.getAttribute('aria-level')).toBe('2');
expect(overlay3.element.getAttribute('aria-level')).toBe('1');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
expect(overlay2.element.style.zIndex).toBe(zIndexValue(4));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(2));
overlay1.bringToFront();
expect(overlay1.element.getAttribute('aria-level')).toBe('2');
expect(overlay2.element.getAttribute('aria-level')).toBe('1');
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(4));
expect(overlay2.element.style.zIndex).toBe(zIndexValue(2));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
overlay2.dispose();
expect(overlay1.element.getAttribute('aria-level')).toBe('1');
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(2));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
overlay1.dispose();
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
});
});

View File

@ -0,0 +1,265 @@
import { Droptarget } from '../../dnd/droptarget';
import { IDockviewPanel } from '../../dockview/dockviewPanel';
import { Emitter } from '../../events';
import {
IRenderable,
OverlayRenderContainer,
} from '../../overlay/overlayRenderContainer';
import { fromPartial } from '@total-typescript/shoehorn';
import { Writable, exhaustMicrotaskQueue } from '../__test_utils__/utils';
import { DockviewComponent } from '../../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
describe('overlayRenderContainer', () => {
let referenceContainer: IRenderable;
let parentContainer: HTMLElement;
beforeEach(() => {
parentContainer = document.createElement('div');
referenceContainer = {
element: document.createElement('div'),
dropTarget: fromPartial<Droptarget>({}),
};
});
test('that attach(...) and detach(...) mutate the DOM as expected', () => {
const cut = new OverlayRenderContainer(
parentContainer,
fromPartial<DockviewComponent>({})
);
const panelContentEl = document.createElement('div');
const onDidVisibilityChange = new Emitter<any>();
const onDidDimensionsChange = new Emitter<any>();
const onDidLocationChange = new Emitter<any>();
const panel = fromPartial<IDockviewPanel>({
api: {
id: 'test_panel_id',
onDidVisibilityChange: onDidVisibilityChange.event,
onDidDimensionsChange: onDidDimensionsChange.event,
onDidLocationChange: onDidLocationChange.event,
isVisible: true,
location: { type: 'grid' },
},
view: {
content: {
element: panelContentEl,
},
},
group: {
api: {
location: { type: 'grid' },
},
},
});
cut.attach({ panel, referenceContainer });
expect(panelContentEl.parentElement?.parentElement).toBe(
parentContainer
);
cut.detatch(panel);
expect(panelContentEl.parentElement?.parentElement).toBeUndefined();
});
test('add a view that is not currently in the DOM', async () => {
const cut = new OverlayRenderContainer(
parentContainer,
fromPartial<DockviewComponent>({})
);
const panelContentEl = document.createElement('div');
const onDidVisibilityChange = new Emitter<any>();
const onDidDimensionsChange = new Emitter<any>();
const onDidLocationChange = new Emitter<any>();
const panel = fromPartial<IDockviewPanel>({
api: {
id: 'test_panel_id',
onDidVisibilityChange: onDidVisibilityChange.event,
onDidDimensionsChange: onDidDimensionsChange.event,
onDidLocationChange: onDidLocationChange.event,
isVisible: true,
location: { type: 'grid' },
},
view: {
content: {
element: panelContentEl,
},
},
group: {
api: {
location: { type: 'grid' },
},
},
});
(parentContainer as jest.Mocked<HTMLDivElement>).getBoundingClientRect =
jest
.fn<DOMRect, []>()
.mockReturnValueOnce(
fromPartial<DOMRect>({
left: 100,
top: 200,
width: 1000,
height: 500,
})
)
.mockReturnValueOnce(
fromPartial<DOMRect>({
left: 101,
top: 201,
width: 1000,
height: 500,
})
)
.mockReturnValueOnce(
fromPartial<DOMRect>({
left: 100,
top: 200,
width: 1000,
height: 500,
})
);
(
referenceContainer.element as jest.Mocked<HTMLDivElement>
).getBoundingClientRect = jest
.fn<DOMRect, []>()
.mockReturnValueOnce(
fromPartial<DOMRect>({
left: 150,
top: 300,
width: 100,
height: 200,
})
)
.mockReturnValueOnce(
fromPartial<DOMRect>({
left: 150,
top: 300,
width: 101,
height: 201,
})
)
.mockReturnValueOnce(
fromPartial<DOMRect>({
left: 150,
top: 300,
width: 100,
height: 200,
})
);
const container = cut.attach({ panel, referenceContainer });
await exhaustMicrotaskQueue();
expect(panelContentEl.parentElement).toBe(container);
expect(container.parentElement).toBe(parentContainer);
expect(container.style.display).toBe('');
expect(container.style.left).toBe('50px');
expect(container.style.top).toBe('100px');
expect(container.style.width).toBe('100px');
expect(container.style.height).toBe('200px');
expect(
referenceContainer.element.getBoundingClientRect
).toHaveBeenCalledTimes(1);
onDidDimensionsChange.fire({});
expect(container.style.display).toBe('');
expect(container.style.left).toBe('49px');
expect(container.style.top).toBe('99px');
expect(container.style.width).toBe('101px');
expect(container.style.height).toBe('201px');
expect(
referenceContainer.element.getBoundingClientRect
).toHaveBeenCalledTimes(2);
(panel as Writable<IDockviewPanel>).api.isVisible = false;
onDidVisibilityChange.fire({});
expect(container.style.display).toBe('none');
expect(
referenceContainer.element.getBoundingClientRect
).toHaveBeenCalledTimes(2);
(panel as Writable<IDockviewPanel>).api.isVisible = true;
onDidVisibilityChange.fire({});
expect(container.style.display).toBe('');
expect(container.style.left).toBe('50px');
expect(container.style.top).toBe('100px');
expect(container.style.width).toBe('100px');
expect(container.style.height).toBe('200px');
expect(
referenceContainer.element.getBoundingClientRect
).toHaveBeenCalledTimes(3);
});
test('related z-index from `aria-level` set on floating panels', async () => {
const group = fromPartial<DockviewGroupPanel>({});
const element = document.createElement('div');
element.setAttribute('aria-level', '2');
const spy = jest.spyOn(element, 'getAttribute');
const accessor = fromPartial<DockviewComponent>({
floatingGroups: [
{
group,
overlay: {
element,
},
},
],
});
const cut = new OverlayRenderContainer(parentContainer, accessor);
const panelContentEl = document.createElement('div');
const onDidVisibilityChange = new Emitter<any>();
const onDidDimensionsChange = new Emitter<any>();
const onDidLocationChange = new Emitter<any>();
const panel = fromPartial<IDockviewPanel>({
api: {
id: 'test_panel_id',
onDidVisibilityChange: onDidVisibilityChange.event,
onDidDimensionsChange: onDidDimensionsChange.event,
onDidLocationChange: onDidLocationChange.event,
isVisible: true,
group,
location: { type: 'floating' },
},
view: {
content: {
element: panelContentEl,
},
},
group: {
api: {
location: { type: 'floating' },
},
},
});
cut.attach({ panel, referenceContainer });
await exhaustMicrotaskQueue();
expect(spy).toHaveBeenCalledWith('aria-level');
expect(panelContentEl.parentElement!.style.zIndex).toBe(
'calc(var(--dv-overlay-z-index, 999) + 5)'
);
});
});

View File

@ -1,102 +0,0 @@
import { createComponent } from '../../panel/componentFactory';
describe('componentFactory', () => {
describe('createComponent', () => {
test('valid component and framework component', () => {
const mock = jest.fn();
const mock2 = jest.fn();
expect(() =>
createComponent(
'id-1',
'component-1',
{ 'component-1': mock },
{ 'component-1': mock2 }
)
).toThrow(
"Cannot create 'id-1'. component 'component-1' registered as both a component and frameworkComponent"
);
});
test('valid framework component but no factory', () => {
const mock = jest.fn();
expect(() =>
createComponent(
'id-1',
'component-1',
{},
{ 'component-1': mock }
)
).toThrow(
"Cannot create 'id-1' for framework component 'component-1'. you must register a frameworkPanelWrapper to use framework components"
);
});
test('valid framework component', () => {
const component = jest.fn();
const createComponentFn = jest
.fn()
.mockImplementation(() => component);
const frameworkComponent = jest.fn();
expect(
createComponent(
'id-1',
'component-1',
{},
{ 'component-1': frameworkComponent },
{
createComponent: createComponentFn,
}
)
).toBe(component);
expect(createComponentFn).toHaveBeenCalledWith(
'id-1',
'component-1',
frameworkComponent
);
});
test('no valid component with fallback', () => {
const mock = jest.fn();
expect(
createComponent(
'id-1',
'component-1',
{},
{},
{
createComponent: () => null,
},
() => mock
)
).toBe(mock);
});
test('no valid component', () => {
expect(() =>
createComponent('id-1', 'component-1', {}, {})
).toThrow(
"Cannot create 'id-1', no component 'component-1' provided"
);
});
test('valid component', () => {
const component = jest.fn();
const componentResult = createComponent(
'id-1',
'component-1',
{ 'component-1': component },
{}
);
expect(component).toHaveBeenCalled();
expect(componentResult instanceof component).toBeTruthy();
});
});
});

View File

@ -1,14 +1,10 @@
import { CompositeDisposable } from '../../lifecycle';
import { Paneview } from '../../paneview/paneview';
import {
IPaneBodyPart,
IPaneHeaderPart,
PaneviewPanel,
} from '../../paneview/paneviewPanel';
import { IPanePart, PaneviewPanel } from '../../paneview/paneviewPanel';
import { Orientation } from '../../splitview/splitview';
class TestPanel extends PaneviewPanel {
protected getBodyComponent(): IPaneBodyPart {
protected getBodyComponent(): IPanePart {
return {
element: document.createElement('div'),
update: () => {
@ -23,7 +19,7 @@ class TestPanel extends PaneviewPanel {
};
}
protected getHeaderComponent(): IPaneHeaderPart {
protected getHeaderComponent(): IPanePart {
return {
element: document.createElement('div'),
update: () => {
@ -60,22 +56,28 @@ describe('paneview', () => {
paneview.onDidRemoveView((view) => removed.push(view))
);
const view1 = new TestPanel(
'id',
'component',
'headerComponent',
Orientation.VERTICAL,
true,
true
);
const view2 = new TestPanel(
'id2',
'component',
'headerComponent',
Orientation.VERTICAL,
true,
true
);
const view1 = new TestPanel({
id: 'id',
component: 'component',
headerComponent: 'headerComponent',
orientation: Orientation.VERTICAL,
isExpanded: true,
isHeaderVisible: true,
headerSize: 22,
minimumBodySize: 0,
maximumBodySize: Number.MAX_SAFE_INTEGER,
});
const view2 = new TestPanel({
id: 'id2',
component: 'component',
headerComponent: 'headerComponent',
orientation: Orientation.VERTICAL,
isExpanded: true,
isHeaderVisible: true,
headerSize: 22,
minimumBodySize: 0,
maximumBodySize: Number.MAX_SAFE_INTEGER,
});
expect(added.length).toBe(0);
expect(removed.length).toBe(0);
@ -110,22 +112,28 @@ describe('paneview', () => {
orientation: Orientation.HORIZONTAL,
});
const view1 = new TestPanel(
'id',
'component',
'headerComponent',
Orientation.VERTICAL,
true,
true
);
const view2 = new TestPanel(
'id2',
'component',
'headerComponent',
Orientation.VERTICAL,
true,
true
);
const view1 = new TestPanel({
id: 'id',
component: 'component',
headerComponent: 'headerComponent',
orientation: Orientation.VERTICAL,
isExpanded: true,
isHeaderVisible: true,
headerSize: 22,
minimumBodySize: 0,
maximumBodySize: Number.MAX_SAFE_INTEGER,
});
const view2 = new TestPanel({
id: 'id2',
component: 'component',
headerComponent: 'headerComponent',
orientation: Orientation.VERTICAL,
isExpanded: true,
isHeaderVisible: true,
headerSize: 22,
minimumBodySize: 0,
maximumBodySize: Number.MAX_SAFE_INTEGER,
});
paneview.addPane(view1);
paneview.addPane(view2);

View File

@ -4,19 +4,28 @@ import { PanelUpdateEvent } from '../../panel/types';
import { PaneviewComponent } from '../../paneview/paneviewComponent';
import {
PaneviewPanel,
IPaneBodyPart,
IPaneHeaderPart,
IPanePart,
PanePanelComponentInitParameter,
} from '../../paneview/paneviewPanel';
import { Orientation } from '../../splitview/splitview';
class TestPanel extends PaneviewPanel {
constructor(id: string, component: string) {
super(id, component, 'header', Orientation.VERTICAL, false, true);
super({
id,
component,
headerComponent: 'header',
orientation: Orientation.VERTICAL,
isExpanded: false,
isHeaderVisible: true,
headerSize: 22,
minimumBodySize: 0,
maximumBodySize: Number.MAX_SAFE_INTEGER,
});
}
getHeaderComponent() {
return new (class Header implements IPaneHeaderPart {
return new (class Header implements IPanePart {
private _element: HTMLElement = document.createElement('div');
get element() {
@ -38,7 +47,7 @@ class TestPanel extends PaneviewPanel {
}
getBodyComponent() {
return new (class Header implements IPaneBodyPart {
return new (class Header implements IPanePart {
private _element: HTMLElement = document.createElement('div');
get element() {
@ -60,7 +69,7 @@ class TestPanel extends PaneviewPanel {
}
}
describe('componentPaneview', () => {
describe('paneviewComponent', () => {
let container: HTMLElement;
beforeEach(() => {
@ -68,26 +77,52 @@ describe('componentPaneview', () => {
container.className = 'container';
});
test('vertical panels', () => {
const disposables = new CompositeDisposable();
test('that the container is not removed when grid is disposed', () => {
const root = document.createElement('div');
const container = document.createElement('div');
root.appendChild(container);
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
paneview.layout(600, 400);
paneview.dispose();
expect(container.parentElement).toBe(root);
expect(container.children.length).toBe(0);
});
test('vertical panels', () => {
const disposables = new CompositeDisposable();
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
paneview.layout(300, 200);
paneview.addPanel({
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
});
paneview.addPanel({
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel2',
});
@ -108,6 +143,8 @@ describe('componentPaneview', () => {
})
);
paneview.layout(600, 400);
expect(panel1Dimensions).toEqual({ width: 600, height: 22 });
expect(panel2Dimensions).toEqual({ width: 600, height: 22 });
@ -142,13 +179,19 @@ describe('componentPaneview', () => {
});
test('serialization', () => {
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
expect(container.querySelectorAll('.dv-pane-container').length).toBe(1);
paneview.fromJSON({
size: 6,
views: [
@ -156,7 +199,7 @@ describe('componentPaneview', () => {
size: 1,
data: {
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
},
expanded: true,
@ -165,7 +208,7 @@ describe('componentPaneview', () => {
size: 2,
data: {
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
},
expanded: false,
@ -174,13 +217,15 @@ describe('componentPaneview', () => {
size: 3,
data: {
id: 'panel3',
component: 'testPanel',
component: 'default',
title: 'Panel 3',
},
},
],
});
expect(container.querySelectorAll('.dv-pane-container').length).toBe(1);
paneview.layout(400, 800);
const panel1 = paneview.getPanel('panel1');
@ -220,53 +265,57 @@ describe('componentPaneview', () => {
size: 756,
data: {
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
},
expanded: true,
minimumSize: 100,
headerSize: 22,
},
{
size: 22,
data: {
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
},
expanded: false,
minimumSize: 100,
headerSize: 22,
},
{
size: 22,
data: {
id: 'panel3',
component: 'testPanel',
component: 'default',
title: 'Panel 3',
},
expanded: false,
minimumSize: 100,
headerSize: 22,
},
],
});
});
test('toJSON shouldnt fire any layout events', () => {
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
paneview.layout(1000, 1000);
paneview.addPanel({
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
});
paneview.addPanel({
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
});
@ -280,41 +329,15 @@ describe('componentPaneview', () => {
disposable.dispose();
});
test('dispose of paneviewComponent', () => {
expect(container.childNodes.length).toBe(0);
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
},
});
paneview.layout(1000, 1000);
paneview.addPanel({
id: 'panel1',
component: 'testPanel',
title: 'Panel 1',
});
paneview.addPanel({
id: 'panel2',
component: 'testPanel',
title: 'Panel 2',
});
expect(container.childNodes.length).toBeGreaterThan(0);
paneview.dispose();
expect(container.childNodes.length).toBe(0);
});
test('panel is disposed of when component is disposed', () => {
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -322,12 +345,12 @@ describe('componentPaneview', () => {
paneview.addPanel({
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
});
paneview.addPanel({
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
});
@ -344,10 +367,14 @@ describe('componentPaneview', () => {
});
test('panel is disposed of when removed', () => {
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -355,12 +382,12 @@ describe('componentPaneview', () => {
paneview.addPanel({
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
});
paneview.addPanel({
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
});
@ -377,10 +404,14 @@ describe('componentPaneview', () => {
});
test('panel is disposed of when fromJSON is called', () => {
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -388,12 +419,12 @@ describe('componentPaneview', () => {
paneview.addPanel({
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
});
paneview.addPanel({
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
});
@ -410,10 +441,14 @@ describe('componentPaneview', () => {
});
test('that fromJSON layouts are resized to the current dimensions', async () => {
const paneview = new PaneviewComponent({
parentElement: container,
components: {
testPanel: TestPanel,
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -426,16 +461,17 @@ describe('componentPaneview', () => {
size: 1,
data: {
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
},
minimumSize: 100,
expanded: true,
},
{
size: 2,
data: {
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
},
expanded: true,
@ -444,7 +480,7 @@ describe('componentPaneview', () => {
size: 3,
data: {
id: 'panel3',
component: 'testPanel',
component: 'default',
title: 'Panel 3',
},
expanded: true,
@ -460,33 +496,124 @@ describe('componentPaneview', () => {
size: 122,
data: {
id: 'panel1',
component: 'testPanel',
component: 'default',
title: 'Panel 1',
},
expanded: true,
minimumSize: 100,
headerSize: 22,
},
{
size: 122,
size: 22,
data: {
id: 'panel2',
component: 'testPanel',
component: 'default',
title: 'Panel 2',
},
expanded: true,
minimumSize: 100,
headerSize: 22,
},
{
size: 356,
size: 456,
data: {
id: 'panel3',
component: 'testPanel',
component: 'default',
title: 'Panel 3',
},
expanded: true,
minimumSize: 100,
headerSize: 22,
},
],
});
});
test('that disableAutoResizing is false by default', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
expect(paneview.disableResizing).toBeFalsy();
});
test('that disableAutoResizing can be enabled', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
expect(paneview.disableResizing).toBeTruthy();
});
test('that setVisible toggles visiblity', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
paneview.layout(1000, 1000);
const panel1 = paneview.addPanel({
id: 'panel1',
component: 'default',
title: 'panel1',
});
const panel2 = paneview.addPanel({
id: 'panel2',
component: 'default',
title: 'panel2',
});
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(false);
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(true);
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
});
test('update className', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
className: 'test-a test-b',
});
expect(paneview.element.className).toBe('test-a test-b');
paneview.updateOptions({ className: 'test-b test-c' });
expect(paneview.element.className).toBe('test-b test-c');
});
});

View File

@ -96,7 +96,7 @@ describe('splitview', () => {
expect(splitview.orientation).toBe(Orientation.HORIZONTAL);
const viewQuery = container.querySelectorAll(
'.split-view-container horizontal'
'.dv-split-view-container dv-horizontal'
);
expect(viewQuery).toBeTruthy();
@ -111,7 +111,7 @@ describe('splitview', () => {
expect(splitview.orientation).toBe(Orientation.VERTICAL);
const viewQuery = container.querySelectorAll(
'.split-view-container vertical'
'.dv-split-view-container dv-vertical'
);
expect(viewQuery).toBeTruthy();
@ -128,48 +128,48 @@ describe('splitview', () => {
splitview.addView(new Testview(50, 50));
let viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view'
'.dv-split-view-container > .dv-view-container > .dv-view'
);
expect(viewQuery.length).toBe(3);
let sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash'
'.dv-split-view-container > .dv-sash-container > .dv-sash'
);
expect(sashQuery.length).toBe(2);
splitview.removeView(2);
viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view'
'.dv-split-view-container > .dv-view-container > .dv-view'
);
expect(viewQuery.length).toBe(2);
sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash'
'.dv-split-view-container > .dv-sash-container > .dv-sash'
);
expect(sashQuery.length).toBe(1);
splitview.removeView(0);
viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view'
'.dv-split-view-container > .dv-view-container > .dv-view'
);
expect(viewQuery.length).toBe(1);
sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash'
'.dv-split-view-container > .dv-sash-container > .dv-sash'
);
expect(sashQuery.length).toBe(0);
splitview.removeView(0);
viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view'
'.dv-split-view-container > .dv-view-container > .dv-view'
);
expect(viewQuery.length).toBe(0);
sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash'
'.dv-split-view-container > .dv-sash-container > .dv-sash'
);
expect(sashQuery.length).toBe(0);
@ -188,14 +188,14 @@ describe('splitview', () => {
splitview.addView(view2);
let viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view.visible'
'.dv-split-view-container > .dv-view-container > .dv-view.visible'
);
expect(viewQuery.length).toBe(2);
splitview.setViewVisible(1, false);
viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view.visible'
'.dv-split-view-container > .dv-view-container > .dv-view.visible'
);
expect(viewQuery.length).toBe(1);
@ -619,7 +619,7 @@ describe('splitview', () => {
);
const sashElement = container
.getElementsByClassName('sash')
.getElementsByClassName('dv-sash')
.item(0) as HTMLElement;
// validate the expected state before drag
@ -676,4 +676,226 @@ describe('splitview', () => {
expect(addEventListenerSpy).toBeCalledTimes(3);
expect(removeEventListenerSpy).toBeCalledTimes(3);
});
test('setViewVisible', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
splitview.setViewVisible(0, false);
expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]);
splitview.setViewVisible(0, true);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('setViewVisible with one view having high layout priority', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000, LayoutPriority.High);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
splitview.setViewVisible(0, false);
expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]);
splitview.setViewVisible(0, true);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('set view size', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
view1.fireChangeEvent({ size: 0 });
expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]);
view1.fireChangeEvent({ size: 300 });
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('set view size with one view having high layout priority', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000, LayoutPriority.High);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
view1.fireChangeEvent({ size: 0 });
expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]);
view1.fireChangeEvent({ size: 300 });
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('that margins are applied to view sizing', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
margin: 24,
});
splitview.layout(924, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000);
const view3 = new Testview(0, 1000);
const view4 = new Testview(0, 1000);
splitview.addView(view1);
expect([view1.size]).toEqual([924]);
splitview.addView(view2);
expect([view1.size, view2.size]).toEqual([450, 450]); // 450 + 24 + 450 = 924
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([292, 292, 292]); // 292 + 24 + 292 + 24 + 292 = 924
splitview.addView(view4);
expect([view1.size, view2.size, view3.size, view4.size]).toEqual([
213, 213, 213, 213,
]); // 213 + 24 + 213 + 24 + 213 + 24 + 213 = 924
let viewQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-view-container > .dv-view'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
height: e.style.height,
width: e.style.width,
}));
let sashQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-sash-container > .dv-sash'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
}));
// check HTMLElement positions since these are the ones that really matter
expect(viewQuery).toEqual([
{ left: '0px', top: '', width: '213px', height: '' },
// 213 + 24 = 237
{ left: '237px', top: '', width: '213px', height: '' },
// 237 + 213 + 24 = 474
{ left: '474px', top: '', width: '213px', height: '' },
// 474 + 213 + 24 = 474
{ left: '711px', top: '', width: '213px', height: '' },
// 711 + 213 = 924
]);
// 924 / 4 = 231 view size
// 231 - (24*3/4) = 213 margin adjusted view size
// 213 - 4/2 + 24/2 = 223
expect(sashQuery).toEqual([
// 213 - 4/2 + 24/2 = 223
{ left: '223px', top: '0px' },
// 213 + 24 + 213 = 450
// 450 - 4/2 + 24/2 = 460
{ left: '460px', top: '0px' },
// 213 + 24 + 213 + 24 + 213 = 687
// 687 - 4/2 + 24/2 = 697
{ left: '697px', top: '0px' },
]);
splitview.setViewVisible(0, false);
viewQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-view-container > .dv-view'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
height: e.style.height,
width: e.style.width,
}));
sashQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-sash-container > .dv-sash'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
}));
expect(viewQuery).toEqual([
{ left: '0px', top: '', width: '0px', height: '' },
{ left: '0px', top: '', width: '215px', height: '' },
{ left: '239px', top: '', width: '215px', height: '' },
{ left: '478px', top: '', width: '446px', height: '' },
]);
expect(sashQuery).toEqual([
{ left: '0px', top: '0px' },
{ left: '225px', top: '0px' },
{ left: '464px', top: '0px' },
]);
});
});

View File

@ -26,25 +26,52 @@ describe('componentSplitview', () => {
container.className = 'container';
});
test('that the container is not removed when grid is disposed', () => {
const root = document.createElement('div');
const container = document.createElement('div');
root.appendChild(container);
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.dispose();
expect(container.parentElement).toBe(root);
expect(container.children.length).toBe(0);
});
test('event leakage', () => {
Emitter.setLeakageMonitorEnabled(true);
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(600, 400);
const panel1 = splitview.addPanel({
id: 'panel1',
component: 'testPanel',
component: 'default',
});
const panel2 = splitview.addPanel({
id: 'panel2',
component: 'testPanel',
component: 'default',
});
splitview.movePanel(0, 1);
@ -66,18 +93,22 @@ describe('componentSplitview', () => {
});
test('remove panel', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(600, 400);
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
splitview.addPanel({ id: 'panel3', component: 'testPanel' });
splitview.addPanel({ id: 'panel1', component: 'default' });
splitview.addPanel({ id: 'panel2', component: 'default' });
splitview.addPanel({ id: 'panel3', component: 'default' });
const panel1 = splitview.getPanel('panel1')!;
const panel2 = splitview.getPanel('panel2')!;
@ -102,11 +133,15 @@ describe('componentSplitview', () => {
});
test('horizontal dimensions', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(600, 400);
@ -116,11 +151,15 @@ describe('componentSplitview', () => {
});
test('vertical dimensions', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(600, 400);
@ -130,18 +169,22 @@ describe('componentSplitview', () => {
});
test('api resize', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(400, 600);
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
splitview.addPanel({ id: 'panel3', component: 'testPanel' });
splitview.addPanel({ id: 'panel1', component: 'default' });
splitview.addPanel({ id: 'panel2', component: 'default' });
splitview.addPanel({ id: 'panel3', component: 'default' });
const panel1 = splitview.getPanel('panel1')!;
const panel2 = splitview.getPanel('panel2')!;
@ -183,16 +226,20 @@ describe('componentSplitview', () => {
});
test('api', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(600, 400);
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
splitview.addPanel({ id: 'panel1', component: 'default' });
const panel1 = splitview.getPanel('panel1');
@ -203,7 +250,7 @@ describe('componentSplitview', () => {
// expect(panel1?.api.isFocused).toBeFalsy();
expect(panel1!.api.isVisible).toBeTruthy();
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
splitview.addPanel({ id: 'panel2', component: 'default' });
const panel2 = splitview.getPanel('panel2');
@ -225,18 +272,22 @@ describe('componentSplitview', () => {
test('vertical panels', () => {
const disposables = new CompositeDisposable();
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(600, 400);
splitview.layout(300, 200);
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
splitview.addPanel({ id: 'panel1', component: 'default' });
splitview.addPanel({ id: 'panel2', component: 'default' });
const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
@ -255,6 +306,8 @@ describe('componentSplitview', () => {
})
);
splitview.layout(600, 400);
expect(panel1Dimensions).toEqual({ width: 600, height: 200 });
expect(panel2Dimensions).toEqual({ width: 600, height: 200 });
@ -275,18 +328,22 @@ describe('componentSplitview', () => {
test('horizontal panels', () => {
const disposables = new CompositeDisposable();
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(600, 400);
splitview.layout(300, 200);
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
splitview.addPanel({ id: 'panel1', component: 'default' });
splitview.addPanel({ id: 'panel2', component: 'default' });
const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
@ -305,6 +362,8 @@ describe('componentSplitview', () => {
})
);
splitview.layout(600, 400);
expect(panel1Dimensions).toEqual({ width: 300, height: 400 });
expect(panel2Dimensions).toEqual({ width: 300, height: 400 });
@ -323,51 +382,63 @@ describe('componentSplitview', () => {
});
test('serialization', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(400, 6);
expect(
container.querySelectorAll('.dv-split-view-container').length
).toBe(1);
splitview.fromJSON({
views: [
{
size: 1,
data: { id: 'panel1', component: 'testPanel' },
data: { id: 'panel1', component: 'default' },
snap: false,
},
{
size: 2,
data: { id: 'panel2', component: 'testPanel' },
data: { id: 'panel2', component: 'default' },
snap: true,
},
{ size: 3, data: { id: 'panel3', component: 'testPanel' } },
{ size: 3, data: { id: 'panel3', component: 'default' } },
],
size: 6,
orientation: Orientation.VERTICAL,
activeView: 'panel1',
});
expect(
container.querySelectorAll('.dv-split-view-container').length
).toBe(1);
expect(splitview.length).toBe(3);
expect(JSON.parse(JSON.stringify(splitview.toJSON()))).toEqual({
views: [
{
size: 1,
data: { id: 'panel1', component: 'testPanel' },
data: { id: 'panel1', component: 'default' },
snap: false,
},
{
size: 2,
data: { id: 'panel2', component: 'testPanel' },
data: { id: 'panel2', component: 'default' },
snap: true,
},
{
size: 3,
data: { id: 'panel3', component: 'testPanel' },
data: { id: 'panel3', component: 'default' },
snap: false,
},
],
@ -378,11 +449,15 @@ describe('componentSplitview', () => {
});
test('toJSON shouldnt fire any layout events', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -390,11 +465,11 @@ describe('componentSplitview', () => {
splitview.addPanel({
id: 'panel1',
component: 'testPanel',
component: 'default',
});
splitview.addPanel({
id: 'panel2',
component: 'testPanel',
component: 'default',
});
const disposable = splitview.onDidLayoutChange(() => {
@ -407,41 +482,16 @@ describe('componentSplitview', () => {
disposable.dispose();
});
test('dispose of splitviewComponent', () => {
expect(container.childNodes.length).toBe(0);
const splitview = new SplitviewComponent({
parentElement: container,
orientation: Orientation.HORIZONTAL,
components: {
testPanel: TestPanel,
},
});
splitview.layout(1000, 1000);
splitview.addPanel({
id: 'panel1',
component: 'testPanel',
});
splitview.addPanel({
id: 'panel2',
component: 'testPanel',
});
expect(container.childNodes.length).toBeGreaterThan(0);
splitview.dispose();
expect(container.childNodes.length).toBe(0);
});
test('panel is disposed of when component is disposed', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
components: {
default: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -469,11 +519,15 @@ describe('componentSplitview', () => {
});
test('panel is disposed of when removed', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
components: {
default: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -501,11 +555,15 @@ describe('componentSplitview', () => {
});
test('panel is disposed of when fromJSON is called', () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
components: {
default: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
@ -537,11 +595,15 @@ describe('componentSplitview', () => {
});
test('that fromJSON layouts are resized to the current dimensions', async () => {
const splitview = new SplitviewComponent({
parentElement: container,
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(400, 600);
@ -550,15 +612,15 @@ describe('componentSplitview', () => {
views: [
{
size: 1,
data: { id: 'panel1', component: 'testPanel' },
data: { id: 'panel1', component: 'default' },
snap: false,
},
{
size: 2,
data: { id: 'panel2', component: 'testPanel' },
data: { id: 'panel2', component: 'default' },
snap: true,
},
{ size: 3, data: { id: 'panel3', component: 'testPanel' } },
{ size: 3, data: { id: 'panel3', component: 'default' } },
],
size: 6,
orientation: Orientation.VERTICAL,
@ -569,17 +631,17 @@ describe('componentSplitview', () => {
views: [
{
size: 100,
data: { id: 'panel1', component: 'testPanel' },
data: { id: 'panel1', component: 'default' },
snap: false,
},
{
size: 200,
data: { id: 'panel2', component: 'testPanel' },
data: { id: 'panel2', component: 'default' },
snap: true,
},
{
size: 300,
data: { id: 'panel3', component: 'testPanel' },
data: { id: 'panel3', component: 'default' },
snap: false,
},
],
@ -588,4 +650,94 @@ describe('componentSplitview', () => {
activeView: 'panel1',
});
});
test('that disableAutoResizing is false by default', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
expect(splitview.disableResizing).toBeFalsy();
});
test('that disableAutoResizing can be enabled', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
expect(splitview.disableResizing).toBeTruthy();
});
test('that setVisible toggles visiblity', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(1000, 1000);
const panel1 = splitview.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = splitview.addPanel({
id: 'panel2',
component: 'default',
});
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(false);
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(true);
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
});
test('update className', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
className: 'test-a test-b',
});
expect(splitview.element.className).toBe('test-a test-b');
splitview.updateOptions({ className: 'test-b test-c' });
expect(splitview.element.className).toBe('test-b test-c');
});
});

View File

@ -1,13 +1,20 @@
import {
DockviewDropEvent,
DockviewMaximizedGroupChanged,
FloatingGroupOptions,
IDockviewComponent,
MovePanelEvent,
PopoutGroupChangePositionEvent,
PopoutGroupChangeSizeEvent,
SerializedDockview,
} from '../dockview/dockviewComponent';
import {
AddGroupOptions,
AddPanelOptions,
DockviewComponentOptions,
DockviewDndOverlayEvent,
MovementOptions,
} from '../dockview/options';
import { Parameters } from '../panel/types';
import { Direction } from '../gridview/baseComponentGridview';
import {
AddComponentOptions,
@ -26,7 +33,6 @@ import {
AddSplitviewComponentOptions,
ISplitviewComponent,
SerializedSplitview,
SplitviewComponentUpdateOptions,
} from '../splitview/splitviewComponent';
import { IView, Orientation, Sizing } from '../splitview/splitview';
import { ISplitviewPanel } from '../splitview/splitviewPanel';
@ -34,9 +40,25 @@ import {
DockviewGroupPanel,
IDockviewGroupPanel,
} from '../dockview/dockviewGroupPanel';
import { Emitter, Event } from '../events';
import { Event } from '../events';
import { IDockviewPanel } from '../dockview/dockviewPanel';
import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel';
import { PaneviewDidDropEvent } from '../paneview/draggablePaneviewPanel';
import {
GroupDragEvent,
TabDragEvent,
} from '../dockview/components/titlebar/tabsContainer';
import { Box } from '../types';
import {
DockviewDidDropEvent,
DockviewWillDropEvent,
WillShowOverlayLocationEvent,
} from '../dockview/dockviewGroupPanelModel';
import {
PaneviewComponentOptions,
PaneviewDndOverlayEvent,
} from '../paneview/options';
import { SplitviewComponentOptions } from '../splitview/options';
import { GridviewComponentOptions } from '../gridview/options';
export interface CommonApi<T = any> {
readonly height: number;
@ -48,236 +70,413 @@ export interface CommonApi<T = any> {
fromJSON(data: T): void;
toJSON(): T;
clear(): void;
dispose(): void;
}
export class SplitviewApi implements CommonApi<SerializedSplitview> {
/**
* The minimum size the component can reach where size is measured in the direction of orientation provided.
*/
get minimumSize(): number {
return this.component.minimumSize;
}
/**
* The maximum size the component can reach where size is measured in the direction of orientation provided.
*/
get maximumSize(): number {
return this.component.maximumSize;
}
get height(): number {
return this.component.height;
}
/**
* Width of the component.
*/
get width(): number {
return this.component.width;
}
/**
* Height of the component.
*/
get height(): number {
return this.component.height;
}
/**
* The current number of panels.
*/
get length(): number {
return this.component.length;
}
/**
* The current orientation of the component.
*/
get orientation(): Orientation {
return this.component.orientation;
}
/**
* The list of current panels.
*/
get panels(): ISplitviewPanel[] {
return this.component.panels;
}
/**
* Invoked after a layout is loaded through the `fromJSON` method.
*/
get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON;
}
/**
* Invoked whenever any aspect of the layout changes.
* If listening to this event it may be worth debouncing ouputs.
*/
get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange;
}
/**
* Invoked when a view is added.
*/
get onDidAddView(): Event<IView> {
return this.component.onDidAddView;
}
/**
* Invoked when a view is removed.
*/
get onDidRemoveView(): Event<IView> {
return this.component.onDidRemoveView;
}
constructor(private readonly component: ISplitviewComponent) {}
updateOptions(options: SplitviewComponentUpdateOptions): void {
this.component.updateOptions(options);
}
/**
* Removes an existing panel and optionally provide a `Sizing` method
* for the subsequent resize.
*/
removePanel(panel: ISplitviewPanel, sizing?: Sizing): void {
this.component.removePanel(panel, sizing);
}
/**
* Focus the component.
*/
focus(): void {
this.component.focus();
}
/**
* Get the reference to a panel given it's `string` id.
*/
getPanel(id: string): ISplitviewPanel | undefined {
return this.component.getPanel(id);
}
/**
* Layout the panel with a width and height.
*/
layout(width: number, height: number): void {
return this.component.layout(width, height);
}
addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel {
/**
* Add a new panel and return the created instance.
*/
addPanel<T extends object = Parameters>(
options: AddSplitviewComponentOptions<T>
): ISplitviewPanel {
return this.component.addPanel(options);
}
/**
* Move a panel given it's current and desired index.
*/
movePanel(from: number, to: number): void {
this.component.movePanel(from, to);
}
/**
* Deserialize a layout to built a splitivew.
*/
fromJSON(data: SerializedSplitview): void {
this.component.fromJSON(data);
}
/** Serialize a layout */
toJSON(): SerializedSplitview {
return this.component.toJSON();
}
/**
* Remove all panels and clear the component.
*/
clear(): void {
this.component.clear();
}
/**
* Update configuratable options.
*/
updateOptions(options: Partial<SplitviewComponentOptions>): void {
this.component.updateOptions(options);
}
/**
* Release resources and teardown component. Do not call when using framework versions of dockview.
*/
dispose(): void {
this.component.dispose();
}
}
export class PaneviewApi implements CommonApi<SerializedPaneview> {
/**
* The minimum size the component can reach where size is measured in the direction of orientation provided.
*/
get minimumSize(): number {
return this.component.minimumSize;
}
/**
* The maximum size the component can reach where size is measured in the direction of orientation provided.
*/
get maximumSize(): number {
return this.component.maximumSize;
}
get height(): number {
return this.component.height;
}
/**
* Width of the component.
*/
get width(): number {
return this.component.width;
}
/**
* Height of the component.
*/
get height(): number {
return this.component.height;
}
/**
* All panel objects.
*/
get panels(): IPaneviewPanel[] {
return this.component.panels;
}
/**
* Invoked when any layout change occures, an aggregation of many events.
*/
get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange;
}
/**
* Invoked after a layout is deserialzied using the `fromJSON` method.
*/
get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON;
}
/**
* Invoked when a panel is added. May be called multiple times when moving panels.
*/
get onDidAddView(): Event<IPaneviewPanel> {
return this.component.onDidAddView;
}
/**
* Invoked when a panel is removed. May be called multiple times when moving panels.
*/
get onDidRemoveView(): Event<IPaneviewPanel> {
return this.component.onDidRemoveView;
}
get onDidDrop(): Event<PaneviewDropEvent> {
const emitter = new Emitter<PaneviewDropEvent>();
/**
* Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality.
*/
get onDidDrop(): Event<PaneviewDidDropEvent> {
return this.component.onDidDrop;
}
const disposable = this.component.onDidDrop((e) => {
emitter.fire({ ...e, api: this });
});
emitter.dispose = () => {
disposable.dispose();
emitter.dispose();
};
return emitter.event;
get onUnhandledDragOverEvent(): Event<PaneviewDndOverlayEvent> {
return this.component.onUnhandledDragOverEvent;
}
constructor(private readonly component: IPaneviewComponent) {}
/**
* Remove a panel given the panel object.
*/
removePanel(panel: IPaneviewPanel): void {
this.component.removePanel(panel);
}
/**
* Get a panel object given a `string` id. May return `undefined`.
*/
getPanel(id: string): IPaneviewPanel | undefined {
return this.component.getPanel(id);
}
/**
* Move a panel given it's current and desired index.
*/
movePanel(from: number, to: number): void {
this.component.movePanel(from, to);
}
/**
* Focus the component. Will try to focus an active panel if one exists.
*/
focus(): void {
this.component.focus();
}
/**
* Force resize the component to an exact width and height. Read about auto-resizing before using.
*/
layout(width: number, height: number): void {
this.component.layout(width, height);
}
addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel {
/**
* Add a panel and return the created object.
*/
addPanel<T extends object = Parameters>(
options: AddPaneviewComponentOptions<T>
): IPaneviewPanel {
return this.component.addPanel(options);
}
/**
* Create a component from a serialized object.
*/
fromJSON(data: SerializedPaneview): void {
this.component.fromJSON(data);
}
/**
* Create a serialized object of the current component.
*/
toJSON(): SerializedPaneview {
return this.component.toJSON();
}
/**
* Reset the component back to an empty and default state.
*/
clear(): void {
this.component.clear();
}
/**
* Update configuratable options.
*/
updateOptions(options: Partial<PaneviewComponentOptions>): void {
this.component.updateOptions(options);
}
/**
* Release resources and teardown component. Do not call when using framework versions of dockview.
*/
dispose(): void {
this.component.dispose();
}
}
export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
get minimumHeight(): number {
return this.component.minimumHeight;
}
get maximumHeight(): number {
return this.component.maximumHeight;
}
get minimumWidth(): number {
return this.component.minimumWidth;
}
get maximumWidth(): number {
return this.component.maximumWidth;
}
/**
* Width of the component.
*/
get width(): number {
return this.component.width;
}
/**
* Height of the component.
*/
get height(): number {
return this.component.height;
}
/**
* Minimum height of the component.
*/
get minimumHeight(): number {
return this.component.minimumHeight;
}
/**
* Maximum height of the component.
*/
get maximumHeight(): number {
return this.component.maximumHeight;
}
/**
* Minimum width of the component.
*/
get minimumWidth(): number {
return this.component.minimumWidth;
}
/**
* Maximum width of the component.
*/
get maximumWidth(): number {
return this.component.maximumWidth;
}
/**
* Invoked when any layout change occures, an aggregation of many events.
*/
get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange;
}
/**
* Invoked when a panel is added. May be called multiple times when moving panels.
*/
get onDidAddPanel(): Event<IGridviewPanel> {
return this.component.onDidAddGroup;
}
/**
* Invoked when a panel is removed. May be called multiple times when moving panels.
*/
get onDidRemovePanel(): Event<IGridviewPanel> {
return this.component.onDidRemoveGroup;
}
/**
* Invoked when the active panel changes. May be undefined if no panel is active.
*/
get onDidActivePanelChange(): Event<IGridviewPanel | undefined> {
return this.component.onDidActiveGroupChange;
}
/**
* Invoked after a layout is deserialzied using the `fromJSON` method.
*/
get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON;
}
/**
* All panel objects.
*/
get panels(): IGridviewPanel[] {
return this.component.groups;
}
/**
* Current orientation. Can be changed after initialization.
*/
get orientation(): Orientation {
return this.component.orientation;
}
@ -288,22 +487,39 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
constructor(private readonly component: IGridviewComponent) {}
/**
* Focus the component. Will try to focus an active panel if one exists.
*/
focus(): void {
this.component.focus();
}
/**
* Force resize the component to an exact width and height. Read about auto-resizing before using.
*/
layout(width: number, height: number, force = false): void {
this.component.layout(width, height, force);
}
addPanel(options: AddComponentOptions): IGridviewPanel {
/**
* Add a panel and return the created object.
*/
addPanel<T extends object = Parameters>(
options: AddComponentOptions<T>
): IGridviewPanel {
return this.component.addPanel(options);
}
/**
* Remove a panel given the panel object.
*/
removePanel(panel: IGridviewPanel, sizing?: Sizing): void {
this.component.removePanel(panel, sizing);
}
/**
* Move a panel in a particular direction relative to another panel.
*/
movePanel(
panel: IGridviewPanel,
options: { direction: Direction; reference: string; size?: number }
@ -311,174 +527,407 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
this.component.movePanel(panel, options);
}
/**
* Get a panel object given a `string` id. May return `undefined`.
*/
getPanel(id: string): IGridviewPanel | undefined {
return this.component.getPanel(id);
}
/**
* Create a component from a serialized object.
*/
fromJSON(data: SerializedGridviewComponent): void {
return this.component.fromJSON(data);
}
/**
* Create a serialized object of the current component.
*/
toJSON(): SerializedGridviewComponent {
return this.component.toJSON();
}
/**
* Reset the component back to an empty and default state.
*/
clear(): void {
this.component.clear();
}
updateOptions(options: Partial<GridviewComponentOptions>) {
this.component.updateOptions(options);
}
/**
* Release resources and teardown component. Do not call when using framework versions of dockview.
*/
dispose(): void {
this.component.dispose();
}
}
export class DockviewApi implements CommonApi<SerializedDockview> {
/**
* The unique identifier for this instance. Used to manage scope of Drag'n'Drop events.
*/
get id(): string {
return this.component.id;
}
/**
* Width of the component.
*/
get width(): number {
return this.component.width;
}
/**
* Height of the component.
*/
get height(): number {
return this.component.height;
}
/**
* Minimum height of the component.
*/
get minimumHeight(): number {
return this.component.minimumHeight;
}
/**
* Maximum height of the component.
*/
get maximumHeight(): number {
return this.component.maximumHeight;
}
/**
* Minimum width of the component.
*/
get minimumWidth(): number {
return this.component.minimumWidth;
}
/**
* Maximum width of the component.
*/
get maximumWidth(): number {
return this.component.maximumWidth;
}
/**
* Total number of groups.
*/
get size(): number {
return this.component.size;
}
/**
* Total number of panels.
*/
get totalPanels(): number {
return this.component.totalPanels;
}
/**
* Invoked when the active group changes. May be undefined if no group is active.
*/
get onDidActiveGroupChange(): Event<DockviewGroupPanel | undefined> {
return this.component.onDidActiveGroupChange;
}
/**
* Invoked when a group is added. May be called multiple times when moving groups.
*/
get onDidAddGroup(): Event<DockviewGroupPanel> {
return this.component.onDidAddGroup;
}
/**
* Invoked when a group is removed. May be called multiple times when moving groups.
*/
get onDidRemoveGroup(): Event<DockviewGroupPanel> {
return this.component.onDidRemoveGroup;
}
/**
* Invoked when the active panel changes. May be undefined if no panel is active.
*/
get onDidActivePanelChange(): Event<IDockviewPanel | undefined> {
return this.component.onDidActivePanelChange;
}
/**
* Invoked when a panel is added. May be called multiple times when moving panels.
*/
get onDidAddPanel(): Event<IDockviewPanel> {
return this.component.onDidAddPanel;
}
/**
* Invoked when a panel is removed. May be called multiple times when moving panels.
*/
get onDidRemovePanel(): Event<IDockviewPanel> {
return this.component.onDidRemovePanel;
}
get onDidMovePanel(): Event<MovePanelEvent> {
return this.component.onDidMovePanel;
}
/**
* Invoked after a layout is deserialzied using the `fromJSON` method.
*/
get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON;
}
/**
* Invoked when any layout change occures, an aggregation of many events.
*/
get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange;
}
get onDidDrop(): Event<DockviewDropEvent> {
/**
* Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality.
*/
get onDidDrop(): Event<DockviewDidDropEvent> {
return this.component.onDidDrop;
}
/**
* Invoked when a Drag'n'Drop event occurs but before dockview handles it giving the user an opportunity to intecept and
* prevent the event from occuring using the standard `preventDefault()` syntax.
*
* Preventing certain events may causes unexpected behaviours, use carefully.
*/
get onWillDrop(): Event<DockviewWillDropEvent> {
return this.component.onWillDrop;
}
/**
* Invoked before an overlay is shown indicating a drop target.
*
* Calling `event.preventDefault()` will prevent the overlay being shown and prevent
* the any subsequent drop event.
*/
get onWillShowOverlay(): Event<WillShowOverlayLocationEvent> {
return this.component.onWillShowOverlay;
}
/**
* Invoked before a group is dragged.
*
* Calling `event.nativeEvent.preventDefault()` will prevent the group drag starting.
*
*/
get onWillDragGroup(): Event<GroupDragEvent> {
return this.component.onWillDragGroup;
}
/**
* Invoked before a panel is dragged.
*
* Calling `event.nativeEvent.preventDefault()` will prevent the panel drag starting.
*/
get onWillDragPanel(): Event<TabDragEvent> {
return this.component.onWillDragPanel;
}
get onUnhandledDragOverEvent(): Event<DockviewDndOverlayEvent> {
return this.component.onUnhandledDragOverEvent;
}
get onDidPopoutGroupSizeChange(): Event<PopoutGroupChangeSizeEvent> {
return this.component.onDidPopoutGroupSizeChange;
}
get onDidPopoutGroupPositionChange(): Event<PopoutGroupChangePositionEvent> {
return this.component.onDidPopoutGroupPositionChange;
}
/**
* All panel objects.
*/
get panels(): IDockviewPanel[] {
return this.component.panels;
}
/**
* All group objects.
*/
get groups(): DockviewGroupPanel[] {
return this.component.groups;
}
/**
* Active panel object.
*/
get activePanel(): IDockviewPanel | undefined {
return this.component.activePanel;
}
/**
* Active group object.
*/
get activeGroup(): DockviewGroupPanel | undefined {
return this.component.activeGroup;
}
constructor(private readonly component: IDockviewComponent) {}
/**
* Focus the component. Will try to focus an active panel if one exists.
*/
focus(): void {
this.component.focus();
}
/**
* Get a panel object given a `string` id. May return `undefined`.
*/
getPanel(id: string): IDockviewPanel | undefined {
return this.component.getGroupPanel(id);
}
/**
* Force resize the component to an exact width and height. Read about auto-resizing before using.
*/
layout(width: number, height: number, force = false): void {
this.component.layout(width, height, force);
}
addPanel(options: AddPanelOptions): IDockviewPanel {
/**
* Add a panel and return the created object.
*/
addPanel<T extends object = Parameters>(
options: AddPanelOptions<T>
): IDockviewPanel {
return this.component.addPanel(options);
}
/**
* Remove a panel given the panel object.
*/
removePanel(panel: IDockviewPanel): void {
this.component.removePanel(panel);
}
/**
* Add a group and return the created object.
*/
addGroup(options?: AddGroupOptions): DockviewGroupPanel {
return this.component.addGroup(options);
}
moveToNext(options?: MovementOptions): void {
this.component.moveToNext(options);
}
moveToPrevious(options?: MovementOptions): void {
this.component.moveToPrevious(options);
}
/**
* Close all groups and panels.
*/
closeAllGroups(): void {
return this.component.closeAllGroups();
}
/**
* Remove a group and any panels within the group.
*/
removeGroup(group: IDockviewGroupPanel): void {
this.component.removeGroup(<DockviewGroupPanel>group);
}
/**
* Get a group object given a `string` id. May return undefined.
*/
getGroup(id: string): DockviewGroupPanel | undefined {
return this.component.getPanel(id);
}
/**
* Add a floating group
*/
addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number }
options?: FloatingGroupOptions
): void {
return this.component.addFloatingGroup(item, coord);
return this.component.addFloatingGroup(item, options);
}
/**
* Create a component from a serialized object.
*/
fromJSON(data: SerializedDockview): void {
this.component.fromJSON(data);
}
/**
* Create a serialized object of the current component.
*/
toJSON(): SerializedDockview {
return this.component.toJSON();
}
/**
* Reset the component back to an empty and default state.
*/
clear(): void {
this.component.clear();
}
/**
* Move the focus progmatically to the next panel or group.
*/
moveToNext(options?: MovementOptions): void {
this.component.moveToNext(options);
}
/**
* Move the focus progmatically to the previous panel or group.
*/
moveToPrevious(options?: MovementOptions): void {
this.component.moveToPrevious(options);
}
maximizeGroup(panel: IDockviewPanel): void {
this.component.maximizeGroup(panel.group);
}
hasMaximizedGroup(): boolean {
return this.component.hasMaximizedGroup();
}
exitMaximizedGroup(): void {
this.component.exitMaximizedGroup();
}
get onDidMaximizedGroupChange(): Event<DockviewMaximizedGroupChanged> {
return this.component.onDidMaximizedGroupChange;
}
/**
* Add a popout group in a new Window
*/
addPopoutGroup(
item: IDockviewPanel | DockviewGroupPanel,
options?: {
position?: Box;
popoutUrl?: string;
onDidOpen?: (event: { id: string; window: Window }) => void;
onWillClose?: (event: { id: string; window: Window }) => void;
}
): Promise<boolean> {
return this.component.addPopoutGroup(item, options);
}
updateOptions(options: Partial<DockviewComponentOptions>) {
this.component.updateOptions(options);
}
/**
* Release resources and teardown component. Do not call when using framework versions of dockview.
*/
dispose(): void {
this.component.dispose();
}
}

View File

@ -1,51 +1,137 @@
import { Position } from '../dnd/droptarget';
import { Position, positionToDirection } from '../dnd/droptarget';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import {
DockviewGroupChangeEvent,
DockviewGroupLocation,
} from '../dockview/dockviewGroupPanelModel';
import { Emitter, Event } from '../events';
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
export interface DockviewGroupMoveParams {
group?: DockviewGroupPanel;
position?: Position;
/**
* The index to place the panel within a group, only applicable if the placement is within an existing group
*/
index?: number;
}
export interface DockviewGroupPanelApi extends GridviewPanelApi {
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent>;
readonly isFloating: boolean;
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void;
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
readonly onDidActivePanelChange: Event<DockviewGroupChangeEvent>;
readonly location: DockviewGroupLocation;
/**
* If you require the Window object
*/
getWindow(): Window;
moveTo(options: DockviewGroupMoveParams): void;
maximize(): void;
isMaximized(): boolean;
exitMaximized(): void;
close(): void;
}
export interface DockviewGroupPanelFloatingChangeEvent {
readonly isFloating: boolean;
readonly location: DockviewGroupLocation;
}
const NOT_INITIALIZED_MESSAGE =
'dockview: DockviewGroupPanelApiImpl not initialized';
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
private _group: DockviewGroupPanel | undefined;
readonly _onDidFloatingStateChange =
readonly _onDidLocationChange =
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent> =
this._onDidFloatingStateChange.event;
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
this._onDidLocationChange.event;
get isFloating() {
readonly _onDidActivePanelChange = new Emitter<DockviewGroupChangeEvent>();
readonly onDidActivePanelChange = this._onDidActivePanelChange.event;
get location(): DockviewGroupLocation {
if (!this._group) {
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
throw new Error(NOT_INITIALIZED_MESSAGE);
}
return this._group.model.isFloating;
return this._group.model.location;
}
constructor(id: string, private readonly accessor: DockviewComponent) {
super(id);
super(id, '__dockviewgroup__');
this.addDisposables(this._onDidFloatingStateChange);
this.addDisposables(
this._onDidLocationChange,
this._onDidActivePanelChange
);
}
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void {
close(): void {
if (!this._group) {
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
return;
}
return this.accessor.removeGroup(this._group);
}
getWindow(): Window {
return this.location.type === 'popout'
? this.location.getWindow()
: window;
}
moveTo(options: DockviewGroupMoveParams): void {
if (!this._group) {
throw new Error(NOT_INITIALIZED_MESSAGE);
}
this.accessor.moveGroupOrPanel(
options.group,
this._group.id,
undefined,
options.position ?? 'center'
);
const group =
options.group ??
this.accessor.addGroup({
direction: positionToDirection(options.position ?? 'right'),
skipSetActive: true,
});
this.accessor.moveGroupOrPanel({
from: { groupId: this._group.id },
to: {
group,
position: options.group
? options.position ?? 'center'
: 'center',
index: options.index,
},
});
}
maximize(): void {
if (!this._group) {
throw new Error(NOT_INITIALIZED_MESSAGE);
}
if (this.location.type !== 'grid') {
// only grid groups can be maximized
return;
}
this.accessor.maximizeGroup(this._group);
}
isMaximized(): boolean {
if (!this._group) {
throw new Error(NOT_INITIALIZED_MESSAGE);
}
return this.accessor.isMaximizedGroup(this._group);
}
exitMaximized(): void {
if (!this._group) {
throw new Error(NOT_INITIALIZED_MESSAGE);
}
if (this.isMaximized()) {
this.accessor.exitMaximizedGroup();
}
}
initialize(group: DockviewGroupPanel): void {

View File

@ -1,36 +1,67 @@
import { Emitter, Event } from '../events';
import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { MutableDisposable } from '../lifecycle';
import { IDockviewPanel } from '../dockview/dockviewPanel';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { DockviewPanel } from '../dockview/dockviewPanel';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { Position } from '../dnd/droptarget';
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
import {
DockviewGroupMoveParams,
DockviewGroupPanelFloatingChangeEvent,
} from './dockviewGroupPanelApi';
import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel';
export interface TitleEvent {
readonly title: string;
}
/*
* omit visibility modifiers since the visibility of a single group doesn't make sense
* because it belongs to a groupview
*/
export interface RendererChangedEvent {
readonly renderer: DockviewPanelRenderer;
}
export interface ActiveGroupEvent {
readonly isActive: boolean;
}
export interface GroupChangedEvent {
// empty
}
export type DockviewPanelMoveParams = DockviewGroupMoveParams;
export interface DockviewPanelApi
extends Omit<
GridviewPanelApi,
// omit properties that do not make sense here
'setVisible' | 'onDidConstraintsChange' | 'setConstraints'
> {
/**
* The id of the tab component renderer
*
* Undefined if no custom tab renderer is provided
*/
readonly tabComponent: string | undefined;
readonly group: DockviewGroupPanel;
readonly isGroupActive: boolean;
readonly renderer: DockviewPanelRenderer;
readonly title: string | undefined;
readonly onDidActiveGroupChange: Event<void>;
readonly onDidGroupChange: Event<void>;
readonly onDidActiveGroupChange: Event<ActiveGroupEvent>;
readonly onDidGroupChange: Event<GroupChangedEvent>;
readonly onDidTitleChange: Event<TitleEvent>;
readonly onDidRendererChange: Event<RendererChangedEvent>;
readonly location: DockviewGroupLocation;
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
close(): void;
setTitle(title: string): void;
moveTo(options: {
group: DockviewGroupPanel;
position?: Position;
index?: number;
}): void;
setRenderer(renderer: DockviewPanelRenderer): void;
moveTo(options: DockviewPanelMoveParams): void;
maximize(): void;
isMaximized(): boolean;
exitMaximized(): void;
/**
* If you require the Window object
*/
getWindow(): Window;
}
export class DockviewPanelApiImpl
@ -38,41 +69,56 @@ export class DockviewPanelApiImpl
implements DockviewPanelApi
{
private _group: DockviewGroupPanel;
private readonly _tabComponent: string | undefined;
readonly _onDidTitleChange = new Emitter<TitleEvent>();
readonly onDidTitleChange = this._onDidTitleChange.event;
private readonly _onDidActiveGroupChange = new Emitter<void>();
private readonly _onDidActiveGroupChange = new Emitter<ActiveGroupEvent>();
readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event;
private readonly _onDidGroupChange = new Emitter<void>();
private readonly _onDidGroupChange = new Emitter<GroupChangedEvent>();
readonly onDidGroupChange = this._onDidGroupChange.event;
private readonly disposable = new MutableDisposable();
readonly _onDidRendererChange = new Emitter<RendererChangedEvent>();
readonly onDidRendererChange = this._onDidRendererChange.event;
private readonly _onDidLocationChange =
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
this._onDidLocationChange.event;
private readonly groupEventsDisposable = new MutableDisposable();
get location(): DockviewGroupLocation {
return this.group.api.location;
}
get title(): string | undefined {
return this.panel.title;
}
get isGroupActive(): boolean {
return !!this.group?.isActive;
return this.group.isActive;
}
get renderer(): DockviewPanelRenderer {
return this.panel.renderer;
}
set group(value: DockviewGroupPanel) {
const isOldGroupActive = this.isGroupActive;
const oldGroup = this._group;
this._group = value;
if (this._group !== value) {
this._group = value;
this._onDidGroupChange.fire();
this._onDidGroupChange.fire({});
if (this._group) {
this.disposable.value = this._group.api.onDidActiveChange(() => {
this._onDidActiveGroupChange.fire();
this.setupGroupEventListeners(oldGroup);
this._onDidLocationChange.fire({
location: this.group.api.location,
});
if (this.isGroupActive !== isOldGroupActive) {
this._onDidActiveGroupChange.fire();
}
}
}
@ -80,44 +126,111 @@ export class DockviewPanelApiImpl
return this._group;
}
get tabComponent(): string | undefined {
return this._tabComponent;
}
constructor(
private panel: IDockviewPanel,
private readonly panel: DockviewPanel,
group: DockviewGroupPanel,
private readonly accessor: DockviewComponent
private readonly accessor: DockviewComponent,
component: string,
tabComponent?: string
) {
super(panel.id);
super(panel.id, component);
this._tabComponent = tabComponent;
this.initialize(panel);
this._group = group;
this.setupGroupEventListeners();
this.addDisposables(
this.disposable,
this.groupEventsDisposable,
this._onDidRendererChange,
this._onDidTitleChange,
this._onDidGroupChange,
this._onDidActiveGroupChange
this._onDidActiveGroupChange,
this._onDidLocationChange
);
}
moveTo(options: {
group: DockviewGroupPanel;
position?: Position;
index?: number;
}): void {
this.accessor.moveGroupOrPanel(
options.group,
this._group.id,
this.panel.id,
options.position ?? 'center',
options.index
);
getWindow(): Window {
return this.group.api.getWindow();
}
moveTo(options: DockviewPanelMoveParams): void {
this.accessor.moveGroupOrPanel({
from: { groupId: this._group.id, panelId: this.panel.id },
to: {
group: options.group ?? this._group,
position: options.group
? options.position ?? 'center'
: 'center',
index: options.index,
},
});
}
setTitle(title: string): void {
this.panel.setTitle(title);
}
setRenderer(renderer: DockviewPanelRenderer): void {
this.panel.setRenderer(renderer);
}
close(): void {
this.group.model.closePanel(this.panel);
}
maximize(): void {
this.group.api.maximize();
}
isMaximized(): boolean {
return this.group.api.isMaximized();
}
exitMaximized(): void {
this.group.api.exitMaximized();
}
private setupGroupEventListeners(previousGroup?: DockviewGroupPanel) {
let _trackGroupActive = previousGroup?.isActive ?? false; // prevent duplicate events with same state
this.groupEventsDisposable.value = new CompositeDisposable(
this.group.api.onDidVisibilityChange((event) => {
const hasBecomeHidden = !event.isVisible && this.isVisible;
const hasBecomeVisible = event.isVisible && !this.isVisible;
const isActivePanel = this.group.model.isPanelActive(
this.panel
);
if (hasBecomeHidden || (hasBecomeVisible && isActivePanel)) {
this._onDidVisibilityChange.fire(event);
}
}),
this.group.api.onDidLocationChange((event) => {
if (this.group !== this.panel.group) {
return;
}
this._onDidLocationChange.fire(event);
}),
this.group.api.onDidActiveChange(() => {
if (this.group !== this.panel.group) {
return;
}
if (_trackGroupActive !== this.isGroupActive) {
_trackGroupActive = this.isGroupActive;
this._onDidActiveGroupChange.fire({
isActive: this.isGroupActive,
});
}
})
);
}
}

View File

@ -0,0 +1,46 @@
import {
DockviewApi,
GridviewApi,
PaneviewApi,
SplitviewApi,
} from '../api/component.api';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { DockviewComponentOptions } from '../dockview/options';
import { GridviewComponent } from '../gridview/gridviewComponent';
import { GridviewComponentOptions } from '../gridview/options';
import { PaneviewComponentOptions } from '../paneview/options';
import { PaneviewComponent } from '../paneview/paneviewComponent';
import { SplitviewComponentOptions } from '../splitview/options';
import { SplitviewComponent } from '../splitview/splitviewComponent';
export function createDockview(
element: HTMLElement,
options: DockviewComponentOptions
): DockviewApi {
const component = new DockviewComponent(element, options);
return component.api;
}
export function createSplitview(
element: HTMLElement,
options: SplitviewComponentOptions
): SplitviewApi {
const component = new SplitviewComponent(element, options);
return new SplitviewApi(component);
}
export function createGridview(
element: HTMLElement,
options: GridviewComponentOptions
): GridviewApi {
const component = new GridviewComponent(element, options);
return new GridviewApi(component);
}
export function createPaneview(
element: HTMLElement,
options: PaneviewComponentOptions
): PaneviewApi {
const component = new PaneviewComponent(element, options);
return new PaneviewApi(component);
}

View File

@ -37,17 +37,15 @@ export class GridviewPanelApiImpl
readonly onDidConstraintsChangeInternal: Event<GridConstraintChangeEvent2> =
this._onDidConstraintsChangeInternal.event;
readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>({
replay: true,
});
readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>();
readonly onDidConstraintsChange: Event<GridConstraintChangeEvent> =
this._onDidConstraintsChange.event;
private readonly _onDidSizeChange = new Emitter<SizeEvent>();
readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event;
constructor(id: string, panel?: IPanel) {
super(id);
constructor(id: string, component: string, panel?: IPanel) {
super(id, component);
this.addDisposables(
this._onDidConstraintsChangeInternal,

View File

@ -1,4 +1,4 @@
import { Emitter, Event } from '../events';
import { DockviewEvent, Emitter, Event } from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { IPanel, Parameters } from '../panel/types';
@ -24,9 +24,14 @@ export interface PanelApi {
readonly onDidFocusChange: Event<FocusEvent>;
readonly onDidVisibilityChange: Event<VisibilityEvent>;
readonly onDidActiveChange: Event<ActiveEvent>;
setVisible(isVisible: boolean): void;
readonly onDidParametersChange: Event<Parameters>;
setActive(): void;
setVisible(isVisible: boolean): void;
updateParameters(parameters: Parameters): void;
/**
* The id of the component renderer
*/
readonly component: string;
/**
* The id of the panel that would have been assigned when the panel was created
*/
@ -51,6 +56,16 @@ export interface PanelApi {
* The panel height in pixels
*/
readonly height: number;
readonly onWillFocus: Event<WillFocusEvent>;
getParameters<T extends Parameters = Parameters>(): T;
}
export class WillFocusEvent extends DockviewEvent {
constructor() {
super();
}
}
/**
@ -62,67 +77,59 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
private _isVisible = true;
private _width = 0;
private _height = 0;
private _parameters: Parameters = {};
private readonly panelUpdatesDisposable = new MutableDisposable();
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>({
replay: true,
});
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>();
readonly onDidDimensionsChange = this._onDidDimensionChange.event;
//
readonly _onDidChangeFocus = new Emitter<FocusEvent>({
replay: true,
});
readonly _onDidChangeFocus = new Emitter<FocusEvent>();
readonly onDidFocusChange: Event<FocusEvent> = this._onDidChangeFocus.event;
//
readonly _onFocusEvent = new Emitter<void>();
readonly onFocusEvent: Event<void> = this._onFocusEvent.event;
readonly _onWillFocus = new Emitter<WillFocusEvent>();
readonly onWillFocus: Event<WillFocusEvent> = this._onWillFocus.event;
//
readonly _onDidVisibilityChange = new Emitter<VisibilityEvent>({
replay: true,
});
readonly _onDidVisibilityChange = new Emitter<VisibilityEvent>();
readonly onDidVisibilityChange: Event<VisibilityEvent> =
this._onDidVisibilityChange.event;
//
readonly _onVisibilityChange = new Emitter<VisibilityEvent>();
readonly onVisibilityChange: Event<VisibilityEvent> =
this._onVisibilityChange.event;
//
readonly _onDidActiveChange = new Emitter<ActiveEvent>({
replay: true,
});
readonly _onWillVisibilityChange = new Emitter<VisibilityEvent>();
readonly onWillVisibilityChange: Event<VisibilityEvent> =
this._onWillVisibilityChange.event;
readonly _onDidActiveChange = new Emitter<ActiveEvent>();
readonly onDidActiveChange: Event<ActiveEvent> =
this._onDidActiveChange.event;
//
readonly _onActiveChange = new Emitter<void>();
readonly onActiveChange: Event<void> = this._onActiveChange.event;
//
readonly _onUpdateParameters = new Emitter<Parameters>();
readonly onUpdateParameters: Event<Parameters> =
this._onUpdateParameters.event;
//
get isFocused() {
readonly _onDidParametersChange = new Emitter<Parameters>();
readonly onDidParametersChange: Event<Parameters> =
this._onDidParametersChange.event;
get isFocused(): boolean {
return this._isFocused;
}
get isActive() {
get isActive(): boolean {
return this._isActive;
}
get isVisible() {
get isVisible(): boolean {
return this._isVisible;
}
get width() {
get width(): number {
return this._width;
}
get height() {
get height(): number {
return this._height;
}
constructor(readonly id: string) {
constructor(readonly id: string, readonly component: string) {
super();
this.addDisposables(
@ -144,16 +151,22 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
this._onDidChangeFocus,
this._onDidVisibilityChange,
this._onDidActiveChange,
this._onFocusEvent,
this._onWillFocus,
this._onActiveChange,
this._onVisibilityChange,
this._onUpdateParameters
this._onWillFocus,
this._onWillVisibilityChange,
this._onDidParametersChange
);
}
getParameters<T extends Parameters = Parameters>(): T {
return this._parameters as T;
}
public initialize(panel: IPanel): void {
this.panelUpdatesDisposable.value = this._onUpdateParameters.event(
this.panelUpdatesDisposable.value = this._onDidParametersChange.event(
(parameters) => {
this._parameters = parameters;
panel.update({
params: parameters,
});
@ -161,8 +174,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
);
}
setVisible(isVisible: boolean) {
this._onVisibilityChange.fire({ isVisible });
setVisible(isVisible: boolean): void {
this._onWillVisibilityChange.fire({ isVisible });
}
setActive(): void {
@ -170,10 +183,6 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
}
updateParameters(parameters: Parameters): void {
this._onUpdateParameters.fire(parameters);
}
dispose() {
super.dispose();
this._onDidParametersChange.fire(parameters);
}
}

View File

@ -35,8 +35,8 @@ export class PaneviewPanelApiImpl
this._pane = pane;
}
constructor(id: string) {
super(id);
constructor(id: string, component: string) {
super(id, component);
this.addDisposables(
this._onDidExpansionChange,

View File

@ -45,8 +45,8 @@ export class SplitviewPanelApiImpl
this._onDidSizeChange.event;
//
constructor(id: string) {
super(id);
constructor(id: string, component: string) {
super(id, component);
this.addDisposables(
this._onDidConstraintsChangeInternal,

View File

@ -0,0 +1,3 @@
export const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100, width: 300, height: 300 };

View File

@ -1,4 +1,4 @@
import { getElementsByTagName } from '../dom';
import { disableIframePointEvents } from '../dom';
import { addDisposableListener, Emitter } from '../events';
import {
CompositeDisposable,
@ -10,7 +10,7 @@ export abstract class DragHandler extends CompositeDisposable {
private readonly dataDisposable = new MutableDisposable();
private readonly pointerEventsDisposable = new MutableDisposable();
private readonly _onDragStart = new Emitter<void>();
private readonly _onDragStart = new Emitter<DragEvent>();
readonly onDragStart = this._onDragStart.event;
constructor(protected readonly el: HTMLElement) {
@ -25,7 +25,7 @@ export abstract class DragHandler extends CompositeDisposable {
this.configure();
}
abstract getData(dataTransfer?: DataTransfer | null): IDisposable;
abstract getData(event: DragEvent): IDisposable;
protected isCancelled(_event: DragEvent): boolean {
return false;
@ -35,54 +35,49 @@ export abstract class DragHandler extends CompositeDisposable {
this.addDisposables(
this._onDragStart,
addDisposableListener(this.el, 'dragstart', (event) => {
if (this.isCancelled(event)) {
if (event.defaultPrevented || this.isCancelled(event)) {
event.preventDefault();
return;
}
const iframes = [
...getElementsByTagName('iframe'),
...getElementsByTagName('webview'),
];
const iframes = disableIframePointEvents();
this.pointerEventsDisposable.value = {
dispose: () => {
for (const iframe of iframes) {
iframe.style.pointerEvents = 'auto';
}
iframes.release();
},
};
for (const iframe of iframes) {
iframe.style.pointerEvents = 'none';
}
this.el.classList.add('dv-dragged');
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
this.dataDisposable.value = this.getData(event.dataTransfer);
this.dataDisposable.value = this.getData(event);
this._onDragStart.fire(event);
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
/**
* Although this is not used by dockview many third party dnd libraries will check
* dataTransfer.types to determine valid drag events.
*
* For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
* through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
* dnd logic. You can see the code at
* https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
*/
event.dataTransfer.setData(
'text/plain',
'__dockview_internal_drag_event__'
);
const hasData = event.dataTransfer.items.length > 0;
if (!hasData) {
/**
* Although this is not used by dockview many third party dnd libraries will check
* dataTransfer.types to determine valid drag events.
*
* For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
* through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
* dnd logic. You can see the code at
P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
*/
event.dataTransfer.setData('text/plain', '');
}
}
}),
addDisposableListener(this.el, 'dragend', () => {
this.pointerEventsDisposable.dispose();
this.dataDisposable.dispose();
setTimeout(() => {
this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing
}, 0);
})
);
}

View File

@ -1,7 +1,5 @@
class TransferObject {
constructor() {
//
}
// intentionally empty class
}
export class PanelTransfer extends TransferObject {

View File

@ -13,22 +13,51 @@ export class DragAndDropObserver extends CompositeDisposable {
private target: EventTarget | null = null;
constructor(
private element: HTMLElement,
private callbacks: IDragAndDropObserverCallbacks
private readonly element: HTMLElement,
private readonly callbacks: IDragAndDropObserverCallbacks
) {
super();
this.registerListeners();
}
onDragEnter(e: DragEvent): void {
this.target = e.target;
this.callbacks.onDragEnter(e);
}
onDragOver(e: DragEvent): void {
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
if (this.callbacks.onDragOver) {
this.callbacks.onDragOver(e);
}
}
onDragLeave(e: DragEvent): void {
if (this.target === e.target) {
this.target = null;
this.callbacks.onDragLeave(e);
}
}
onDragEnd(e: DragEvent): void {
this.target = null;
this.callbacks.onDragEnd(e);
}
onDrop(e: DragEvent): void {
this.callbacks.onDrop(e);
}
private registerListeners(): void {
this.addDisposables(
addDisposableListener(
this.element,
'dragenter',
(e: DragEvent) => {
this.target = e.target;
this.callbacks.onDragEnter(e);
this.onDragEnter(e);
},
true
)
@ -39,11 +68,7 @@ export class DragAndDropObserver extends CompositeDisposable {
this.element,
'dragover',
(e: DragEvent) => {
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
if (this.callbacks.onDragOver) {
this.callbacks.onDragOver(e);
}
this.onDragOver(e);
},
true
)
@ -51,24 +76,19 @@ export class DragAndDropObserver extends CompositeDisposable {
this.addDisposables(
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
if (this.target === e.target) {
this.target = null;
this.callbacks.onDragLeave(e);
}
this.onDragLeave(e);
})
);
this.addDisposables(
addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
this.target = null;
this.callbacks.onDragEnd(e);
this.onDragEnd(e);
})
);
this.addDisposables(
addDisposableListener(this.element, 'drop', (e: DragEvent) => {
this.callbacks.onDrop(e);
this.onDrop(e);
})
);
}

View File

@ -0,0 +1,23 @@
.dv-drop-target-container {
position: absolute;
z-index: 9999;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
pointer-events: none;
overflow: hidden;
--dv-transition-duration: 300ms;
.dv-drop-target-anchor {
position: relative;
border: var(--dv-drag-over-border);
transition: opacity var(--dv-transition-duration) ease-in,
top var(--dv-transition-duration) ease-out,
left var(--dv-transition-duration) ease-out,
width var(--dv-transition-duration) ease-out,
height var(--dv-transition-duration) ease-out;
background-color: var(--dv-drag-over-background-color);
opacity: 1;
}
}

View File

@ -0,0 +1,102 @@
import { CompositeDisposable, Disposable } from '../lifecycle';
import { DropTargetTargetModel } from './droptarget';
export class DropTargetAnchorContainer extends CompositeDisposable {
private _model:
| { root: HTMLElement; overlay: HTMLElement; changed: boolean }
| undefined;
private _outline: HTMLElement | undefined;
private _disabled = false;
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
if (this.disabled === value) {
return;
}
this._disabled = value;
if (value) {
this.model?.clear();
}
}
get model(): DropTargetTargetModel | undefined {
if (this.disabled) {
return undefined;
}
return {
clear: () => {
if (this._model) {
this._model.root.parentElement?.removeChild(
this._model.root
);
}
this._model = undefined;
},
exists: () => {
return !!this._model;
},
getElements: (event?: DragEvent, outline?: HTMLElement) => {
const changed = this._outline !== outline;
this._outline = outline;
if (this._model) {
this._model.changed = changed;
return this._model;
}
const container = this.createContainer();
const anchor = this.createAnchor();
this._model = { root: container, overlay: anchor, changed };
container.appendChild(anchor);
this.element.appendChild(container);
if (event?.target instanceof HTMLElement) {
const targetBox = event.target.getBoundingClientRect();
const box = this.element.getBoundingClientRect();
anchor.style.left = `${targetBox.left - box.left}px`;
anchor.style.top = `${targetBox.top - box.top}px`;
}
return this._model;
},
};
}
constructor(readonly element: HTMLElement, options: { disabled: boolean }) {
super();
this._disabled = options.disabled;
this.addDisposables(
Disposable.from(() => {
this.model?.clear();
})
);
}
private createContainer(): HTMLElement {
const el = document.createElement('div');
el.className = 'dv-drop-target-container';
return el;
}
private createAnchor(): HTMLElement {
const el = document.createElement('div');
el.className = 'dv-drop-target-anchor';
el.style.visibility = 'hidden';
return el;
}
}

View File

@ -1,7 +1,8 @@
.drop-target {
.dv-drop-target {
position: relative;
--dv-transition-duration: 70ms;
> .drop-target-dropzone {
> .dv-drop-target-dropzone {
position: absolute;
left: 0px;
top: 0px;
@ -10,32 +11,43 @@
z-index: 1000;
pointer-events: none;
> .drop-target-selection {
> .dv-drop-target-selection {
position: relative;
box-sizing: border-box;
height: 100%;
width: 100%;
border: var(--dv-drag-over-border);
background-color: var(--dv-drag-over-background-color);
transition: top 70ms ease-out, left 70ms ease-out,
width 70ms ease-out, height 70ms ease-out,
opacity 0.15s ease-out;
transition: top var(--dv-transition-duration) ease-out,
left var(--dv-transition-duration) ease-out,
width var(--dv-transition-duration) ease-out,
height var(--dv-transition-duration) ease-out,
opacity var(--dv-transition-duration) ease-out;
will-change: transform;
pointer-events: none;
&.small-top {
border-top: 1px solid var(--dv-drag-over-border-color);
&.dv-drop-target-top {
&.dv-drop-target-small-vertical {
border-top: 1px solid var(--dv-drag-over-border-color);
}
}
&.small-bottom {
border-bottom: 1px solid var(--dv-drag-over-border-color);
&.dv-drop-target-bottom {
&.dv-drop-target-small-vertical {
border-bottom: 1px solid var(--dv-drag-over-border-color);
}
}
&.small-left {
border-left: 1px solid var(--dv-drag-over-border-color);
&.dv-drop-target-left {
&.dv-drop-target-small-horizontal {
border-left: 1px solid var(--dv-drag-over-border-color);
}
}
&.small-right {
border-right: 1px solid var(--dv-drag-over-border-color);
&.dv-drop-target-right {
&.dv-drop-target-small-horizontal {
border-right: 1px solid var(--dv-drag-over-border-color);
}
}
}
}

View File

@ -1,12 +1,35 @@
import { toggleClass } from '../dom';
import { Emitter, Event } from '../events';
import { DockviewEvent, Emitter, Event } from '../events';
import { CompositeDisposable } from '../lifecycle';
import { DragAndDropObserver } from './dnd';
import { clamp } from '../math';
import { Direction } from '../gridview/baseComponentGridview';
function numberOrFallback(maybeNumber: any, fallback: number): number {
return typeof maybeNumber === 'number' ? maybeNumber : fallback;
export interface DroptargetEvent {
readonly position: Position;
readonly nativeEvent: DragEvent;
}
export class WillShowOverlayEvent
extends DockviewEvent
implements DroptargetEvent
{
get nativeEvent(): DragEvent {
return this.options.nativeEvent;
}
get position(): Position {
return this.options.position;
}
constructor(
private readonly options: {
nativeEvent: DragEvent;
position: Position;
}
) {
super();
}
}
export function directionToPosition(direction: Direction): Position {
@ -43,16 +66,54 @@ export function positionToDirection(position: Position): Direction {
}
}
export interface DroptargetEvent {
readonly position: Position;
readonly nativeEvent: DragEvent;
}
export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
export type CanDisplayOverlay =
| boolean
| ((dragEvent: DragEvent, state: Position) => boolean);
export type CanDisplayOverlay = (
dragEvent: DragEvent,
state: Position
) => boolean;
export type MeasuredValue = { value: number; type: 'pixels' | 'percentage' };
export type DroptargetOverlayModel = {
size?: MeasuredValue;
activationSize?: MeasuredValue;
};
const DEFAULT_ACTIVATION_SIZE: MeasuredValue = {
value: 20,
type: 'percentage',
};
const DEFAULT_SIZE: MeasuredValue = {
value: 50,
type: 'percentage',
};
const SMALL_WIDTH_BOUNDARY = 100;
const SMALL_HEIGHT_BOUNDARY = 100;
export interface DropTargetTargetModel {
getElements(
event?: DragEvent,
outline?: HTMLElement
): {
root: HTMLElement;
overlay: HTMLElement;
changed: boolean;
};
exists(): boolean;
clear(): void;
}
export interface DroptargetOptions {
canDisplayOverlay: CanDisplayOverlay;
acceptedTargetZones: Position[];
overlayModel?: DroptargetOverlayModel;
getOverrideTarget?: () => DropTargetTargetModel | undefined;
className?: string;
getOverlayOutline?: () => HTMLElement | null;
}
export class Droptarget extends CompositeDisposable {
private targetElement: HTMLElement | undefined;
@ -63,131 +124,204 @@ export class Droptarget extends CompositeDisposable {
private readonly _onDrop = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
private readonly _onWillShowOverlay = new Emitter<WillShowOverlayEvent>();
readonly onWillShowOverlay: Event<WillShowOverlayEvent> =
this._onWillShowOverlay.event;
readonly dnd: DragAndDropObserver;
private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__';
private static ACTUAL_TARGET: Droptarget | undefined;
private _disabled: boolean;
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = value;
}
get state(): Position | undefined {
return this._state;
}
constructor(
private readonly element: HTMLElement,
private readonly options: {
canDisplayOverlay: CanDisplayOverlay;
acceptedTargetZones: Position[];
overlayModel?: {
size?: { value: number; type: 'pixels' | 'percentage' };
activationSize?: {
value: number;
type: 'pixels' | 'percentage';
};
};
}
private readonly options: DroptargetOptions
) {
super();
this._disabled = false;
// use a set to take advantage of #<set>.has
this._acceptedTargetZonesSet = new Set(
this.options.acceptedTargetZones
);
this.addDisposables(
this._onDrop,
new DragAndDropObserver(this.element, {
onDragEnter: () => undefined,
onDragOver: (e) => {
if (this._acceptedTargetZonesSet.size === 0) {
this.removeDropTarget();
this.dnd = new DragAndDropObserver(this.element, {
onDragEnter: () => {
this.options.getOverrideTarget?.()?.getElements();
},
onDragOver: (e) => {
Droptarget.ACTUAL_TARGET = this;
const overrideTraget = this.options.getOverrideTarget?.();
if (this._acceptedTargetZonesSet.size === 0) {
if (overrideTraget) {
return;
}
this.removeDropTarget();
return;
}
const width = this.element.clientWidth;
const height = this.element.clientHeight;
const target =
this.options.getOverlayOutline?.() ?? this.element;
if (width === 0 || height === 0) {
return; // avoid div!0
}
const width = target.offsetWidth;
const height = target.offsetHeight;
const rect = (
e.currentTarget as HTMLElement
).getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (width === 0 || height === 0) {
return; // avoid div!0
}
const quadrant = this.calculateQuadrant(
this._acceptedTargetZonesSet,
x,
y,
width,
height
);
const rect = (
e.currentTarget as HTMLElement
).getBoundingClientRect();
const x = (e.clientX ?? 0) - rect.left;
const y = (e.clientY ?? 0) - rect.top;
/**
* If the event has already been used by another DropTarget instance
* then don't show a second drop target, only one target should be
* active at any one time
*/
if (this.isAlreadyUsed(e) || quadrant === null) {
// no drop target should be displayed
this.removeDropTarget();
const quadrant = this.calculateQuadrant(
this._acceptedTargetZonesSet,
x,
y,
width,
height
);
/**
* If the event has already been used by another DropTarget instance
* then don't show a second drop target, only one target should be
* active at any one time
*/
if (this.isAlreadyUsed(e) || quadrant === null) {
// no drop target should be displayed
this.removeDropTarget();
return;
}
if (!this.options.canDisplayOverlay(e, quadrant)) {
if (overrideTraget) {
return;
}
if (typeof this.options.canDisplayOverlay === 'boolean') {
if (!this.options.canDisplayOverlay) {
this.removeDropTarget();
return;
}
} else if (!this.options.canDisplayOverlay(e, quadrant)) {
this.removeDropTarget();
return;
}
this.markAsUsed(e);
if (!this.targetElement) {
this.targetElement = document.createElement('div');
this.targetElement.className = 'drop-target-dropzone';
this.overlayElement = document.createElement('div');
this.overlayElement.className = 'drop-target-selection';
this._state = 'center';
this.targetElement.appendChild(this.overlayElement);
this.element.classList.add('drop-target');
this.element.append(this.targetElement);
}
this.toggleClasses(quadrant, width, height);
this.setState(quadrant);
},
onDragLeave: () => {
this.removeDropTarget();
},
onDragEnd: () => {
return;
}
const willShowOverlayEvent = new WillShowOverlayEvent({
nativeEvent: e,
position: quadrant,
});
/**
* Provide an opportunity to prevent the overlay appearing and in turn
* any dnd behaviours
*/
this._onWillShowOverlay.fire(willShowOverlayEvent);
if (willShowOverlayEvent.defaultPrevented) {
this.removeDropTarget();
},
onDrop: (e) => {
e.preventDefault();
return;
}
const state = this._state;
this.markAsUsed(e);
this.removeDropTarget();
if (overrideTraget) {
//
} else if (!this.targetElement) {
this.targetElement = document.createElement('div');
this.targetElement.className = 'dv-drop-target-dropzone';
this.overlayElement = document.createElement('div');
this.overlayElement.className = 'dv-drop-target-selection';
this._state = 'center';
this.targetElement.appendChild(this.overlayElement);
if (state) {
target.classList.add('dv-drop-target');
target.append(this.targetElement);
// this.overlayElement.style.opacity = '0';
// requestAnimationFrame(() => {
// if (this.overlayElement) {
// this.overlayElement.style.opacity = '';
// }
// });
}
this.toggleClasses(quadrant, width, height);
this._state = quadrant;
},
onDragLeave: () => {
const target = this.options.getOverrideTarget?.();
if (target) {
return;
}
this.removeDropTarget();
},
onDragEnd: (e) => {
const target = this.options.getOverrideTarget?.();
if (target && Droptarget.ACTUAL_TARGET === this) {
if (this._state) {
// only stop the propagation of the event if we are dealing with it
// which is only when the target has state
e.stopPropagation();
this._onDrop.fire({ position: state, nativeEvent: e });
this._onDrop.fire({
position: this._state,
nativeEvent: e,
});
}
},
})
);
}
this.removeDropTarget();
target?.clear();
},
onDrop: (e) => {
e.preventDefault();
const state = this._state;
this.removeDropTarget();
this.options.getOverrideTarget?.()?.clear();
if (state) {
// only stop the propagation of the event if we are dealing with it
// which is only when the target has state
e.stopPropagation();
this._onDrop.fire({ position: state, nativeEvent: e });
}
},
});
this.addDisposables(this._onDrop, this._onWillShowOverlay, this.dnd);
}
setTargetZones(acceptedTargetZones: Position[]): void {
this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
}
setOverlayModel(model: DroptargetOverlayModel): void {
this.options.overlayModel = model;
}
dispose(): void {
this.removeDropTarget();
super.dispose();
@ -201,7 +335,7 @@ export class Droptarget extends CompositeDisposable {
}
/**
* Check is the event has already been used by another instance od DropTarget
* Check is the event has already been used by another instance of DropTarget
*/
private isAlreadyUsed(event: DragEvent): boolean {
const value = (event as any)[Droptarget.USED_EVENT_ID];
@ -213,12 +347,14 @@ export class Droptarget extends CompositeDisposable {
width: number,
height: number
): void {
if (!this.overlayElement) {
const target = this.options.getOverrideTarget?.();
if (!target && !this.overlayElement) {
return;
}
const isSmallX = width < 100;
const isSmallY = height < 100;
const isSmallX = width < SMALL_WIDTH_BOUNDARY;
const isSmallY = height < SMALL_HEIGHT_BOUNDARY;
const isLeft = quadrant === 'left';
const isRight = quadrant === 'right';
@ -230,68 +366,175 @@ export class Droptarget extends CompositeDisposable {
const topClass = !isSmallY && isTop;
const bottomClass = !isSmallY && isBottom;
let size = 0.5;
let size = 1;
if (this.options.overlayModel?.size?.type === 'percentage') {
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
}
const sizeOptions = this.options.overlayModel?.size ?? DEFAULT_SIZE;
if (this.options.overlayModel?.size?.type === 'pixels') {
if (sizeOptions.type === 'percentage') {
size = clamp(sizeOptions.value, 0, 100) / 100;
} else {
if (rightClass || leftClass) {
size =
clamp(0, this.options.overlayModel.size.value, width) /
width;
size = clamp(0, sizeOptions.value, width) / width;
}
if (topClass || bottomClass) {
size =
clamp(0, this.options.overlayModel.size.value, height) /
height;
size = clamp(0, sizeOptions.value, height) / height;
}
}
const translate = (1 - size) / 2;
const scale = size;
if (target) {
const outlineEl =
this.options.getOverlayOutline?.() ?? this.element;
const elBox = outlineEl.getBoundingClientRect();
let transform: string;
const ta = target.getElements(undefined, outlineEl);
const el = ta.root;
const overlay = ta.overlay;
const bigbox = el.getBoundingClientRect();
const rootTop = elBox.top - bigbox.top;
const rootLeft = elBox.left - bigbox.left;
const box = {
top: rootTop,
left: rootLeft,
width: width,
height: height,
};
if (rightClass) {
box.left = rootLeft + width * (1 - size);
box.width = width * size;
} else if (leftClass) {
box.width = width * size;
} else if (topClass) {
box.height = height * size;
} else if (bottomClass) {
box.top = rootTop + height * (1 - size);
box.height = height * size;
}
if (isSmallX && isLeft) {
box.width = 4;
}
if (isSmallX && isRight) {
box.left = rootLeft + width - 4;
box.width = 4;
}
const topPx = `${Math.round(box.top)}px`;
const leftPx = `${Math.round(box.left)}px`;
const widthPx = `${Math.round(box.width)}px`;
const heightPx = `${Math.round(box.height)}px`;
if (
overlay.style.top === topPx &&
overlay.style.left === leftPx &&
overlay.style.width === widthPx &&
overlay.style.height === heightPx
) {
return;
}
overlay.style.top = topPx;
overlay.style.left = leftPx;
overlay.style.width = widthPx;
overlay.style.height = heightPx;
overlay.style.visibility = 'visible';
overlay.className = `dv-drop-target-anchor${
this.options.className ? ` ${this.options.className}` : ''
}`;
toggleClass(overlay, 'dv-drop-target-left', isLeft);
toggleClass(overlay, 'dv-drop-target-right', isRight);
toggleClass(overlay, 'dv-drop-target-top', isTop);
toggleClass(overlay, 'dv-drop-target-bottom', isBottom);
toggleClass(
overlay,
'dv-drop-target-center',
quadrant === 'center'
);
if (ta.changed) {
toggleClass(
overlay,
'dv-drop-target-anchor-container-changed',
true
);
setTimeout(() => {
toggleClass(
overlay,
'dv-drop-target-anchor-container-changed',
false
);
}, 10);
}
return;
}
if (!this.overlayElement) {
return;
}
const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
/**
* You can also achieve the overlay placement using the transform CSS property
* to translate and scale the element however this has the undesired effect of
* 'skewing' the element. Comment left here for anybody that ever revisits this.
*
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform
*
* right
* translateX(${100 * (1 - size) / 2}%) scaleX(${scale})
*
* left
* translateX(-${100 * (1 - size) / 2}%) scaleX(${scale})
*
* top
* translateY(-${100 * (1 - size) / 2}%) scaleY(${scale})
*
* bottom
* translateY(${100 * (1 - size) / 2}%) scaleY(${scale})
*/
if (rightClass) {
transform = `translateX(${100 * translate}%) scaleX(${scale})`;
box.left = `${100 * (1 - size)}%`;
box.width = `${100 * size}%`;
} else if (leftClass) {
transform = `translateX(-${100 * translate}%) scaleX(${scale})`;
box.width = `${100 * size}%`;
} else if (topClass) {
transform = `translateY(-${100 * translate}%) scaleY(${scale})`;
box.height = `${100 * size}%`;
} else if (bottomClass) {
transform = `translateY(${100 * translate}%) scaleY(${scale})`;
} else {
transform = '';
box.top = `${100 * (1 - size)}%`;
box.height = `${100 * size}%`;
}
this.overlayElement.style.transform = transform;
this.overlayElement.style.top = box.top;
this.overlayElement.style.left = box.left;
this.overlayElement.style.width = box.width;
this.overlayElement.style.height = box.height;
toggleClass(this.overlayElement, 'small-right', isSmallX && isRight);
toggleClass(this.overlayElement, 'small-left', isSmallX && isLeft);
toggleClass(this.overlayElement, 'small-top', isSmallY && isTop);
toggleClass(this.overlayElement, 'small-bottom', isSmallY && isBottom);
}
private setState(quadrant: Position): void {
switch (quadrant) {
case 'top':
this._state = 'top';
break;
case 'left':
this._state = 'left';
break;
case 'bottom':
this._state = 'bottom';
break;
case 'right':
this._state = 'right';
break;
case 'center':
this._state = 'center';
break;
}
toggleClass(
this.overlayElement,
'dv-drop-target-small-vertical',
isSmallY
);
toggleClass(
this.overlayElement,
'dv-drop-target-small-horizontal',
isSmallX
);
toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
toggleClass(this.overlayElement, 'dv-drop-target-right', isRight);
toggleClass(this.overlayElement, 'dv-drop-target-top', isTop);
toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom);
toggleClass(
this.overlayElement,
'dv-drop-target-center',
quadrant === 'center'
);
}
private calculateQuadrant(
@ -301,14 +544,11 @@ export class Droptarget extends CompositeDisposable {
width: number,
height: number
): Position | null {
const isPercentage =
this.options.overlayModel?.activationSize === undefined ||
this.options.overlayModel?.activationSize?.type === 'percentage';
const activationSizeOptions =
this.options.overlayModel?.activationSize ??
DEFAULT_ACTIVATION_SIZE;
const value = numberOrFallback(
this.options?.overlayModel?.activationSize?.value,
20
);
const isPercentage = activationSizeOptions.type === 'percentage';
if (isPercentage) {
return calculateQuadrantAsPercentage(
@ -317,7 +557,7 @@ export class Droptarget extends CompositeDisposable {
y,
width,
height,
value
activationSizeOptions.value
);
}
@ -327,17 +567,19 @@ export class Droptarget extends CompositeDisposable {
y,
width,
height,
value
activationSizeOptions.value
);
}
private removeDropTarget(): void {
if (this.targetElement) {
this._state = undefined;
this.element.removeChild(this.targetElement);
this.targetElement.parentElement?.classList.remove(
'dv-drop-target'
);
this.targetElement.remove();
this.targetElement = undefined;
this.overlayElement = undefined;
this.element.classList.remove('drop-target');
}
}
}

View File

@ -2,13 +2,17 @@ import { addClasses, removeClasses } from '../dom';
export function addGhostImage(
dataTransfer: DataTransfer,
ghostElement: HTMLElement
ghostElement: HTMLElement,
options?: { x?: number; y?: number }
): void {
// class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
addClasses(ghostElement, 'dv-dragged');
// move the element off-screen initially otherwise it may in some cases be rendered at (0,0) momentarily
ghostElement.style.top = '-9999px';
document.body.appendChild(ghostElement);
dataTransfer.setDragImage(ghostElement, 0, 0);
dataTransfer.setDragImage(ghostElement, options?.x ?? 0, options?.y ?? 0);
setTimeout(() => {
removeClasses(ghostElement, 'dv-dragged');

View File

@ -1,3 +1,4 @@
import { DockviewComponent } from '../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { quasiPreventDefault } from '../dom';
import { addDisposableListener } from '../events';
@ -12,7 +13,7 @@ export class GroupDragHandler extends DragHandler {
constructor(
element: HTMLElement,
private readonly accessorId: string,
private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanel
) {
super(element);
@ -20,7 +21,7 @@ export class GroupDragHandler extends DragHandler {
this.addDisposables(
addDisposableListener(
element,
'mousedown',
'pointerdown',
(e) => {
if (e.shiftKey) {
/**
@ -37,15 +38,17 @@ export class GroupDragHandler extends DragHandler {
}
override isCancelled(_event: DragEvent): boolean {
if (this.group.api.isFloating && !_event.shiftKey) {
if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
return true;
}
return false;
}
getData(dataTransfer: DataTransfer | null): IDisposable {
getData(dragEvent: DragEvent): IDisposable {
const dataTransfer = dragEvent.dataTransfer;
this.panelTransfer.setData(
[new PanelTransfer(this.accessorId, this.group.id, null)],
[new PanelTransfer(this.accessor.id, this.group.id, null)],
PanelTransfer.prototype
);
@ -69,9 +72,11 @@ export class GroupDragHandler extends DragHandler {
ghostElement.style.lineHeight = '20px';
ghostElement.style.borderRadius = '12px';
ghostElement.style.position = 'absolute';
ghostElement.style.pointerEvents = 'none';
ghostElement.style.top = '-9999px';
ghostElement.textContent = `Multiple Panels (${this.group.size})`;
addGhostImage(dataTransfer, ghostElement);
addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
}
return {

View File

@ -6,8 +6,13 @@ import {
import { Emitter, Event } from '../../../events';
import { trackFocus } from '../../../dom';
import { IDockviewPanel } from '../../dockviewPanel';
import { DockviewComponent } from '../../dockviewComponent';
import { Droptarget } from '../../../dnd/droptarget';
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
import { getPanelData } from '../../../dnd/dataTransfer';
export interface IContentContainer extends IDisposable {
readonly dropTarget: Droptarget;
onDidFocus: Event<void>;
onDidBlur: Event<void>;
element: HTMLElement;
@ -16,15 +21,16 @@ export interface IContentContainer extends IDisposable {
closePanel: () => void;
show(): void;
hide(): void;
renderPanel(panel: IDockviewPanel, options: { asActive: boolean }): void;
}
export class ContentContainer
extends CompositeDisposable
implements IContentContainer
{
private _element: HTMLElement;
private readonly _element: HTMLElement;
private panel: IDockviewPanel | undefined;
private disposable = new MutableDisposable();
private readonly disposable = new MutableDisposable();
private readonly _onDidFocus = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event;
@ -36,19 +42,57 @@ export class ContentContainer
return this._element;
}
constructor() {
readonly dropTarget: Droptarget;
constructor(
private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanelModel
) {
super();
this._element = document.createElement('div');
this._element.className = 'content-container';
this._element.className = 'dv-content-container';
this._element.tabIndex = -1;
this.addDisposables(this._onDidFocus, this._onDidBlur);
// for hosted containers
// 1) register a drop target on the host
// 2) register window dragStart events to disable pointer events
// 3) register dragEnd events
// 4) register mouseMove events (if no buttons are present we take this as a dragEnd event)
const target = group.dropTargetContainer;
this.dropTarget = new Droptarget(this.element, {
getOverlayOutline: () => {
return accessor.options.theme?.dndPanelOverlay === 'group'
? this.element.parentElement
: null;
},
className: 'dv-drop-target-content',
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
canDisplayOverlay: (event, position) => {
if (
this.group.locked === 'no-drop-target' ||
(this.group.locked && position === 'center')
) {
return false;
}
const data = getPanelData();
if (
!data &&
event.shiftKey &&
this.group.location.type !== 'floating'
) {
return false;
}
if (data && data.viewId === this.accessor.id) {
return true;
}
return this.group.canDisplayOverlay(event, position, 'content');
},
getOverrideTarget: target ? () => target.model : undefined,
});
this.addDisposables(this.dropTarget);
}
show(): void {
@ -59,25 +103,60 @@ export class ContentContainer
this.element.style.display = 'none';
}
public openPanel(panel: IDockviewPanel): void {
if (this.panel === panel) {
return;
}
if (this.panel) {
if (this.panel.view?.content) {
this._element.removeChild(this.panel.view.content.element);
}
this.panel = undefined;
renderPanel(
panel: IDockviewPanel,
options: { asActive: boolean } = { asActive: true }
): void {
const doRender =
options.asActive ||
(this.panel && this.group.isPanelActive(this.panel));
if (
this.panel &&
this.panel.view.content.element.parentElement === this._element
) {
/**
* If the currently attached panel is mounted directly to the content then remove it
*/
this._element.removeChild(this.panel.view.content.element);
}
this.panel = panel;
const disposable = new CompositeDisposable();
let container: HTMLElement;
if (this.panel.view) {
const _onDidFocus = this.panel.view.content.onDidFocus;
const _onDidBlur = this.panel.view.content.onDidBlur;
switch (panel.api.renderer) {
case 'onlyWhenVisible':
this.group.renderContainer.detatch(panel);
if (this.panel) {
if (doRender) {
this._element.appendChild(
this.panel.view.content.element
);
}
}
container = this._element;
break;
case 'always':
if (
panel.view.content.element.parentElement === this._element
) {
this._element.removeChild(panel.view.content.element);
}
container = this.group.renderContainer.attach({
panel,
referenceContainer: this,
});
break;
default:
throw new Error(
`dockview: invalid renderer type '${panel.api.renderer}'`
);
}
const focusTracker = trackFocus(this._element);
if (doRender) {
const focusTracker = trackFocus(container);
const disposable = new CompositeDisposable();
disposable.addDisposables(
focusTracker,
@ -85,21 +164,16 @@ export class ContentContainer
focusTracker.onDidBlur(() => this._onDidBlur.fire())
);
if (_onDidFocus) {
disposable.addDisposables(
_onDidFocus(() => this._onDidFocus.fire())
);
}
if (_onDidBlur) {
disposable.addDisposables(
_onDidBlur(() => this._onDidBlur.fire())
);
}
this.disposable.value = disposable;
}
}
this._element.appendChild(this.panel.view.content.element);
public openPanel(panel: IDockviewPanel): void {
if (this.panel === panel) {
return;
}
this.disposable.value = disposable;
this.renderPanel(panel);
}
public layout(_width: number, _height: number): void {
@ -107,10 +181,14 @@ export class ContentContainer
}
public closePanel(): void {
if (this.panel?.view?.content?.element) {
this._element.removeChild(this.panel.view.content.element);
this.panel = undefined;
if (this.panel) {
if (this.panel.api.renderer === 'onlyWhenVisible') {
this.panel.view.content.element.parentElement?.removeChild(
this.panel.view.content.element
);
}
}
this.panel = undefined;
}
public dispose(): void {

View File

@ -0,0 +1,87 @@
import { shiftAbsoluteElementIntoView } from '../../dom';
import { addDisposableListener } from '../../events';
import {
CompositeDisposable,
Disposable,
MutableDisposable,
} from '../../lifecycle';
export class PopupService extends CompositeDisposable {
private readonly _element: HTMLElement;
private _active: HTMLElement | null = null;
private readonly _activeDisposable = new MutableDisposable();
constructor(private readonly root: HTMLElement) {
super();
this._element = document.createElement('div');
this._element.className = 'dv-popover-anchor';
this._element.style.position = 'relative';
this.root.prepend(this._element);
this.addDisposables(
Disposable.from(() => {
this.close();
}),
this._activeDisposable
);
}
openPopover(
element: HTMLElement,
position: { x: number; y: number; zIndex?: string }
): void {
this.close();
const wrapper = document.createElement('div');
wrapper.style.position = 'absolute';
wrapper.style.zIndex = position.zIndex ?? 'var(--dv-overlay-z-index)';
wrapper.appendChild(element);
const anchorBox = this._element.getBoundingClientRect();
const offsetX = anchorBox.left;
const offsetY = anchorBox.top;
wrapper.style.top = `${position.y - offsetY}px`;
wrapper.style.left = `${position.x - offsetX}px`;
this._element.appendChild(wrapper);
this._active = wrapper;
this._activeDisposable.value = new CompositeDisposable(
addDisposableListener(window, 'pointerdown', (event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) {
return;
}
let el: HTMLElement | null = target;
while (el && el !== wrapper) {
el = el?.parentElement ?? null;
}
if (el) {
return; // clicked within popover
}
this.close();
})
);
requestAnimationFrame(() => {
shiftAbsoluteElementIntoView(wrapper, this.root);
});
}
close(): void {
if (this._active) {
this._active.remove();
this._activeDisposable.dispose();
this._active = null;
}
}
}

View File

@ -6,70 +6,78 @@
); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
}
.tab {
.dv-tab {
flex-shrink: 0;
&:focus-within,
&:focus {
position: relative;
&::after {
position: absolute;
content: '';
height: 100%;
width: 100%;
top: 0px;
left: 0px;
pointer-events: none;
outline: 1px solid var(--dv-tab-divider-color) !important;
outline-offset: -1px;
z-index: 5;
}
}
&.dv-tab-dragging {
.tab-action {
.dv-default-tab-action {
background-color: var(--dv-activegroup-visiblepanel-tab-color);
}
}
&.active-tab > .default-tab {
.tab-action {
visibility: visible;
}
}
&.inactive-tab > .default-tab {
.tab-action {
visibility: hidden;
}
&:hover {
.tab-action {
&.dv-active-tab {
.dv-default-tab {
.dv-default-tab-action {
visibility: visible;
}
}
}
.default-tab {
position: relative;
height: 100%;
display: flex;
min-width: 80px;
align-items: center;
padding: 0px 8px;
white-space: nowrap;
text-overflow: elipsis;
.tab-content {
padding: 0px 8px;
flex-grow: 1;
}
.action-container {
text-align: right;
display: flex;
.tab-list {
display: flex;
padding: 0px;
margin: 0px;
justify-content: flex-end;
.tab-action {
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
&:hover {
border-radius: 2px;
background-color: var(--dv-icon-hover-background-color);
}
&.dv-inactive-tab {
.dv-default-tab {
.dv-default-tab-action {
visibility: hidden;
}
&:hover {
.dv-default-tab-action {
visibility: visible;
}
}
}
}
.dv-default-tab {
position: relative;
height: 100%;
display: flex;
align-items: center;
white-space: nowrap;
text-overflow: ellipsis;
.dv-default-tab-content {
flex-grow: 1;
margin-right: 4px;
}
.dv-default-tab-action {
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
&:hover {
border-radius: 2px;
background-color: var(--dv-icon-hover-background-color);
}
}
}
}

View File

@ -1,18 +1,13 @@
import { CompositeDisposable } from '../../../lifecycle';
import { ITabRenderer, GroupPanelPartInitParameters } from '../../types';
import { addDisposableListener } from '../../../events';
import { PanelUpdateEvent } from '../../../panel/types';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { createCloseButton } from '../../../svg';
export class DefaultTab extends CompositeDisposable implements ITabRenderer {
private _element: HTMLElement;
private _content: HTMLElement;
private _actionContainer: HTMLElement;
private _list: HTMLElement;
private action: HTMLElement;
//
private params: GroupPanelPartInitParameters = {} as any;
private readonly _element: HTMLElement;
private readonly _content: HTMLElement;
private readonly action: HTMLElement;
private _title: string | undefined;
get element(): HTMLElement {
return this._element;
@ -22,70 +17,48 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
super();
this._element = document.createElement('div');
this._element.className = 'default-tab';
//
this._element.className = 'dv-default-tab';
this._content = document.createElement('div');
this._content.className = 'tab-content';
//
this._actionContainer = document.createElement('div');
this._actionContainer.className = 'action-container';
//
this._list = document.createElement('ul');
this._list.className = 'tab-list';
//
this._content.className = 'dv-default-tab-content';
this.action = document.createElement('div');
this.action.className = 'tab-action';
this.action.className = 'dv-default-tab-action';
this.action.appendChild(createCloseButton());
//
this._element.appendChild(this._content);
this._element.appendChild(this._actionContainer);
this._actionContainer.appendChild(this._list);
this._list.appendChild(this.action);
//
this._element.appendChild(this.action);
this.render();
}
init(params: GroupPanelPartInitParameters): void {
this._title = params.title;
this.addDisposables(
addDisposableListener(this._actionContainer, 'mousedown', (ev) => {
params.api.onDidTitleChange((event) => {
this._title = event.title;
this.render();
}),
addDisposableListener(this.action, 'pointerdown', (ev) => {
ev.preventDefault();
}),
addDisposableListener(this.action, 'click', (ev) => {
if (ev.defaultPrevented) {
return;
}
ev.preventDefault();
params.api.close();
})
);
this.render();
}
public update(event: PanelUpdateEvent): void {
this.params = { ...this.params, ...event.params };
this.render();
}
focus(): void {
//noop
}
public init(params: GroupPanelPartInitParameters): void {
this.params = params;
this._content.textContent = params.title;
addDisposableListener(this.action, 'click', (ev) => {
ev.preventDefault(); //
this.params.api.close();
});
}
onGroupChange(_group: DockviewGroupPanel): void {
this.render();
}
onPanelVisibleChange(_isPanelVisible: boolean): void {
this.render();
}
public layout(_width: number, _height: number): void {
// noop
}
private render(): void {
if (this._content.textContent !== this.params.title) {
this._content.textContent = this.params.title;
if (this._content.textContent !== this._title) {
this._content.textContent = this._title ?? '';
}
}
}

View File

@ -7,85 +7,88 @@ import {
} from '../../../dnd/dataTransfer';
import { toggleClass } from '../../../dom';
import { DockviewComponent } from '../../dockviewComponent';
import { DockviewDropTargets, ITabRenderer } from '../../types';
import { ITabRenderer } from '../../types';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget';
import {
DroptargetEvent,
Droptarget,
WillShowOverlayEvent,
} from '../../../dnd/droptarget';
import { DragHandler } from '../../../dnd/abstractDragHandler';
import { IDockviewPanel } from '../../dockviewPanel';
import { addGhostImage } from '../../../dnd/ghost';
export interface ITab extends IDisposable {
readonly panelId: string;
readonly element: HTMLElement;
setContent: (element: ITabRenderer) => void;
onChanged: Event<MouseEvent>;
onDrop: Event<DroptargetEvent>;
setActive(isActive: boolean): void;
class TabDragHandler extends DragHandler {
private readonly panelTransfer =
LocalSelectionTransfer.getInstance<PanelTransfer>();
constructor(
element: HTMLElement,
private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanel,
private readonly panel: IDockviewPanel
) {
super(element);
}
getData(event: DragEvent): IDisposable {
this.panelTransfer.setData(
[new PanelTransfer(this.accessor.id, this.group.id, this.panel.id)],
PanelTransfer.prototype
);
return {
dispose: () => {
this.panelTransfer.clearData(PanelTransfer.prototype);
},
};
}
}
export class Tab extends CompositeDisposable implements ITab {
export class Tab extends CompositeDisposable {
private readonly _element: HTMLElement;
private readonly droptarget: Droptarget;
private content?: ITabRenderer;
private readonly dropTarget: Droptarget;
private content: ITabRenderer | undefined = undefined;
private readonly _onChanged = new Emitter<MouseEvent>();
readonly onChanged: Event<MouseEvent> = this._onChanged.event;
private readonly _onPointDown = new Emitter<MouseEvent>();
readonly onPointerDown: Event<MouseEvent> = this._onPointDown.event;
private readonly _onDropped = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
private readonly _onDragStart = new Emitter<DragEvent>();
readonly onDragStart = this._onDragStart.event;
readonly onWillShowOverlay: Event<WillShowOverlayEvent>;
public get element(): HTMLElement {
return this._element;
}
constructor(
public readonly panelId: string,
public readonly panel: IDockviewPanel,
private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanel
) {
super();
this._element = document.createElement('div');
this._element.className = 'tab';
this._element.className = 'dv-tab';
this._element.tabIndex = 0;
this._element.draggable = true;
toggleClass(this.element, 'inactive-tab', true);
toggleClass(this.element, 'dv-inactive-tab', true);
this.addDisposables(
this._onChanged,
this._onDropped,
new (class Handler extends DragHandler {
private readonly panelTransfer =
LocalSelectionTransfer.getInstance<PanelTransfer>();
getData(): IDisposable {
this.panelTransfer.setData(
[new PanelTransfer(accessor.id, group.id, panelId)],
PanelTransfer.prototype
);
return {
dispose: () => {
this.panelTransfer.clearData(
PanelTransfer.prototype
);
},
};
}
})(this._element)
const dragHandler = new TabDragHandler(
this._element,
this.accessor,
this.group,
this.panel
);
this.addDisposables(
addDisposableListener(this._element, 'mousedown', (event) => {
if (event.defaultPrevented) {
return;
}
this._onChanged.fire(event);
})
);
this.droptarget = new Droptarget(this._element, {
acceptedTargetZones: ['center'],
this.dropTarget = new Droptarget(this._element, {
acceptedTargetZones: ['left', 'right'],
overlayModel: { activationSize: { value: 50, type: 'percentage' } },
canDisplayOverlay: (event, position) => {
if (this.group.locked) {
return false;
@ -94,36 +97,58 @@ export class Tab extends CompositeDisposable implements ITab {
const data = getPanelData();
if (data && this.accessor.id === data.viewId) {
if (
data.panelId === null &&
data.groupId === this.group.id
) {
// don't allow group move to drop on self
return false;
}
return this.panelId !== data.panelId;
return true;
}
return this.group.model.canDisplayOverlay(
event,
position,
DockviewDropTargets.Tab
'tab'
);
},
getOverrideTarget: () => group.model.dropTargetContainer?.model,
});
this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
this.addDisposables(
this.droptarget.onDrop((event) => {
this._onPointDown,
this._onDropped,
this._onDragStart,
dragHandler.onDragStart((event) => {
if (event.dataTransfer) {
const style = getComputedStyle(this.element);
const newNode = this.element.cloneNode(true) as HTMLElement;
Array.from(style).forEach((key) =>
newNode.style.setProperty(
key,
style.getPropertyValue(key),
style.getPropertyPriority(key)
)
);
newNode.style.position = 'absolute';
addGhostImage(event.dataTransfer, newNode, {
y: -10,
x: 30,
});
}
this._onDragStart.fire(event);
}),
dragHandler,
addDisposableListener(this._element, 'pointerdown', (event) => {
this._onPointDown.fire(event);
}),
this.dropTarget.onDrop((event) => {
this._onDropped.fire(event);
}),
this.droptarget
this.dropTarget
);
}
public setActive(isActive: boolean): void {
toggleClass(this.element, 'active-tab', isActive);
toggleClass(this.element, 'inactive-tab', !isActive);
toggleClass(this.element, 'dv-active-tab', isActive);
toggleClass(this.element, 'dv-inactive-tab', !isActive);
}
public setContent(part: ITabRenderer): void {

View File

@ -0,0 +1,19 @@
.dv-tabs-overflow-dropdown-default {
height: 100%;
color: var(--dv-activegroup-hiddenpanel-tab-color);
margin: var(--dv-tab-margin);
display: flex;
align-items: center;
flex-shrink: 0;
padding: 0.25rem 0.5rem;
cursor: pointer;
> span {
padding-left: 0.25rem;
}
> svg {
transform: rotate(90deg);
}
}

View File

@ -0,0 +1,25 @@
import { createChevronRightButton } from '../../../svg';
export type DropdownElement = {
element: HTMLElement;
update: (params: { tabs: number }) => void;
dispose?: () => void;
};
export function createDropdownElementHandle(): DropdownElement {
const el = document.createElement('div');
el.className = 'dv-tabs-overflow-dropdown-default';
const text = document.createElement('span');
text.textContent = ``;
const icon = createChevronRightButton();
el.appendChild(icon);
el.appendChild(text);
return {
element: el,
update: (params: { tabs: number }) => {
text.textContent = `${params.tabs}`;
},
};
}

View File

@ -0,0 +1,79 @@
.dv-tabs-container {
display: flex;
height: 100%;
overflow: auto;
scrollbar-width: thin; // firefox
&.dv-horizontal {
.dv-tab {
&:not(:first-child)::before {
content: ' ';
position: absolute;
top: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--dv-tab-divider-color);
width: 1px;
height: 100%;
}
}
}
&::-webkit-scrollbar {
height: 3px;
}
/* Track */
&::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: var(--dv-tabs-container-scrollbar-color);
}
}
.dv-scrollable {
> .dv-tabs-container {
overflow: hidden;
}
}
.dv-tab {
-webkit-user-drag: element;
outline: none;
padding: 0.25rem 0.5rem;
cursor: pointer;
position: relative;
box-sizing: border-box;
font-size: var(--dv-tab-font-size);
margin: var(--dv-tab-margin);
}
.dv-tabs-overflow-container {
flex-direction: column;
height: unset;
border: 1px solid var(--dv-tab-divider-color);
background-color: var(--dv-group-view-background-color);
.dv-tab {
&:not(:last-child) {
border-bottom: 1px solid var(--dv-tab-divider-color);
}
}
.dv-active-tab {
background-color: var(
--dv-activegroup-visiblepanel-tab-background-color
);
color: var(--dv-activegroup-visiblepanel-tab-color);
}
.dv-inactive-tab {
background-color: var(
--dv-activegroup-hiddenpanel-tab-background-color
);
color: var(--dv-activegroup-hiddenpanel-tab-color);
}
}

View File

@ -0,0 +1,301 @@
import { getPanelData } from '../../../dnd/dataTransfer';
import {
isChildEntirelyVisibleWithinParent,
OverflowObserver,
} from '../../../dom';
import { addDisposableListener, Emitter, Event } from '../../../events';
import {
CompositeDisposable,
Disposable,
IValueDisposable,
MutableDisposable,
} from '../../../lifecycle';
import { Scrollbar } from '../../../scrollbar';
import { DockviewComponent } from '../../dockviewComponent';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
import { Tab } from '../tab/tab';
import { TabDragEvent, TabDropIndexEvent } from './tabsContainer';
export class Tabs extends CompositeDisposable {
private readonly _element: HTMLElement;
private readonly _tabsList: HTMLElement;
private readonly _observerDisposable = new MutableDisposable();
private _tabs: IValueDisposable<Tab>[] = [];
private selectedIndex = -1;
private _showTabsOverflowControl = false;
private readonly _onTabDragStart = new Emitter<TabDragEvent>();
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
private readonly _onWillShowOverlay =
new Emitter<WillShowOverlayLocationEvent>();
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
this._onWillShowOverlay.event;
private readonly _onOverflowTabsChange = new Emitter<{
tabs: string[];
reset: boolean;
}>();
readonly onOverflowTabsChange = this._onOverflowTabsChange.event;
get showTabsOverflowControl(): boolean {
return this._showTabsOverflowControl;
}
set showTabsOverflowControl(value: boolean) {
if (this._showTabsOverflowControl == value) {
return;
}
this._showTabsOverflowControl = value;
if (value) {
const observer = new OverflowObserver(this._tabsList);
this._observerDisposable.value = new CompositeDisposable(
observer,
observer.onDidChange((event) => {
const hasOverflow = event.hasScrollX || event.hasScrollY;
this.toggleDropdown({ reset: !hasOverflow });
}),
addDisposableListener(this._tabsList, 'scroll', () => {
this.toggleDropdown({ reset: false });
})
);
}
}
get element(): HTMLElement {
return this._element;
}
get panels(): string[] {
return this._tabs.map((_) => _.value.panel.id);
}
get size(): number {
return this._tabs.length;
}
get tabs(): Tab[] {
return this._tabs.map((_) => _.value);
}
constructor(
private readonly group: DockviewGroupPanel,
private readonly accessor: DockviewComponent,
options: {
showTabsOverflowControl: boolean;
}
) {
super();
this._tabsList = document.createElement('div');
this._tabsList.className = 'dv-tabs-container dv-horizontal';
this.showTabsOverflowControl = options.showTabsOverflowControl;
if (accessor.options.scrollbars === 'native') {
this._element = this._tabsList;
} else {
const scrollbar = new Scrollbar(this._tabsList);
this._element = scrollbar.element;
this.addDisposables(scrollbar);
}
this.addDisposables(
this._onOverflowTabsChange,
this._observerDisposable,
this._onWillShowOverlay,
this._onDrop,
this._onTabDragStart,
addDisposableListener(this.element, 'pointerdown', (event) => {
if (event.defaultPrevented) {
return;
}
const isLeftClick = event.button === 0;
if (isLeftClick) {
this.accessor.doSetGroupActive(this.group);
}
}),
Disposable.from(() => {
for (const { value, disposable } of this._tabs) {
disposable.dispose();
value.dispose();
}
this._tabs = [];
})
);
}
indexOf(id: string): number {
return this._tabs.findIndex((tab) => tab.value.panel.id === id);
}
isActive(tab: Tab): boolean {
return (
this.selectedIndex > -1 &&
this._tabs[this.selectedIndex].value === tab
);
}
setActivePanel(panel: IDockviewPanel): void {
let runningWidth = 0;
for (const tab of this._tabs) {
const isActivePanel = panel.id === tab.value.panel.id;
tab.value.setActive(isActivePanel);
if (isActivePanel) {
const element = tab.value.element;
const parentElement = element.parentElement!;
if (
runningWidth < parentElement.scrollLeft ||
runningWidth + element.clientWidth >
parentElement.scrollLeft + parentElement.clientWidth
) {
parentElement.scrollLeft = runningWidth;
}
}
runningWidth += tab.value.element.clientWidth;
}
}
openPanel(panel: IDockviewPanel, index: number = this._tabs.length): void {
if (this._tabs.find((tab) => tab.value.panel.id === panel.id)) {
return;
}
const tab = new Tab(panel, this.accessor, this.group);
tab.setContent(panel.view.tab);
const disposable = new CompositeDisposable(
tab.onDragStart((event) => {
this._onTabDragStart.fire({ nativeEvent: event, panel });
}),
tab.onPointerDown((event) => {
if (event.defaultPrevented) {
return;
}
const isFloatingGroupsEnabled =
!this.accessor.options.disableFloatingGroups;
const isFloatingWithOnePanel =
this.group.api.location.type === 'floating' &&
this.size === 1;
if (
isFloatingGroupsEnabled &&
!isFloatingWithOnePanel &&
event.shiftKey
) {
event.preventDefault();
const panel = this.accessor.getGroupPanel(tab.panel.id);
const { top, left } = tab.element.getBoundingClientRect();
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(panel as DockviewPanel, {
x: left - rootLeft,
y: top - rootTop,
inDragMode: true,
});
return;
}
switch (event.button) {
case 0: // left click or touch
if (this.group.activePanel !== panel) {
this.group.model.openPanel(panel);
}
break;
}
}),
tab.onDrop((event) => {
this._onDrop.fire({
event: event.nativeEvent,
index: this._tabs.findIndex((x) => x.value === tab),
});
}),
tab.onWillShowOverlay((event) => {
this._onWillShowOverlay.fire(
new WillShowOverlayLocationEvent(event, {
kind: 'tab',
panel: this.group.activePanel,
api: this.accessor.api,
group: this.group,
getData: getPanelData,
})
);
})
);
const value: IValueDisposable<Tab> = { value: tab, disposable };
this.addTab(value, index);
}
delete(id: string): void {
const index = this.indexOf(id);
const tabToRemove = this._tabs.splice(index, 1)[0];
const { value, disposable } = tabToRemove;
disposable.dispose();
value.dispose();
value.element.remove();
}
private addTab(
tab: IValueDisposable<Tab>,
index: number = this._tabs.length
): void {
if (index < 0 || index > this._tabs.length) {
throw new Error('invalid location');
}
this._tabsList.insertBefore(
tab.value.element,
this._tabsList.children[index]
);
this._tabs = [
...this._tabs.slice(0, index),
tab,
...this._tabs.slice(index),
];
if (this.selectedIndex < 0) {
this.selectedIndex = index;
}
}
private toggleDropdown(options: { reset: boolean }): void {
const tabs = options.reset
? []
: this._tabs
.filter(
(tab) =>
!isChildEntirelyVisibleWithinParent(
tab.value.element,
this._tabsList
)
)
.map((x) => x.value.panel.id);
this._onOverflowTabsChange.fire({ tabs, reset: options.reset });
}
}

View File

@ -1,4 +1,4 @@
.tabs-and-actions-container {
.dv-tabs-and-actions-container {
display: flex;
background-color: var(--dv-tabs-and-actions-container-background-color);
flex-shrink: 0;
@ -6,70 +6,32 @@
height: var(--dv-tabs-and-actions-container-height);
font-size: var(--dv-tabs-and-actions-container-font-size);
&.hidden {
display: none;
}
&.dv-single-tab.dv-full-width-single-tab {
.tabs-container {
flex-grow: 1;
.tab {
.dv-scrollable {
flex-grow: 1;
}
}
}
.void-container {
flex-grow: 0;
}
.dv-tabs-container {
flex-grow: 1;
.dv-tab {
flex-grow: 1;
padding: 0px;
}
}
.dv-void-container {
flex-grow: 0;
}
}
.void-container {
.dv-void-container {
display: flex;
flex-grow: 1;
cursor: grab;
}
.tabs-container {
.dv-right-actions-container {
display: flex;
overflow-x: overlay;
overflow-y: hidden;
scrollbar-width: thin; // firefox
&::-webkit-scrollbar {
height: 3px;
}
/* Track */
&::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: var(--dv-tabs-container-scrollbar-color);
}
.tab {
-webkit-user-drag: element;
outline: none;
min-width: 75px;
cursor: pointer;
position: relative;
box-sizing: border-box;
&:not(:first-child)::before {
content: ' ';
position: absolute;
top: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--dv-tab-divider-color);
width: 1px;
height: 100%;
}
}
}
}

View File

@ -1,36 +1,58 @@
import {
IDisposable,
CompositeDisposable,
IValueDisposable,
Disposable,
MutableDisposable,
} from '../../../lifecycle';
import { addDisposableListener, Emitter, Event } from '../../../events';
import { ITab, Tab } from '../tab/tab';
import { DockviewComponent } from '../../dockviewComponent';
import { Tab } from '../tab/tab';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { VoidContainer } from './voidContainer';
import { toggleClass } from '../../../dom';
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
import { findRelativeZIndexParent, toggleClass } from '../../../dom';
import { IDockviewPanel } from '../../dockviewPanel';
import { DockviewComponent } from '../../dockviewComponent';
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
import { getPanelData } from '../../../dnd/dataTransfer';
import { Tabs } from './tabs';
import {
createDropdownElementHandle,
DropdownElement,
} from './tabOverflowControl';
export interface TabDropIndexEvent {
readonly event: DragEvent;
readonly index: number;
}
export interface TabDragEvent {
readonly nativeEvent: DragEvent;
readonly panel: IDockviewPanel;
}
export interface GroupDragEvent {
readonly nativeEvent: DragEvent;
readonly group: DockviewGroupPanel;
}
export interface ITabsContainer extends IDisposable {
readonly element: HTMLElement;
readonly panels: string[];
readonly size: number;
delete: (id: string) => void;
indexOf: (id: string) => number;
onDrop: Event<TabDropIndexEvent>;
setActive: (isGroupActive: boolean) => void;
setActivePanel: (panel: IDockviewPanel) => void;
isActive: (tab: ITab) => boolean;
closePanel: (panel: IDockviewPanel) => void;
openPanel: (panel: IDockviewPanel, index?: number) => void;
readonly onDrop: Event<TabDropIndexEvent>;
readonly onTabDragStart: Event<TabDragEvent>;
readonly onGroupDragStart: Event<GroupDragEvent>;
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent>;
hidden: boolean;
delete(id: string): void;
indexOf(id: string): number;
setActive(isGroupActive: boolean): void;
setActivePanel(panel: IDockviewPanel): void;
isActive(tab: Tab): boolean;
closePanel(panel: IDockviewPanel): void;
openPanel(panel: IDockviewPanel, index?: number): void;
setRightActionsElement(element: HTMLElement | undefined): void;
setLeftActionsElement(element: HTMLElement | undefined): void;
hidden: boolean;
setPrefixActionsElement(element: HTMLElement | undefined): void;
show(): void;
hide(): void;
}
@ -40,27 +62,44 @@ export class TabsContainer
implements ITabsContainer
{
private readonly _element: HTMLElement;
private readonly tabContainer: HTMLElement;
private readonly tabs: Tabs;
private readonly rightActionsContainer: HTMLElement;
private readonly leftActionsContainer: HTMLElement;
private readonly preActionsContainer: HTMLElement;
private readonly voidContainer: VoidContainer;
private tabs: IValueDisposable<ITab>[] = [];
private selectedIndex = -1;
private rightActions: HTMLElement | undefined;
private leftActions: HTMLElement | undefined;
private preActions: HTMLElement | undefined;
private _hidden = false;
private dropdownPart: DropdownElement | null = null;
private _overflowTabs: string[] = [];
private readonly _dropdownDisposable = new MutableDisposable();
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
get onTabDragStart(): Event<TabDragEvent> {
return this.tabs.onTabDragStart;
}
private readonly _onGroupDragStart = new Emitter<GroupDragEvent>();
readonly onGroupDragStart: Event<GroupDragEvent> =
this._onGroupDragStart.event;
private readonly _onWillShowOverlay =
new Emitter<WillShowOverlayLocationEvent>();
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
this._onWillShowOverlay.event;
get panels(): string[] {
return this.tabs.map((_) => _.value.panelId);
return this.tabs.panels;
}
get size(): number {
return this.tabs.length;
return this.tabs.size;
}
get hidden(): boolean {
@ -72,6 +111,118 @@ export class TabsContainer
this.element.style.display = value ? 'none' : '';
}
get element(): HTMLElement {
return this._element;
}
constructor(
private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanel
) {
super();
this._element = document.createElement('div');
this._element.className = 'dv-tabs-and-actions-container';
toggleClass(
this._element,
'dv-full-width-single-tab',
this.accessor.options.singleTabMode === 'fullwidth'
);
this.rightActionsContainer = document.createElement('div');
this.rightActionsContainer.className = 'dv-right-actions-container';
this.leftActionsContainer = document.createElement('div');
this.leftActionsContainer.className = 'dv-left-actions-container';
this.preActionsContainer = document.createElement('div');
this.preActionsContainer.className = 'dv-pre-actions-container';
this.tabs = new Tabs(group, accessor, {
showTabsOverflowControl: !accessor.options.disableTabsOverflowList,
});
this.voidContainer = new VoidContainer(this.accessor, this.group);
this._element.appendChild(this.preActionsContainer);
this._element.appendChild(this.tabs.element);
this._element.appendChild(this.leftActionsContainer);
this._element.appendChild(this.voidContainer.element);
this._element.appendChild(this.rightActionsContainer);
this.addDisposables(
this.tabs.onDrop((e) => this._onDrop.fire(e)),
this.tabs.onWillShowOverlay((e) => this._onWillShowOverlay.fire(e)),
accessor.onDidOptionsChange(() => {
this.tabs.showTabsOverflowControl =
!accessor.options.disableTabsOverflowList;
}),
this.tabs.onOverflowTabsChange((event) => {
this.toggleDropdown(event);
}),
this.tabs,
this._onWillShowOverlay,
this._onDrop,
this._onGroupDragStart,
this.voidContainer,
this.voidContainer.onDragStart((event) => {
this._onGroupDragStart.fire({
nativeEvent: event,
group: this.group,
});
}),
this.voidContainer.onDrop((event) => {
this._onDrop.fire({
event: event.nativeEvent,
index: this.tabs.size,
});
}),
this.voidContainer.onWillShowOverlay((event) => {
this._onWillShowOverlay.fire(
new WillShowOverlayLocationEvent(event, {
kind: 'header_space',
panel: this.group.activePanel,
api: this.accessor.api,
group: this.group,
getData: getPanelData,
})
);
}),
addDisposableListener(
this.voidContainer.element,
'pointerdown',
(event) => {
if (event.defaultPrevented) {
return;
}
const isFloatingGroupsEnabled =
!this.accessor.options.disableFloatingGroups;
if (
isFloatingGroupsEnabled &&
event.shiftKey &&
this.group.api.location.type !== 'floating'
) {
event.preventDefault();
const { top, left } =
this.element.getBoundingClientRect();
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(this.group, {
x: left - rootLeft + 20,
y: top - rootTop + 20,
inDragMode: true,
});
}
}
)
);
}
show(): void {
if (!this.hidden) {
this.element.style.display = '';
@ -110,259 +261,143 @@ export class TabsContainer
}
}
get element(): HTMLElement {
return this._element;
setPrefixActionsElement(element: HTMLElement | undefined): void {
if (this.preActions === element) {
return;
}
if (this.preActions) {
this.preActions.remove();
this.preActions = undefined;
}
if (element) {
this.preActionsContainer.appendChild(element);
this.preActions = element;
}
}
public isActive(tab: ITab): boolean {
return (
this.selectedIndex > -1 &&
this.tabs[this.selectedIndex].value === tab
);
isActive(tab: Tab): boolean {
return this.tabs.isActive(tab);
}
public indexOf(id: string): number {
return this.tabs.findIndex((tab) => tab.value.panelId === id);
indexOf(id: string): number {
return this.tabs.indexOf(id);
}
constructor(
private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanel
) {
super();
this.addDisposables(this._onDrop);
this._element = document.createElement('div');
this._element.className = 'tabs-and-actions-container';
toggleClass(
this._element,
'dv-full-width-single-tab',
this.accessor.options.singleTabMode === 'fullwidth'
);
this.addDisposables(
this.accessor.onDidAddPanel((e) => {
if (e.api.group === this.group) {
toggleClass(
this._element,
'dv-single-tab',
this.size === 1
);
}
}),
this.accessor.onDidRemovePanel((e) => {
if (e.api.group === this.group) {
toggleClass(
this._element,
'dv-single-tab',
this.size === 1
);
}
})
);
this.rightActionsContainer = document.createElement('div');
this.rightActionsContainer.className = 'right-actions-container';
this.leftActionsContainer = document.createElement('div');
this.leftActionsContainer.className = 'left-actions-container';
this.tabContainer = document.createElement('div');
this.tabContainer.className = 'tabs-container';
this.voidContainer = new VoidContainer(this.accessor, this.group);
this._element.appendChild(this.tabContainer);
this._element.appendChild(this.leftActionsContainer);
this._element.appendChild(this.voidContainer.element);
this._element.appendChild(this.rightActionsContainer);
this.addDisposables(
this.voidContainer,
this.voidContainer.onDrop((event) => {
this._onDrop.fire({
event: event.nativeEvent,
index: this.tabs.length,
});
}),
addDisposableListener(
this.voidContainer.element,
'mousedown',
(event) => {
const isFloatingGroupsEnabled =
!this.accessor.options.disableFloatingGroups;
if (
isFloatingGroupsEnabled &&
event.shiftKey &&
!this.group.api.isFloating
) {
event.preventDefault();
const { top, left } =
this.element.getBoundingClientRect();
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(
this.group,
{
x: left - rootLeft + 20,
y: top - rootTop + 20,
},
{ inDragMode: true }
);
}
}
),
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
if (event.defaultPrevented) {
return;
}
const isLeftClick = event.button === 0;
if (isLeftClick) {
this.accessor.doSetGroupActive(this.group);
}
})
);
}
public setActive(_isGroupActive: boolean) {
setActive(_isGroupActive: boolean) {
// noop
}
private addTab(
tab: IValueDisposable<ITab>,
index: number = this.tabs.length
): void {
if (index < 0 || index > this.tabs.length) {
throw new Error('invalid location');
}
this.tabContainer.insertBefore(
tab.value.element,
this.tabContainer.children[index]
);
this.tabs = [
...this.tabs.slice(0, index),
tab,
...this.tabs.slice(index),
];
if (this.selectedIndex < 0) {
this.selectedIndex = index;
}
delete(id: string): void {
this.tabs.delete(id);
this.updateClassnames();
}
public delete(id: string): void {
const index = this.tabs.findIndex((tab) => tab.value.panelId === id);
const tabToRemove = this.tabs.splice(index, 1)[0];
const { value, disposable } = tabToRemove;
disposable.dispose();
value.dispose();
value.element.remove();
setActivePanel(panel: IDockviewPanel): void {
this.tabs.setActivePanel(panel);
}
public setActivePanel(panel: IDockviewPanel): void {
this.tabs.forEach((tab) => {
const isActivePanel = panel.id === tab.value.panelId;
tab.value.setActive(isActivePanel);
});
openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void {
this.tabs.openPanel(panel, index);
this.updateClassnames();
}
public openPanel(
panel: IDockviewPanel,
index: number = this.tabs.length
): void {
if (this.tabs.find((tab) => tab.value.panelId === panel.id)) {
return;
}
const tabToAdd = new Tab(panel.id, this.accessor, this.group);
if (!panel.view?.tab) {
throw new Error('invalid header component');
}
tabToAdd.setContent(panel.view.tab);
const disposable = CompositeDisposable.from(
tabToAdd.onChanged((event) => {
const isFloatingGroupsEnabled =
!this.accessor.options.disableFloatingGroups;
const isFloatingWithOnePanel =
this.group.api.isFloating && this.size === 1;
if (
isFloatingGroupsEnabled &&
!isFloatingWithOnePanel &&
event.shiftKey
) {
event.preventDefault();
const panel = this.accessor.getGroupPanel(tabToAdd.panelId);
const { top, left } =
tabToAdd.element.getBoundingClientRect();
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(
panel as DockviewPanel,
{
x: left - rootLeft,
y: top - rootTop,
},
{ inDragMode: true }
);
return;
}
const alreadyFocused =
panel.id === this.group.model.activePanel?.id &&
this.group.model.isContentFocused;
const isLeftClick = event.button === 0;
if (!isLeftClick || event.defaultPrevented) {
return;
}
this.group.model.openPanel(panel, {
skipFocus: alreadyFocused,
});
}),
tabToAdd.onDrop((event) => {
this._onDrop.fire({
event: event.nativeEvent,
index: this.tabs.findIndex((x) => x.value === tabToAdd),
});
})
);
const value: IValueDisposable<ITab> = { value: tabToAdd, disposable };
this.addTab(value, index);
}
public closePanel(panel: IDockviewPanel): void {
closePanel(panel: IDockviewPanel): void {
this.delete(panel.id);
}
public dispose(): void {
super.dispose();
private updateClassnames(): void {
toggleClass(this._element, 'dv-single-tab', this.size === 1);
}
for (const { value, disposable } of this.tabs) {
disposable.dispose();
value.dispose();
private toggleDropdown(options: { tabs: string[]; reset: boolean }): void {
const tabs = options.reset ? [] : options.tabs;
this._overflowTabs = tabs;
if (this._overflowTabs.length > 0 && this.dropdownPart) {
this.dropdownPart.update({ tabs: tabs.length });
return;
}
this.tabs = [];
if (this._overflowTabs.length === 0) {
this._dropdownDisposable.dispose();
return;
}
const root = document.createElement('div');
root.className = 'dv-tabs-overflow-dropdown-root';
const part = createDropdownElementHandle();
part.update({ tabs: tabs.length });
this.dropdownPart = part;
root.appendChild(part.element);
this.rightActionsContainer.prepend(root);
this._dropdownDisposable.value = new CompositeDisposable(
Disposable.from(() => {
root.remove();
this.dropdownPart?.dispose?.();
this.dropdownPart = null;
}),
addDisposableListener(
root,
'pointerdown',
(event) => {
event.preventDefault();
},
{ capture: true }
),
addDisposableListener(root, 'click', (event) => {
const el = document.createElement('div');
el.style.overflow = 'auto';
el.className = 'dv-tabs-overflow-container';
for (const tab of this.tabs.tabs.filter((tab) =>
this._overflowTabs.includes(tab.panel.id)
)) {
const panelObject = this.group.panels.find(
(panel) => panel === tab.panel
)!;
const tabComponent =
panelObject.view.createTabRenderer('headerOverflow');
const child = tabComponent.element;
const wrapper = document.createElement('div');
toggleClass(wrapper, 'dv-tab', true);
toggleClass(
wrapper,
'dv-active-tab',
panelObject.api.isActive
);
toggleClass(
wrapper,
'dv-inactive-tab',
!panelObject.api.isActive
);
wrapper.addEventListener('pointerdown', () => {
this.accessor.popupService.close();
tab.element.scrollIntoView();
tab.panel.api.setActive();
});
wrapper.appendChild(child);
el.appendChild(wrapper);
}
const relativeParent = findRelativeZIndexParent(root);
this.accessor.popupService.openPopover(el, {
x: event.clientX,
y: event.clientY,
zIndex: relativeParent?.style.zIndex
? `calc(${relativeParent.style.zIndex} * 2)`
: undefined,
});
})
);
}
}

View File

@ -1,20 +1,28 @@
import { last } from '../../../array';
import { getPanelData } from '../../../dnd/dataTransfer';
import { Droptarget, DroptargetEvent } from '../../../dnd/droptarget';
import {
Droptarget,
DroptargetEvent,
WillShowOverlayEvent,
} from '../../../dnd/droptarget';
import { GroupDragHandler } from '../../../dnd/groupDragHandler';
import { DockviewComponent } from '../../dockviewComponent';
import { addDisposableListener, Emitter, Event } from '../../../events';
import { CompositeDisposable } from '../../../lifecycle';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { DockviewDropTargets } from '../../types';
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
export class VoidContainer extends CompositeDisposable {
private readonly _element: HTMLElement;
private readonly voidDropTarget: Droptarget;
private readonly dropTraget: Droptarget;
private readonly _onDrop = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
private readonly _onDragStart = new Emitter<DragEvent>();
readonly onDragStart = this._onDragStart.event;
readonly onWillShowOverlay: Event<WillShowOverlayEvent>;
get element(): HTMLElement {
return this._element;
}
@ -27,51 +35,48 @@ export class VoidContainer extends CompositeDisposable {
this._element = document.createElement('div');
this._element.className = 'void-container';
this._element.tabIndex = 0;
this._element.className = 'dv-void-container';
this._element.draggable = true;
this.addDisposables(
this._onDrop,
addDisposableListener(this._element, 'click', () => {
this._onDragStart,
addDisposableListener(this._element, 'pointerdown', () => {
this.accessor.doSetGroupActive(this.group);
})
);
const handler = new GroupDragHandler(this._element, accessor.id, group);
const handler = new GroupDragHandler(this._element, accessor, group);
this.voidDropTarget = new Droptarget(this._element, {
this.dropTraget = new Droptarget(this._element, {
acceptedTargetZones: ['center'],
canDisplayOverlay: (event, position) => {
const data = getPanelData();
if (data && this.accessor.id === data.viewId) {
if (
data.panelId === null &&
data.groupId === this.group.id
) {
// don't allow group move to drop on self
return false;
}
// don't show the overlay if the tab being dragged is the last panel of this group
return last(this.group.panels)?.id !== data.panelId;
return true;
}
return group.model.canDisplayOverlay(
event,
position,
DockviewDropTargets.Panel
'header_space'
);
},
getOverrideTarget: () => group.model.dropTargetContainer?.model,
});
this.onWillShowOverlay = this.dropTraget.onWillShowOverlay;
this.addDisposables(
handler,
this.voidDropTarget.onDrop((event) => {
handler.onDragStart((event) => {
this._onDragStart.fire(event);
}),
this.dropTraget.onDrop((event) => {
this._onDrop.fire(event);
}),
this.voidDropTarget
this.dropTraget
);
}
}

View File

@ -1,45 +1,4 @@
.watermark {
.dv-watermark {
display: flex;
width: 100%;
&.has-actions {
.watermark-title {
.actions-container {
display: none;
}
}
}
.watermark-title {
height: 35px;
width: 100%;
display: flex;
}
.watermark-content {
flex-grow: 1;
}
.actions-container {
display: flex;
align-items: center;
padding: 0px 8px;
// padding: 0px;
// margin: 0px;
// justify-content: flex-end;
.close-action {
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
cursor: pointer;
color: var(--dv-activegroup-hiddenpanel-tab-color);
&:hover {
border-radius: 2px;
background-color: var(--dv-icon-hover-background-color);
}
}
}
height: 100%;
}

View File

@ -2,21 +2,13 @@ import {
IWatermarkRenderer,
WatermarkRendererInitParameters,
} from '../../types';
import { addDisposableListener } from '../../../events';
import { toggleClass } from '../../../dom';
import { CompositeDisposable } from '../../../lifecycle';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { PanelUpdateEvent } from '../../../panel/types';
import { createCloseButton } from '../../../svg';
import { DockviewApi } from '../../../api/component.api';
export class Watermark
extends CompositeDisposable
implements IWatermarkRenderer
{
private _element: HTMLElement;
private _group: DockviewGroupPanel | undefined;
private _api: DockviewApi | undefined;
private readonly _element: HTMLElement;
get element(): HTMLElement {
return this._element;
@ -25,70 +17,10 @@ export class Watermark
constructor() {
super();
this._element = document.createElement('div');
this._element.className = 'watermark';
const title = document.createElement('div');
title.className = 'watermark-title';
const emptySpace = document.createElement('span');
emptySpace.style.flexGrow = '1';
const content = document.createElement('div');
content.className = 'watermark-content';
this._element.appendChild(title);
this._element.appendChild(content);
const actionsContainer = document.createElement('div');
actionsContainer.className = 'actions-container';
const closeAnchor = document.createElement('div');
closeAnchor.className = 'close-action';
closeAnchor.appendChild(createCloseButton());
actionsContainer.appendChild(closeAnchor);
title.appendChild(emptySpace);
title.appendChild(actionsContainer);
this.addDisposables(
addDisposableListener(closeAnchor, 'click', (ev) => {
ev.preventDefault();
if (this._group) {
this._api?.removeGroup(this._group);
}
})
);
}
update(_event: PanelUpdateEvent): void {
// noop
}
focus(): void {
// noop
}
layout(_width: number, _height: number): void {
// noop
this._element.className = 'dv-watermark';
}
init(_params: WatermarkRendererInitParameters): void {
this._api = _params.containerApi;
this.render();
}
updateParentGroup(group: DockviewGroupPanel, _visible: boolean): void {
this._group = group;
this.render();
}
dispose(): void {
super.dispose();
}
private render(): void {
const isOneGroup = !!(this._api && this._api.size <= 1);
toggleClass(this.element, 'has-actions', isOneGroup);
// noop
}
}

View File

@ -21,7 +21,7 @@ interface LegacyState extends GroupviewPanelState {
}
export class DefaultDockviewDeserialzier implements IPanelDeserializer {
constructor(private readonly layout: DockviewComponent) {}
constructor(private readonly accessor: DockviewComponent) {}
public fromJSON(
panelData: GroupviewPanelState,
@ -35,13 +35,13 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
const contentComponent = viewData
? viewData.content.id
: panelData.contentComponent || 'unknown';
: panelData.contentComponent ?? 'unknown';
const tabComponent = viewData
? viewData.tab?.id
: panelData.tabComponent;
const view = new DockviewPanelModel(
this.layout,
this.accessor,
panelId,
contentComponent,
tabComponent
@ -49,15 +49,24 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
const panel = new DockviewPanel(
panelId,
this.layout,
new DockviewApi(this.layout),
contentComponent,
tabComponent,
this.accessor,
new DockviewApi(this.accessor),
group,
view
view,
{
renderer: panelData.renderer,
minimumWidth: panelData.minimumWidth,
minimumHeight: panelData.minimumHeight,
maximumWidth: panelData.maximumWidth,
maximumHeight: panelData.maximumHeight,
}
);
panel.init({
title: title || panelId,
params: params || {},
title: title ?? panelId,
params: params ?? {},
});
return panel;

View File

@ -10,38 +10,46 @@
width: 100%;
z-index: 1;
}
.dv-overlay-render-container {
position: relative;
}
}
.groupview {
&.active-group {
> .tabs-and-actions-container > .tabs-container > .tab {
&.active-tab {
background-color: var(
--dv-activegroup-visiblepanel-tab-background-color
);
color: var(--dv-activegroup-visiblepanel-tab-color);
}
&.inactive-tab {
background-color: var(
--dv-activegroup-hiddenpanel-tab-background-color
);
color: var(--dv-activegroup-hiddenpanel-tab-color);
.dv-groupview {
&.dv-active-group {
> .dv-tabs-and-actions-container {
.dv-tabs-container > .dv-tab {
&.dv-active-tab {
background-color: var(
--dv-activegroup-visiblepanel-tab-background-color
);
color: var(--dv-activegroup-visiblepanel-tab-color);
}
&.dv-inactive-tab {
background-color: var(
--dv-activegroup-hiddenpanel-tab-background-color
);
color: var(--dv-activegroup-hiddenpanel-tab-color);
}
}
}
}
&.inactive-group {
> .tabs-and-actions-container > .tabs-container > .tab {
&.active-tab {
background-color: var(
--dv-inactivegroup-visiblepanel-tab-background-color
);
color: var(--dv-inactivegroup-visiblepanel-tab-color);
}
&.inactive-tab {
background-color: var(
--dv-inactivegroup-hiddenpanel-tab-background-color
);
color: var(--dv-inactivegroup-hiddenpanel-tab-color);
&.dv-inactive-group {
> .dv-tabs-and-actions-container {
.dv-tabs-container > .dv-tab {
&.dv-active-tab {
background-color: var(
--dv-inactivegroup-visiblepanel-tab-background-color
);
color: var(--dv-inactivegroup-visiblepanel-tab-color);
}
&.dv-inactive-tab {
background-color: var(
--dv-inactivegroup-hiddenpanel-tab-background-color
);
color: var(--dv-inactivegroup-hiddenpanel-tab-color);
}
}
}
}
@ -51,7 +59,7 @@
* when a tab is dragged we lose the above stylings because they are conditional on parent elements
* therefore we also set some stylings for the dragging event
**/
.tab {
.dv-tab {
&.dv-tab-dragging {
background-color: var(
--dv-activegroup-visiblepanel-tab-background-color

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More