mirror of
https://github.com/mathuo/dockview
synced 2025-05-04 02:28:26 +00:00
Compare commits
756 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1e63da807b | ||
|
68d7947dea | ||
|
093a034738 | ||
|
f9ca5b99d5 | ||
|
420466398d | ||
|
20b5c844f3 | ||
|
6d2f2f1012 | ||
|
3ddea55834 | ||
|
2f334c63c1 | ||
|
8334f9795c | ||
|
7ea3154946 | ||
|
1ef3dc06ce | ||
|
1ff74f2e65 | ||
|
a82aac09bc | ||
|
d1239a5ee5 | ||
|
22deb851dc | ||
|
87f257df1e | ||
|
a92a056981 | ||
|
c3e203f420 | ||
|
8bdd15161c | ||
|
8a3a7e5063 | ||
|
65d95f1271 | ||
|
3ec6627013 | ||
|
c0227d620c | ||
|
aad168da84 | ||
|
dfee4111a2 | ||
|
712f3f860d | ||
|
f545a0a5c7 | ||
|
e3d39fd2ed | ||
|
dca0eb0df7 | ||
|
8188fd9429 | ||
|
54a51ed10b | ||
|
1a11a4ba2c | ||
|
b6d83df1aa | ||
|
6444e4cb7d | ||
|
ca7bd0a86d | ||
|
dc828b7658 | ||
|
6e02e59fec | ||
|
f79bbe0a6a | ||
|
12448e006f | ||
|
5e380affe7 | ||
|
a44fc01f34 | ||
|
8e03e584a7 | ||
|
1ddcaefe80 | ||
|
d086dfd081 | ||
|
b231367e44 | ||
|
bba22fedd4 | ||
|
3ebcb8eaef | ||
|
39dd5f0759 | ||
|
e4f07dfbda | ||
|
d30263af67 | ||
|
54ae457ccd | ||
|
416039bc99 | ||
|
e8fdabba08 | ||
|
9373802115 | ||
|
759dcc7b05 | ||
|
f8faca731d | ||
|
d168356344 | ||
|
64e11a880c | ||
|
a306742508 | ||
|
bffd2dea89 | ||
|
ac92d59d0a | ||
|
21d94b654b | ||
|
1030bdb778 | ||
|
082b811d20 | ||
|
4eff83e9a0 | ||
|
090f2d26f8 | ||
|
e34fb43913 | ||
|
c0119d65c0 | ||
|
d3d57b62b2 | ||
|
96d6947aa6 | ||
|
5fb13dd8a2 | ||
|
cfe37766a9 | ||
|
7a6b2cb26d | ||
|
6511b8d936 | ||
|
5754ddc3f4 | ||
|
eec57c1cde | ||
|
3530c9896e | ||
|
d811ca6554 | ||
|
ab965ca726 | ||
|
b13c841416 | ||
|
773bf5a53c | ||
|
837fabac86 | ||
|
21caabce60 | ||
|
14b918b7ae | ||
|
f98be640ce | ||
|
cd1aa81d93 | ||
|
490fc05baf | ||
|
16373429a8 | ||
|
59fba25ade | ||
|
cdd0183fa9 | ||
|
98cbe377c2 | ||
|
d8da2f29c3 | ||
|
8ed0fb5586 | ||
|
a56f01215b | ||
|
e71d8b1d09 | ||
|
71b99541b3 | ||
|
0e3afcf83f | ||
|
1a85444b81 | ||
|
19a22c49c3 | ||
|
06f02ba411 | ||
|
0e9c648fa9 | ||
|
bb93c9e4c8 | ||
|
c6dcef537f | ||
|
3e409c4265 | ||
|
ca09ae537d | ||
|
e9985df262 | ||
|
dc6bfc67e3 | ||
|
5ca5ffac8d | ||
|
723272d6de | ||
|
6421a72653 | ||
|
0c1b8ce4d7 | ||
|
f40532f272 | ||
|
6b75b5093c | ||
|
c0cbe89835 | ||
|
b46a473cdf | ||
|
239c99ffe5 | ||
|
6678ae24e0 | ||
|
680eaca96c | ||
|
3d23a2d352 | ||
|
c7c1ae9238 | ||
|
c122bfd310 | ||
|
1a3c6ea7db | ||
|
c6865c691c | ||
|
872ec7cba9 | ||
|
2a3647db28 | ||
|
2f9af48146 | ||
|
04e4ea1a70 | ||
|
2d8d38b466 | ||
|
10fd5778dc | ||
|
cd91f4a4d3 | ||
|
148a05a201 | ||
|
8359596be0 | ||
|
cb45889aca | ||
|
5e116c80b4 | ||
|
20eb30ed1c | ||
|
5173922c69 | ||
|
0ca56eaa8a | ||
|
c14b66e5e5 | ||
|
cc5037d208 | ||
|
a53665adc5 | ||
|
d799b5bc89 | ||
|
fade057412 | ||
|
f6e7e4e390 | ||
|
d33babd522 | ||
|
7df9c510bd | ||
|
3479828237 | ||
|
b3ce3e0366 | ||
|
32fa1511d8 | ||
|
e1304ce694 | ||
|
bdf81fd5b5 | ||
|
cd7984cb2b | ||
|
491872ea75 | ||
|
296b49180e | ||
|
487d02bd90 | ||
|
4d01d4e0af | ||
|
8650de90aa | ||
|
e45d9e48e8 | ||
|
da750e89d7 | ||
|
81a904ccbc | ||
|
6016362c5c | ||
|
7fe2e43d43 | ||
|
56d4dc7c74 | ||
|
6fbe2b4233 | ||
|
f749372eb2 | ||
|
c216d70354 | ||
|
0d32d285d2 | ||
|
5d868e63ce | ||
|
7515b9b05d | ||
|
703418e27f | ||
|
53cdf42d84 | ||
|
140a61f401 | ||
|
ff0bf75632 | ||
|
dd918294d1 | ||
|
684b0593e7 | ||
|
13c4a65e18 | ||
|
755135c5ad | ||
|
15bf4bbd1f | ||
|
429177436e | ||
|
cf6891368e | ||
|
e2302372b1 | ||
|
f13f009155 | ||
|
c5025424c7 | ||
|
a66fdae8a3 | ||
|
c777ff463d | ||
|
04327b4613 | ||
|
a02241d1a7 | ||
|
615886e666 | ||
|
1b921864fc | ||
|
1ce96c8ab8 | ||
|
d651c2213e | ||
|
1d61a47e7c | ||
|
aeb0e38249 | ||
|
df3269d56e | ||
|
0233e8fa42 | ||
|
2bcd62412d | ||
|
32e759ef75 | ||
|
ede5a58ec9 | ||
|
56bdec846c | ||
|
25489bf48e | ||
|
9564319e43 | ||
|
461c30dc5b | ||
|
be628a5683 | ||
|
19f027a0db | ||
|
e46a586044 | ||
|
2f4150013b | ||
|
24cc974a68 | ||
|
a00875aae0 | ||
|
02d0ad8634 | ||
|
93b29e77c4 | ||
|
df0d12128b | ||
|
986132c602 | ||
|
fe39c475a2 | ||
|
c9e90168a4 | ||
|
1a9ee8c34e | ||
|
8a05aede4c | ||
|
83b01a9a66 | ||
|
7ebbd437a1 | ||
|
d8ae04e274 | ||
|
3f5c9743ad | ||
|
3d1fa25677 | ||
|
108bc49401 | ||
|
291e76afdb | ||
|
2dc4f81b20 | ||
|
ca6f46ac8e | ||
|
dad9eba474 | ||
|
adaeb16b98 | ||
|
ee7cf637bb | ||
|
e70755e4fc | ||
|
112dc2e2a9 | ||
|
5cf3cfb616 | ||
|
f6f248c63a | ||
|
4bf3fc4820 | ||
|
40c1a26ead | ||
|
3f3ed52007 | ||
|
b812cf9117 | ||
|
210ff38cfa | ||
|
3ffb5034c6 | ||
|
2657c79620 | ||
|
cd4cd0f0ce | ||
|
408db93917 | ||
|
f557e9080a | ||
|
e0744531ca | ||
|
3c680b251f | ||
|
c086cabd39 | ||
|
a202a49182 | ||
|
efd77413da | ||
|
0097da7fde | ||
|
8c7369ccf0 | ||
|
8015860bde | ||
|
14382aa0fd | ||
|
7ddb63383f | ||
|
ac56a1f250 | ||
|
12bb6f6d20 | ||
|
bc455265cd | ||
|
6881daa593 | ||
|
3c90e9d196 | ||
|
c55257ac56 | ||
|
1b7f29e800 | ||
|
693a3cd6ef | ||
|
d1d8c8083e | ||
|
fd29ea570f | ||
|
1acdd969a0 | ||
|
aeda5f4ca1 | ||
|
5df4c5cce1 | ||
|
62fee4e7ec | ||
|
b24470fa42 | ||
|
0c602afab5 | ||
|
ab5a11f13b | ||
|
fc7b9fb2e2 | ||
|
d519c239e3 | ||
|
2b36844110 | ||
|
e2e91834ff | ||
|
72f457ab9d | ||
|
1033b80783 | ||
|
7adabec088 | ||
|
4841d95573 | ||
|
2dc0dafa5a | ||
|
9d4f4cb534 | ||
|
05084030db | ||
|
4d71775804 | ||
|
77bccb3f69 | ||
|
affb8590dc | ||
|
6c39c448bb | ||
|
a6a2d048c7 | ||
|
464e255d56 | ||
|
0e8139217b | ||
|
1876511c70 | ||
|
56182aa60f | ||
|
bf35b265db | ||
|
520aa39724 | ||
|
59acd94bc6 | ||
|
619b0b3276 | ||
|
d686b2c2c7 | ||
|
479d4bb322 | ||
|
49b1c5a174 | ||
|
35b3ee2b0f | ||
|
a443bb5e41 | ||
|
a05d2af417 | ||
|
1efa5c0ef6 | ||
|
6bf0263797 | ||
|
ed3fd57d6a | ||
|
5a80b3a58d | ||
|
f022f3cb26 | ||
|
af5ce5bbfd | ||
|
ea5b94ad90 | ||
|
e5e9603221 | ||
|
cc18f5055c | ||
|
fafc18b419 | ||
|
bb312d871c | ||
|
49561d14cc | ||
|
1cd4251083 | ||
|
ee74785d7e | ||
|
eda3ea1210 | ||
|
f689e38e59 | ||
|
f765dd52fc | ||
|
04d54e1dc5 | ||
|
e93a008fe4 | ||
|
5b9dbdf57e | ||
|
0e77bdf4d7 | ||
|
0211148b66 | ||
|
973ecff0be | ||
|
bdf286103f | ||
|
e86155adf4 | ||
|
374bd2adff | ||
|
8618ed3aad | ||
|
aa39b55563 | ||
|
24e8c99dea | ||
|
eeab0251d2 | ||
|
2111e2293c | ||
|
bd28468bf5 | ||
|
05b1c27320 | ||
|
b326291f94 | ||
|
8a4380ec14 | ||
|
eb3cac8b1b | ||
|
0db4f59990 | ||
|
aed7b97bac | ||
|
9e4b3bbdd6 | ||
|
cd59248f34 | ||
|
876b107284 | ||
|
ea9dc13992 | ||
|
f20b5285da | ||
|
9b46c1e7d9 | ||
|
d7e1fd4bb2 | ||
|
ae88703a8b | ||
|
cf8f18dbbd | ||
|
b96ccf00ce | ||
|
5285baebdb | ||
|
3652505a08 | ||
|
c5f94906d4 | ||
|
e3fb689d27 | ||
|
93e5ecec4c | ||
|
82dbdb29c3 | ||
|
476b0a1d42 | ||
|
e7622f6c2b | ||
|
cd6604d28c | ||
|
cffd742b7b | ||
|
bbdb99dbb5 | ||
|
ce381f8ce9 | ||
|
1bd2bff8f8 | ||
|
943c2dc425 | ||
|
bc278f2beb | ||
|
b81afd45e8 | ||
|
77b8023b56 | ||
|
963607defe | ||
|
4a0b328622 | ||
|
5762faac9d | ||
|
7f750330b1 | ||
|
55d9ca31d4 | ||
|
fde6764377 | ||
|
9c6dae30ca | ||
|
acadeedaf6 | ||
|
fc84942b9b | ||
|
e3eb851ab7 | ||
|
84d02b9925 | ||
|
3fcbf6515e | ||
|
9306d9fcdc | ||
|
d29052e606 | ||
|
9ee2b821ff | ||
|
c4ab6ac562 | ||
|
35630e4f1c | ||
|
07b548c6b6 | ||
|
7eff8a8642 | ||
|
abaad37edd | ||
|
b68d8df9ac | ||
|
07d67b2246 | ||
|
0ae16cf444 | ||
|
26cd1cc1cc | ||
|
56457fe269 | ||
|
688abef81d | ||
|
4803109203 | ||
|
4d878a375c | ||
|
0de2e57a1d | ||
|
87d42c96e2 | ||
|
61abf780d1 | ||
|
ffe7d59b74 | ||
|
3dc590351c | ||
|
5d6055c4d2 | ||
|
d0eda81a9e | ||
|
db9703a150 | ||
|
f6e816fa10 | ||
|
9c091f5126 | ||
|
c6403ff6e8 | ||
|
24817976f6 | ||
|
459963c13b | ||
|
eaabf7968c | ||
|
521d2a0e6d | ||
|
2a3f623c30 | ||
|
33d8a7a026 | ||
|
dc684d05a7 | ||
|
36527910f3 | ||
|
4b1b4bab32 | ||
|
2f0e34c99f | ||
|
67fee68ebe | ||
|
7eff1da988 | ||
|
ffbd7bcf04 | ||
|
dbe97b086c | ||
|
b0ed0a99ff | ||
|
3c45c962b2 | ||
|
f124414594 | ||
|
715cd1e03d | ||
|
d00c2207d9 | ||
|
6ca39ba58b | ||
|
4a05fd0aed | ||
|
bc8fa59557 | ||
|
936499aa1e | ||
|
9642dd844d | ||
|
40e5c8053d | ||
|
b0d98102fc | ||
|
ec5b681196 | ||
|
5a1e41775f | ||
|
95685efbbd | ||
|
ce3706d1b3 | ||
|
bb09f2ccf0 | ||
|
da0270066b | ||
|
baef193d78 | ||
|
61f3c252d4 | ||
|
b0e4c6d926 | ||
|
eb7d6b87d6 | ||
|
78a5534bab | ||
|
f79b7296e6 | ||
|
deb59eb94a | ||
|
331be77b08 | ||
|
b09ab48cd8 | ||
|
61eaae30c8 | ||
|
362cce88eb | ||
|
09edbddf72 | ||
|
4e54915a93 | ||
|
4a9a791047 | ||
|
db747f1ead | ||
|
e147255fcd | ||
|
0b459800cc | ||
|
c5770d4381 | ||
|
ddcb42fe53 | ||
|
5bd1f1e1c6 | ||
|
ba0b5f3985 | ||
|
451a8579d9 | ||
|
da6704b517 | ||
|
68f0319e1b | ||
|
c55c3e4a2f | ||
|
f93e5b613e | ||
|
1c33ee46a2 | ||
|
258be8594d | ||
|
b25264e190 | ||
|
45919ff397 | ||
|
c584f24a02 | ||
|
9a9dc3500e | ||
|
0634f4fe52 | ||
|
f00b3d88e1 | ||
|
d3b22d83cf | ||
|
2ee9b46eb9 | ||
|
2f1f35bd31 | ||
|
465141c097 | ||
|
0d34d7285e | ||
|
9ee00ab423 | ||
|
12c873c7cf | ||
|
5a635336b3 | ||
|
60f49ce449 | ||
|
50608f4520 | ||
|
566fc0a7c8 | ||
|
d2baa1371c | ||
|
814a8e4dae | ||
|
e85863b65f | ||
|
cc50199cff | ||
|
8533a18fea | ||
|
fd2460416a | ||
|
889e4d200c | ||
|
14b7e8de80 | ||
|
5fddff72e7 | ||
|
775145f176 | ||
|
5418538874 | ||
|
05ff795188 | ||
|
5c945e97d6 | ||
|
68d36612dc | ||
|
70014a8c10 | ||
|
bb40c45a29 | ||
|
a84bdb9182 | ||
|
d379b111ee | ||
|
157ea8a3d1 | ||
|
9523e6fb48 | ||
|
7b0cc492e7 | ||
|
ed40e224e7 | ||
|
53de69a61a | ||
|
6b2ed70a7c | ||
|
de93234632 | ||
|
024c2d5349 | ||
|
e7ab35a30e | ||
|
874da1c7d9 | ||
|
30c7fa2c6a | ||
|
d2b9493fb4 | ||
|
8d56a2ec3a | ||
|
284fb1440b | ||
|
807ccf80de | ||
|
89fe866ac5 | ||
|
cb69d47926 | ||
|
9e57a8691a | ||
|
f7b173f7df | ||
|
c4f46a190a | ||
|
d488e8c3d9 | ||
|
c70327e268 | ||
|
d8cb5ac3f8 | ||
|
9b641f64be | ||
|
aeccce639c | ||
|
1d7ab22027 | ||
|
0b1a09d910 | ||
|
5fc8a6a4d3 | ||
|
66d96cf6ae | ||
|
926b688267 | ||
|
0bca63b550 | ||
|
c2791c6124 | ||
|
e1849f72a5 | ||
|
fb6aba1d7f | ||
|
e28c76626f | ||
|
aa8e7e09e0 | ||
|
47b691b6b4 | ||
|
20c1a66d20 | ||
|
8f9d225c61 | ||
|
0127857544 | ||
|
6274708acb | ||
|
a8472e8b71 | ||
|
586400019f | ||
|
0fd3a669c7 | ||
|
a24dd21ca2 | ||
|
b901f4de0c | ||
|
c7eb2dc105 | ||
|
c11d65bef7 | ||
|
f4866c9f89 | ||
|
e9cf1bfd9f | ||
|
e47ccdea6a | ||
|
a855c55132 | ||
|
8f24d6b366 | ||
|
75066f2420 | ||
|
1cc340f3f1 | ||
|
23b1edb003 | ||
|
87e52f88d0 | ||
|
6499e78544 | ||
|
02af66a10f | ||
|
a6a6ffc058 | ||
|
03a39507dc | ||
|
502a984d2b | ||
|
1762389406 | ||
|
87b999fe46 | ||
|
b0a5e66cff | ||
|
16e0971275 | ||
|
cae8f3664e | ||
|
9ccc7add41 | ||
|
22ba48e81e | ||
|
6a1f47d4da | ||
|
8da26f1484 | ||
|
512a8d2c72 | ||
|
6c670c1fbb | ||
|
cc88096413 | ||
|
e5334f0fd7 | ||
|
a6bafcc063 | ||
|
c61cb1a6e8 | ||
|
c49fcd4d9d | ||
|
e3e87f4133 | ||
|
0df4062de4 | ||
|
8a67f60747 | ||
|
90690fa8ea | ||
|
9e245e6f5e | ||
|
ffddc95602 | ||
|
5ca1cc5e62 | ||
|
9c44a08a31 | ||
|
1d9b285bf4 | ||
|
31596772a7 | ||
|
0380b4fda0 | ||
|
c2ae86de3a | ||
|
718e3344ca | ||
|
ccef43cb82 | ||
|
d0d6508ae3 | ||
|
b084ac6e00 | ||
|
dbe0fe88ad | ||
|
3ec3c8358a | ||
|
312aeded25 | ||
|
16a0bc5b3e | ||
|
c7d7749308 | ||
|
8ca80b542d | ||
|
e686ecb9d5 | ||
|
0b67d8e50b | ||
|
da269aa71b | ||
|
4c9caadcf5 | ||
|
3df1642d91 | ||
|
2c77b7e208 | ||
|
64076b2bdd | ||
|
5e45b9a469 | ||
|
b78a7a6207 | ||
|
105be3661f | ||
|
aa6eb27d7d | ||
|
22d5067395 | ||
|
32d668f69d | ||
|
4b616c5578 | ||
|
10256672b4 | ||
|
43548618ba | ||
|
9b1c366ce5 | ||
|
5a2d7394be | ||
|
b222de86f7 | ||
|
3e9f96bdbf | ||
|
4045689bd0 | ||
|
ecf12b175e | ||
|
740ded45f4 | ||
|
bb1a7934c0 | ||
|
ea51b5c883 | ||
|
5ec847ec77 | ||
|
a18b95f9ad | ||
|
cb203bdf1e | ||
|
cf6ee14f3d | ||
|
6a620d4088 | ||
|
bd2d8d7bf6 | ||
|
c195fa19bf | ||
|
eb0172f06c | ||
|
fa25c55655 | ||
|
406af8a87f | ||
|
ff6d40a545 | ||
|
ca12d7d920 | ||
|
73cd0dba4e | ||
|
584795b099 | ||
|
343d42cb86 | ||
|
36dd190ddb | ||
|
f7fc6235e5 | ||
|
5799549901 | ||
|
450e37f973 | ||
|
a2a4e68166 | ||
|
ef70ea236d | ||
|
de88179b26 | ||
|
b17aa24637 | ||
|
a1b3a94c42 | ||
|
8df1de888a | ||
|
53c0a39d7a | ||
|
3c747aa4a7 | ||
|
8aa0d0192e | ||
|
331e190bf8 | ||
|
ed466f758f | ||
|
7ca2997970 | ||
|
26805d023a | ||
|
126d01ede0 | ||
|
d953cf9e4e | ||
|
9fbb1ee479 | ||
|
8e6f7bf808 | ||
|
882c1353c7 | ||
|
318cbd6854 | ||
|
329ed77115 | ||
|
c104503d62 | ||
|
c6753f176a | ||
|
dc50d4e2ac | ||
|
0f555075ae | ||
|
b4ff626b74 | ||
|
eeddad542b | ||
|
b07961df3a | ||
|
d4d79b220f | ||
|
e3ed4251ed | ||
|
58e9f9b355 | ||
|
47f78ad649 | ||
|
a3b20deee9 | ||
|
3803bfa13a | ||
|
03235172a8 | ||
|
859723a890 | ||
|
b5c3bcc86a | ||
|
e726b4ab02 | ||
|
1661ff77d2 | ||
|
4300b1f89c | ||
|
42a4d50c47 | ||
|
76c3fb6fa0 | ||
|
0ee10912a2 | ||
|
82b83e7ed3 | ||
|
46bceea217 | ||
|
5a3f0b1d4a | ||
|
faeb8241a5 | ||
|
e71fd7b153 | ||
|
5af4aa2da9 | ||
|
4ee0617014 | ||
|
717247bbec | ||
|
2337373a6f | ||
|
a96400d970 | ||
|
9006fb9917 | ||
|
fdb148bc85 | ||
|
bd1a8f1d44 | ||
|
fc1c747bed | ||
|
4c142b9632 | ||
|
08ec3fd3a5 | ||
|
206255b411 | ||
|
ffbcaa6259 | ||
|
a15e895337 | ||
|
19595f3b12 | ||
|
1ccd75b2b3 | ||
|
4d1952387c | ||
|
4ad5b0ffe4 | ||
|
739976ea28 | ||
|
3ea9f21e90 | ||
|
bac862a5fc | ||
|
78e0b89a78 | ||
|
16a49c7b27 | ||
|
292e25935f | ||
|
0b39e84f86 | ||
|
a1cd01380b | ||
|
f6cfb4418c | ||
|
65a7da6a36 | ||
|
4aa707132f | ||
|
63ca9053a5 | ||
|
bfa4f60288 | ||
|
493f843987 | ||
|
e64d96cef7 | ||
|
3832222fef | ||
|
007b2a8fea | ||
|
2ce175dbf3 | ||
|
6c2d511827 | ||
|
715281b804 | ||
|
a1b2b9b1da | ||
|
5ac2a9ffdb | ||
|
821c2f5c44 | ||
|
be0fea7104 | ||
|
ffc19f891d | ||
|
3ef9f4ed2d | ||
|
3fb2800db4 | ||
|
d577f0238c | ||
|
b551d93dac | ||
|
55802a7a16 | ||
|
17c0f6e575 | ||
|
7701625b6f | ||
|
141b2beaf3 | ||
|
bae45867fa | ||
|
8ae5a9fc94 | ||
|
76d1f9ab70 | ||
|
3ab3467547 | ||
|
175e8caa67 | ||
|
b36fd3f312 | ||
|
dfdda78ab4 | ||
|
a8e243b3fd | ||
|
2aa700bca0 | ||
|
8833a973b9 | ||
|
a7dafbf301 | ||
|
4b49c66283 | ||
|
6665c61a95 | ||
|
bfc4faeed2 | ||
|
853a5be569 | ||
|
e6cae5f90d |
@ -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"
|
||||
}
|
||||
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@ -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
|
||||
|
15
.github/workflows/deploy-docs.yml
vendored
15
.github/workflows/deploy-docs.yml
vendored
@ -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
|
||||
|
11
.github/workflows/main.yml
vendored
11
.github/workflows/main.yml
vendored
@ -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
78
.github/workflows/publish.yml
vendored
Normal 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
1
.gitignore
vendored
@ -14,3 +14,4 @@ test-report.xml
|
||||
yarn-error.log
|
||||
/build
|
||||
/docs/
|
||||
/generated/
|
||||
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -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": []
|
||||
|
49
README.md
49
README.md
@ -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>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://www.npmjs.com/package/dockview-core)
|
||||
[](https://www.npmjs.com/package/dockview-core)
|
||||
[](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://bundlephobia.com/result?p=dockview)
|
||||
[](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
8
SECURITY.md
Normal 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
1
jest-setup.ts
Normal file
@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
@ -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',
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
116
package.json
116
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
56
packages/dockview-angular/README.md
Normal file
56
packages/dockview-angular/README.md
Normal 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>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_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>
|
||||
```
|
34
packages/dockview-angular/jest.config.ts
Normal file
34
packages/dockview-angular/jest.config.ts
Normal 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;
|
59
packages/dockview-angular/package.json
Normal file
59
packages/dockview-angular/package.json
Normal 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"
|
||||
}
|
||||
}
|
113
packages/dockview-angular/rollup.config.js
Normal file
113
packages/dockview-angular/rollup.config.js
Normal 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 }),
|
||||
];
|
2
packages/dockview-angular/scripts/rollupEntryTarget.ts
Normal file
2
packages/dockview-angular/scripts/rollupEntryTarget.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import '../dist/styles/dockview.css';
|
||||
export * from '../src/index';
|
5
packages/dockview-angular/src/__tests__/empty.spec.ts
Normal file
5
packages/dockview-angular/src/__tests__/empty.spec.ts
Normal file
@ -0,0 +1,5 @@
|
||||
describe('empty', () => {
|
||||
test('that passes', () => {
|
||||
expect(true).toBeTruthy();
|
||||
});
|
||||
});
|
1
packages/dockview-angular/src/index.ts
Normal file
1
packages/dockview-angular/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from 'dockview-core';
|
14
packages/dockview-angular/tsconfig.esm.json
Normal file
14
packages/dockview-angular/tsconfig.esm.json
Normal 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__"]
|
||||
}
|
11
packages/dockview-angular/tsconfig.json
Normal file
11
packages/dockview-angular/tsconfig.json
Normal 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__"]
|
||||
}
|
5
packages/dockview-angular/typedoc.json
Normal file
5
packages/dockview-angular/typedoc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": ["../../typedoc.base.json"],
|
||||
"entryPoints": ["src/index.ts"],
|
||||
"exclude": ["**/dist/**"]
|
||||
}
|
@ -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>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
@ -15,37 +16,23 @@
|
||||
|
||||
##
|
||||
|
||||

|
||||
|
||||
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).
|
||||
|
@ -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']));
|
||||
|
@ -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__',
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
@ -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 {
|
||||
//
|
||||
}
|
||||
|
45
packages/dockview-core/src/__tests__/__mocks__/mockWindow.ts
Normal file
45
packages/dockview-core/src/__tests__/__mocks__/mockWindow.ts
Normal 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++;
|
||||
},
|
||||
});
|
||||
}
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ describe('api', () => {
|
||||
let api: PanelApiImpl;
|
||||
|
||||
beforeEach(() => {
|
||||
api = new PanelApiImpl('dummy_id');
|
||||
api = new PanelApiImpl('dummy_id', 'fake-component');
|
||||
});
|
||||
|
||||
test('updateParameters', () => {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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 });
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
});
|
||||
});
|
@ -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
|
||||
|
@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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('');
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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]);
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ describe('gridviewPanel', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
418
packages/dockview-core/src/__tests__/overlay/overlay.spec.ts
Normal file
418
packages/dockview-core/src/__tests__/overlay/overlay.spec.ts
Normal 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));
|
||||
});
|
||||
});
|
@ -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)'
|
||||
);
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
46
packages/dockview-core/src/api/entryPoints.ts
Normal file
46
packages/dockview-core/src/api/entryPoints.ts
Normal 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);
|
||||
}
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
3
packages/dockview-core/src/constants.ts
Normal file
3
packages/dockview-core/src/constants.ts
Normal 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 };
|
@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
class TransferObject {
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
// intentionally empty class
|
||||
}
|
||||
|
||||
export class PanelTransfer extends TransferObject {
|
||||
|
@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
102
packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts
Normal file
102
packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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}`;
|
||||
},
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
301
packages/dockview-core/src/dockview/components/titlebar/tabs.ts
Normal file
301
packages/dockview-core/src/dockview/components/titlebar/tabs.ts
Normal 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 });
|
||||
}
|
||||
}
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user