Compare commits

...

1005 Commits

Author SHA1 Message Date
mathuo
6c3ba33226
chore(release): publish v4.7.0 2025-08-22 22:23:42 +01:00
mathuo
89286ebe5c
chore: v4.7.0 docs 2025-08-22 22:23:10 +01:00
mathuo
e1d47ddea2
Merge pull request #998 from mathuo/fix/github-issue-991-group-activation
bug: fix always renderers initial position
2025-08-22 22:02:13 +01:00
mathuo
49014345d9
bug: fix always renderers initial position 2025-08-22 21:55:44 +01:00
mathuo
65b68a66cc
Merge pull request #993 from mathuo/fix/github-issue-991-group-activation
Fix/GitHub issue 991 group activation
2025-08-12 21:18:33 +01:00
mathuo
e9df48e294
Merge branch 'master' of https://github.com/mathuo/dockview into fix/github-issue-991-group-activation 2025-08-12 20:42:50 +01:00
mathuo
de4a31df72
chore: fix tests 2025-08-11 22:38:15 +01:00
mathuo
3e77b8a4ee
Merge pull request #983 from mathuo/fix-issue-926-multiple-popup-persistence
bug: delay popup opens when deserializing
2025-08-11 21:50:18 +01:00
mathuo
0a7e5338ef
Merge pull request #992 from mathuo/fix-issue-988-windows-shaking
Fix issue 988 windows shaking
2025-08-11 21:48:31 +01:00
mathuo
874d6a27ca
chore: use constant 2025-08-11 20:35:19 +01:00
mathuo
d6667f14fd
chore: fixup 2025-08-11 20:31:55 +01:00
mathuo
722150fae7
feat: add gridview normalization to prevent redundant branch nodes
- Add normalize() method to Gridview class to eliminate single-child branch scenarios
- Call normalize() in DockviewComponent.addGroup() to maintain clean structure
- Implement safer disposal order to prevent potential memory leaks
- Add cloneNode() helper function for proper node cloning during normalization

Tests:
- Add comprehensive test suite for normalize() method covering edge cases
- Add integration test verifying addGroup() calls normalize()
- Ensure normalization handles empty gridviews, single leafs, and multiple children correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-10 22:03:51 +01:00
mathuo
be14c4265d
fix: resolve group activation and panel content visibility after tab header space drag (#991)
Fixes GitHub issue #991 where dragging tab header space (empty area) to different
positions caused panel content to disappear and groups to become inactive.

## Changes

**Core Fix (3 minimal changes):**
- Fix panel activation logic to ensure first/active panels render correctly
- Restore group activation for center case moves (root drop to edge zones)
- Fix group activation for non-center moves using correct target group

**Comprehensive Test Suite:**
- Added 5 targeted tests validating the fix for various scenarios
- Tests cover single/multi-panel groups, center/non-center moves, and skipSetActive
- Ensures both panel content rendering and group activation work correctly

## Technical Details

The root cause was in `moveGroup()` method where:
1. Panel content disappeared due to incorrect `skipSetActive: true` for all panels
2. Groups became inactive due to activation calls inside `movingLock()`
3. Non-center moves failed activation when source group was destroyed

**Fixed in dockviewComponent.ts:**
- `skipSetActive: panel \!== activePanel` - ensures active panel renders (line 2347)
- `doSetGroupAndPanelActive(to)` - activates target group for center moves (line 2354)
- `const targetGroup = to ?? from` - uses correct group for non-center activation (line 2485)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-09 19:27:07 +01:00
mathuo
1fa8a61123
feat: fix Windows shaking issue and implement GPU optimizations
- Fix GitHub issue #988: Windows visual shaking with always-rendered panels
  * Add position caching with frame-based invalidation in OverlayRenderContainer
  * Implement requestAnimationFrame batching to prevent layout thrashing
  * Cache DOM measurements to reduce expensive getBoundingClientRect calls

- Implement comprehensive GPU hardware acceleration
  * Add GPU optimizations to drop target system with transform-based positioning
  * Enable hardware acceleration for overlay containers and panel animations
  * Add CSS containment and isolation techniques for better performance
  * Use hybrid approach: traditional positioning + GPU layers for compatibility

- Enhance drop target positioning system
  * Add setGPUOptimizedBounds functions for performance-optimized positioning
  * Maintain proper drop target quadrant behavior while adding GPU acceleration
  * Fix positioning precision issues in complex layouts

- Update test expectations to match RAF batching behavior
  * Adjust overlay render container tests for improved async positioning
  * Fix test precision issues caused by position caching optimizations

- Add debug logging for always render mode investigation
  * Include development-mode logging for overlay positioning diagnostics
  * Add visibility change tracking for better debugging

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-08 21:52:40 +01:00
mathuo
414244cc8c
fix: prevent Windows shaking when adding always-rendered panels #988
- Add position caching with RAF batching to prevent excessive DOM measurements
- Replace direct DOM updates with requestAnimationFrame-based positioning
- Add CSS containment to overlay containers to prevent layout cascade
- Update tests to handle async RAF behavior and add specific test for issue #988

This resolves visual shaking on Windows by eliminating layout thrashing
caused by frequent getBoundingClientRect() calls during panel operations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-08 21:17:37 +01:00
mathuo
212863cbec
chore: fix scripts 2025-08-02 21:18:59 +01:00
mathuo
392e63c226
chore: v4.6.2 docs 2025-08-02 14:56:17 +01:00
mathuo
a7a13b85c1
chore(release): publish v4.6.2 2025-08-02 14:55:50 +01:00
mathuo
ac5af02f20
chore: fix version 2025-08-02 14:55:24 +01:00
mathuo
5c7d5b9ae7
chore(release): publish v4.6.1 2025-08-02 14:51:27 +01:00
mathuo
079a751fba
chore: v4.6.1 docs 2025-08-02 14:50:54 +01:00
mathuo
35bcd9d7a9
chore: add missing script 2025-08-02 14:50:20 +01:00
mathuo
9e3f036f46
chore(release): publish v4.6.0 2025-08-02 14:38:26 +01:00
mathuo
dcc9c60f5c
chore: v4.6.0 docs 2025-08-02 14:37:40 +01:00
mathuo
c20147ab17
chore: release notes process 2025-08-02 14:37:29 +01:00
mathuo
93d248050b
Merge pull request #985 from mathuo/973-closing-tabs-via-dropdown-menu-does-not-work
973 closing tabs via dropdown menu does not work
2025-08-01 20:50:24 +01:00
mathuo
c38ea5eede
test: Add comprehensive tests for dropdown close button functionality
Added extensive test coverage for the fixed close button behavior in tab
overflow dropdowns:

Core DefaultTab Tests:
- Verify close button prevents default behavior
- Test that already prevented events are respected
- Confirm close button visibility by default

TabsContainer Dropdown Tests:
- Test close button visibility and clickability in dropdowns
- Verify tab content clicks still activate tabs properly
- Test preventDefault behavior in dropdown wrapper
- Ensure proper separation of close vs activate functionality

These tests verify that close buttons work correctly in both normal tabs
and overflow dropdown tabs, with proper event handling.

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-31 22:58:29 +01:00
mathuo
55207e3413
Revert "Merge pull request #984 from mathuo/fix/issue-973-popup-tabs"
This reverts commit 9ce146093a532eb53079dd99649d50219849f22c, reversing
changes made to 95c4ee1d26e4e3090ce4faaf6b7cdb3e144003ea.
2025-07-31 22:58:17 +01:00
mathuo
9ce146093a
Merge pull request #984 from mathuo/fix/issue-973-popup-tabs
Fix/issue 973 popup tabs
2025-07-31 22:29:59 +01:00
mathuo
4200d72191
test: Add test for dropdown close button functionality
Verifies that clicking close buttons in tab overflow dropdown works correctly
and doesn't interfere with tab activation behavior.

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-31 21:47:51 +01:00
mathuo
847f4f5219
fix: Enable close button functionality in tab overflow dropdown
The close buttons in the tab dropdown were not working because the wrapper
div's pointerdown event handler was intercepting all clicks. This fix checks
if the click target is within a close button element (.dv-default-tab-action)
and allows the close button's event handler to execute properly.

Fixes #973

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-31 21:45:26 +01:00
mathuo
95c4ee1d26
Merge pull request #978 from mathuo/fix/971-void-container-cursor
bug: grab cursor on empty spaces
2025-07-31 21:35:33 +01:00
mathuo
0a19313cc7
bug: delay popup opens when deserializing 2025-07-31 21:31:29 +01:00
mathuo
7e3ffac8ae
Merge pull request #982 from mathuo/project-fixes
chore: script fixes
2025-07-30 23:12:32 +01:00
mathuo
3f74e0037b
chore: script fixes 2025-07-30 23:11:51 +01:00
mathuo
ff7e3b874d
Merge pull request #980 from mathuo/project-fixes
Project fixes
2025-07-30 22:28:07 +01:00
mathuo
85ae6c2cde
chore: npm scripts 2025-07-30 22:07:02 +01:00
mathuo
d3996e29ee
chore: add CLAUDE.md 2025-07-29 23:08:19 +01:00
mathuo
be9759e617
chore: update api docs 2025-07-29 23:04:27 +01:00
mathuo
17faf5e924
chore: script cleanup 2025-07-29 23:03:48 +01:00
mathuo
e34d7caed1
chore: fix linting 2025-07-29 22:54:09 +01:00
mathuo
2c8547d942
chore: adjust build scripts 2025-07-29 22:34:44 +01:00
mathuo
8bbe482d74
chore: remove circular dependencies 2025-07-29 22:19:25 +01:00
mathuo
26fd23c8c7
bug: grab cursor on empty spaces 2025-07-29 21:52:26 +01:00
mathuo
ea62570b9a
chore: update docs 2025-07-25 22:37:25 +01:00
mathuo
84ec90ee68
Merge pull request #963 from mathuo/934-themes-with-gap-overflow
bug: theme overflow
2025-07-20 20:39:58 +01:00
mathuo
5c6f6d203e
Merge pull request #970 from mathuo/969-theming-not-consistent-between-jsts-and-react
chore: improve templates
2025-07-20 20:25:11 +01:00
mathuo
27b9e29077
chore: improve templates 2025-07-20 20:18:46 +01:00
mathuo
04ae805774
chore(release): publish v4.5.0 2025-07-20 17:05:22 +01:00
mathuo
84a68cfda1
chore: v4.5.0 docs 2025-07-19 14:34:30 +01:00
mathuo
1a17874b7d
Merge pull request #966 from mathuo/960-fix-popout-drag-ghost-group
960 fix popout drag ghost group
2025-07-18 22:17:53 +01:00
mathuo
2a1a8d8b80
Merge pull request #962 from mathuo/813-move-panels-moveto-without-making-them-active-1
feat: add skipSetActive parameter to moveTo functions
2025-07-18 21:57:35 +01:00
mathuo
73d1cfbadd
Merge pull request #964 from mathuo/958-group-drag-in-is-not-positioned-correctly
fix: correct positioning when dragging groups from popout to main window
2025-07-18 21:54:44 +01:00
mathuo
d8916778c8
fix: prevent ghost group creation when dragging popout groups back to grid
Resolves issue #960 where dragging a popout group back to the main grid
would create an additional empty "ghost" group at the leftmost position.

Changes:
- Unified popout disposal logic to prevent automatic restoration
- Always remove popout groups from tracking array before disposal
- Clean up hidden reference groups that could become ghosts
- Use manual window disposal instead of disposable.dispose()
- Added proper support for popout-to-floating group moves
- Fixed conditional logic for grid placement

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-18 21:46:52 +01:00
mathuo
05196fd86a
fix: correct positioning when dragging groups from popout to main window
Fixes issue #958 where groups dragged from popout windows would be restored
to their original ghost position instead of the actual drop target.

Changes:
- Detect cross-window moves from popout to grid locations
- Prevent automatic restoration to reference group during disposal
- Clean up hidden reference groups when moving to new positions
- Ensure proper positioning at actual drop target
- Add comprehensive tests for cross-window drag positioning

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-17 22:28:12 +01:00
mathuo
ef9e3f37eb
bug: theme overflow 2025-07-17 20:57:05 +01:00
mathuo
23a5d84020
chore(release): publish v4.4.1 2025-07-17 20:42:31 +01:00
mathuo
f6d580a6da
chore: v4.4.1 docs 2025-07-17 20:42:11 +01:00
mathuo
eec5380897
Merge pull request #961 from mathuo/950-tabs-have-draggabletrue-when-disabledndtrue
fix: respect disableDnd option for tab draggable attribute
2025-07-17 20:36:20 +01:00
mathuo
6ca6764abf
fix: adjust group merging logic for skipSetActive parameter
- Fix test assertion to verify active panel exists rather than specific panel
- Improve group move logic to properly handle active panel preservation
- Ensure skipSetGroupActive is always true during panel moves for consistency

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-17 08:54:38 +01:00
mathuo
f66cd90ffd
feat: add skipSetActive parameter to moveTo functions
Add optional skipSetActive parameter to panel and group moveTo functions to prevent automatic activation when moving panels or groups. This allows programmatic layout changes without disrupting the current focus.

Changes:
- Add skipSetActive parameter to DockviewGroupMoveParams interface
- Update panel and group moveTo implementations to respect skipSetActive
- Update moveGroupOrPanel and moveGroup functions to handle skipSetActive
- Fix group merging logic to preserve target group's active panel
- Add comprehensive tests for both panel and group moveTo with skipSetActive

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 23:14:53 +01:00
mathuo
782a61bf69
chore: loosen type restrictions 2025-07-16 22:45:46 +01:00
mathuo
ac6154196e
test: add comprehensive unit tests for disableDnd functionality
- Add unit tests for Tab draggable attribute with disableDnd option
- Add unit tests for VoidContainer draggable attribute with disableDnd option
- Add unit tests for updateDragAndDropState methods in all components
- Add integration tests for updateOptions with disableDnd changes
- Fix existing test mocks to include required options property

Tests cover both initial state and dynamic updates when option changes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 22:33:21 +01:00
mathuo
ad9f884847
fix: respect disableDnd option for tab draggable attribute
- Fix tabs and void containers to respect disableDnd option at initialization
- Add dynamic update system when disableDnd option changes via updateOptions()
- Ensure all tab elements properly update their draggable state when option changes
- Maintains consistency with existing paneview behavior that already respects disableDnd

🤖 Generated with [Claude Code](https://claude.ai/code)

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

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

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

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

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

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

* Attempting to make TS happy

* Making typescript happy round two

https://stackoverflow.com/questions/54688147/react-typescript-event-type-for-both-interfaces-mouseevent-and-touchevent

* make TS Happy

* Update splitview.ts

---------

Co-authored-by: Ray Foss <ray@vblip.com>
2023-06-12 21:36:24 +01:00
mathuo
fbe6c65186
Merge pull request #275 from mathuo/274-gridview-tests
test: gridview tests
2023-06-12 21:28:15 +01:00
mathuo
e3dd45a185
test: add tests 2023-06-12 21:19:39 +01:00
mathuo
0eff522f1f
Merge branch 'master' of https://github.com/mathuo/dockview into 274-gridview-tests 2023-06-11 21:32:13 +01:00
mathuo
1e756209a0
Merge pull request #273 from mathuo/270-documentation-for-iframes
chore: docs
2023-06-11 21:17:23 +01:00
mathuo
35a97b7426
test: gridview tests 2023-06-11 21:17:04 +01:00
mathuo
477394cd1e
chore: docs 2023-06-11 20:43:13 +01:00
mathuo
54bc30c1aa
chore(release): publish v1.7.5 2023-06-11 20:25:44 +01:00
mathuo
c42d8648ea
chore: prepare v1.7.5 2023-06-11 20:25:23 +01:00
mathuo
d8e8103f80
chore: prepare v1.7.5 2023-06-11 20:21:11 +01:00
mathuo
1923ad66ca
Merge pull request #272 from mathuo/255-dispose-of-all-resources-correctly-2
fix: revert gridview disposal changes
2023-06-11 20:20:01 +01:00
mathuo
4f5b6adb5f
fix: revert gridview disposal changes 2023-06-11 20:11:08 +01:00
mathuo
6f9b540fdf
Merge pull request #271 from mathuo/270-documentation-for-iframes
chore: iFrame example
2023-06-11 15:55:50 +01:00
mathuo
ba3fe82c02
chore: iFrame example 2023-06-11 15:49:27 +01:00
mathuo
7905af2945
Merge pull request #269 from mathuo/268-touch-support-for-resizing-panels
test: add tests to existing mouse dnd functionality
2023-06-11 15:18:22 +01:00
mathuo
a39c2938f0
test: listener utilities 2023-06-11 14:32:20 +01:00
mathuo
1539321320
test: add tests to existing mouse dnd functionality 2023-06-11 13:23:12 +01:00
mathuo
2ca4c6fd65
Merge branch 'master' of https://github.com/mathuo/dockview into 263-left-header-actions 2023-06-11 11:34:16 +01:00
mathuo
7dde18c636
chore(release): publish v1.7.4 2023-06-11 09:45:28 +01:00
mathuo
d9906eb802
chore: prepare v1.7.4 2023-06-10 11:38:41 +01:00
mathuo
88565f022c
Merge pull request #265 from mathuo/258-dockviewapitojson-doesnt-include-panel-titles-2
bug: title is deleted when updateParameters() is called with no title
2023-06-06 21:37:55 +01:00
mathuo
78eac85c68
bug: title is deleted when updateParameters() is called with no title 2023-06-06 21:30:28 +01:00
mathuo
105017245b
feat: left header actions 2023-06-04 15:15:41 +01:00
mathuo
77925dc4ca
feat: floating groups 2023-06-04 14:38:48 +01:00
mathuo
bd5999b0ea
Merge branch 'master' of https://github.com/mathuo/dockview into 230-explore-floating-groups 2023-06-04 14:36:30 +01:00
mathuo
b112b2d5e4
Merge branch 'master' of https://github.com/mathuo/dockview into 263-left-header-actions 2023-06-04 14:35:07 +01:00
mathuo
936a5c6917
Merge pull request #257 from mathuo/255-dispose-of-all-responses-correctly
cleanup event listeners after use
2023-06-04 14:34:29 +01:00
mathuo
97483623d4
Merge branch 'master' of https://github.com/mathuo/dockview into 255-dispose-of-all-responses-correctly 2023-06-04 14:26:58 +01:00
mathuo
0cb621e860
cleanup event listeners after use 2023-06-04 14:15:36 +01:00
mathuo
d7baa93a9b
feat: left header actions 2023-06-04 14:13:24 +01:00
mathuo
3d47c8e2c5
chore(release): publish v1.7.3 2023-06-04 10:22:22 +01:00
mathuo
1964d6b306
chore: update sandbox metadata 2023-06-04 10:20:29 +01:00
mathuo
5b493b95e0
feat: experimental floating groups 2023-06-04 10:04:24 +01:00
mathuo
44a7019c60
Merge branch 'master' of https://github.com/mathuo/dockview into 263-left-header-actions 2023-06-03 20:33:26 +01:00
mathuo
efbe019583
Merge branch 'master' of https://github.com/mathuo/dockview 2023-06-03 20:26:53 +01:00
mathuo
b49e2e5d10
chore: generate docs for v1.7.3 2023-06-03 20:26:45 +01:00
mathuo
13d3db605b
feat: add left header actions 2023-06-03 20:21:25 +01:00
mathuo
6434e68b5b
Merge pull request #260 from mathuo/258-dockviewapitojson-doesnt-include-panel-titles
bug: fix conflicts between panel.title and panel.params.title (user p…
2023-06-03 14:57:25 +01:00
mathuo
7fdede6952
Merge branch 'master' of https://github.com/mathuo/dockview into 230-explore-floating-groups 2023-05-31 20:02:45 +01:00
mathuo
1cf23f04ba
bug: fix conflicts between panel.title and panel.params.title (user provided title) 2023-05-31 19:37:36 +01:00
mathuo
32746e248d
chore: docs (plain ts examples) 2023-05-22 21:37:50 +01:00
mathuo
107c20a81d
Merge branch 'master' of https://github.com/mathuo/dockview 2023-05-22 21:10:49 +01:00
mathuo
3c24579e90
chore: docs: plain typescript examples 2023-05-22 21:10:43 +01:00
mathuo
d083ddd129
Merge pull request #253 from mathuo/252-add-typedocs
feat: add typedocs
2023-05-16 21:34:29 +01:00
mathuo
36299d08f9
feat: add typedocs 2023-05-16 21:28:02 +01:00
Matthew Pearson
e6cae5f90d added an optional type parameter for addPanel() so the params prop can be strictly typed 2023-05-13 15:14:00 -07:00
mathuo
22018e0d28
chore: docs 2023-05-08 20:46:12 +01:00
mathuo
fda40a9fcb
chore(release): publish v1.7.2 2023-05-07 20:53:10 +01:00
mathuo
50789414ea
chore: prepare v1.7.2 2023-05-07 15:37:00 +01:00
mathuo
5de5bae766
Merge pull request #250 from mpearson/249-stuck-resizing-fix
bug: disable user-select on drag handles
2023-05-07 15:34:00 +01:00
Matthew Pearson
a147a28222 bug: disable user-select on drag handles 2023-05-06 02:19:38 -07:00
mathuo
bdecc00c81
Update issue templates 2023-04-16 22:35:00 +01:00
mathuo
be11692114
Merge pull request #248 from mathuo/247-remove-code-smells
test: remove smells and test
2023-04-16 22:12:56 +01:00
mathuo
ec341f0706
test: adjust tsconfig for tests 2023-04-16 22:06:48 +01:00
mathuo
575a1d7031
test: remove smells and test 2023-04-16 21:53:28 +01:00
mathuo
7f54cff960
docs: fix website 2023-04-11 22:34:30 +01:00
mathuo
302c269849
chore(release): publish v1.7.1 2023-04-11 21:06:54 +01:00
mathuo
99d4c1e180
docs: 1.7.1 2023-04-11 21:06:17 +01:00
mathuo
c1edea8744
docs: 1.7.1 2023-04-11 21:04:34 +01:00
mathuo
d20689170d
chore: remove source maps from rollup jobs 2023-04-11 20:43:38 +01:00
mathuo
75da885f91
Merge pull request #244 from mathuo/234-improve-tsconfig-setup
chore: remove sourcemaps from rollup jobs
2023-04-11 20:29:40 +01:00
mathuo
98c63628af
chore: remove sourcemaps from rollup jobs 2023-04-11 20:29:13 +01:00
mathuo
6b7f18a3ee
Merge pull request #243 from mathuo/242-adding-panel-through-absoulte-direction-bug
bug: flip orientation when removing intermediate node
2023-04-11 19:42:16 +01:00
mathuo
7b3adb919e
bug: flip orientation when removing intermediate node 2023-04-11 19:23:30 +01:00
mathuo
081e665e56
Merge pull request #241 from mathuo/220-improve-documentation
chore: improve docs
2023-04-06 21:07:19 +01:00
mathuo
c4f778f1cc
chore: improve docs 2023-04-06 21:06:55 +01:00
mathuo
acb500a9d8
Merge pull request #240 from mathuo/220-improve-documentation
chore: docs
2023-04-03 22:45:56 +01:00
mathuo
dd70450a8e
chore: docs 2023-04-03 22:41:19 +01:00
mathuo
21bbaa7349
Merge pull request #235 from mathuo/234-improve-tsconfig-setup
feat: tsconfig adjustments
2023-04-03 22:24:07 +01:00
mathuo
24e7b05321
Merge pull request #237 from mathuo/232-minor-typescript-types-adjustment
chore: type definition fix
2023-04-03 22:23:44 +01:00
mathuo
82df784065
chore: type definition fix 2023-04-03 22:14:52 +01:00
mathuo
9c5fd414c1
Merge pull request #236 from mathuo/233-remove-js-tabheight-setter-in-favour-of-css
feature: remove tabHeight setter
2023-04-03 22:06:47 +01:00
mathuo
e07cedf01f
feat: remove tabHeight setter 2023-04-03 22:06:24 +01:00
mathuo
36299f8c93
feat: tsconfig adjustments 2023-04-03 22:06:10 +01:00
mathuo
7343009e9b
chore: align types 2023-04-01 21:01:07 +01:00
mathuo
ffd5db273e
feature: floating groups 2023-04-01 20:57:41 +01:00
mathuo
1f384c3c65
Merge pull request #229 from mathuo/220-improve-documentation
chore: docs
2023-03-27 21:21:09 +01:00
mathuo
74f451ea16
chore: docs 2023-03-27 21:20:41 +01:00
mathuo
d5da2a443b
Merge pull request #228 from mathuo/220-improve-documentation
220 improve documentation
2023-03-27 21:19:26 +01:00
mathuo
d68a1c88d0
chore: docs 2023-03-27 21:11:05 +01:00
mathuo
857b475be7
Merge pull request #221 from mathuo/220-improve-documentation
chore: docs
2023-03-26 19:58:19 +01:00
mathuo
3cbd238c8a
Merge branch 'master' of https://github.com/mathuo/dockview into 220-improve-documentation 2023-03-26 19:52:31 +01:00
mathuo
e95777a6a2
Merge pull request #227 from mathuo/226-move-resizeobserver-logic-into-dockview-core
feat: move resizeObserver logic into dockview-core
2023-03-26 19:51:03 +01:00
mathuo
1853d45191
Merge pull request #223 from mathuo/222-include-source-maps-in-build
feat: adjust tsconfigs
2023-03-26 19:49:13 +01:00
mathuo
c3db8e303d
chore: docs 2023-03-26 19:38:37 +01:00
mathuo
551731ffd1
Merge pull request #225 from mathuo/224-bug-close-button-on-default-watermark-doesnt-work
bug: fix close button on default watermark
2023-03-26 19:34:29 +01:00
mathuo
d847f18cd0
feat: move resizeObserver logic into dockview-core 2023-03-26 19:27:44 +01:00
mathuo
360d6ef429
bug: fix close button on default watermark 2023-03-26 16:03:23 +01:00
mathuo
2b7ba6489c
feat: adjust tsconfigs 2023-03-26 15:12:35 +01:00
mathuo
a51740c123
Merge pull request #217 from mathuo/208-documentation-examples-1
chore: docs
2023-03-25 20:38:01 +00:00
mathuo
0f828a909a
Merge branch 'master' of https://github.com/mathuo/dockview into 208-documentation-examples-1 2023-03-25 20:29:29 +00:00
mathuo
42a608b93d
chore: 1.7.0 docs 2023-03-25 20:26:14 +00:00
mathuo
4a7c6b3fee
chore: docs 2023-03-25 20:16:22 +00:00
856 changed files with 107711 additions and 20086 deletions

View File

@ -1,23 +1,45 @@
{ {
"packages": [ "packages": [
"packages/dockview-core", "packages/dockview-core",
"packages/dockview-vue",
"packages/dockview-react",
"packages/dockview" "packages/dockview"
], ],
"sandboxes": [ "sandboxes": [
"/packages/docs/sandboxes/constraints-dockview", "/packages/docs/sandboxes/constraints-dockview",
"/packages/docs/sandboxes/customheader-dockview", "/packages/docs/sandboxes/demo-dockview",
"/packages/docs/sandboxes/dnd-dockview", "/packages/docs/sandboxes/dnd-dockview",
"/packages/docs/sandboxes/dockview-app",
"/packages/docs/sandboxes/editor-gridview",
"/packages/docs/sandboxes/events-dockview", "/packages/docs/sandboxes/events-dockview",
"/packages/docs/sandboxes/externaldnd-dockview", "/packages/docs/sandboxes/externaldnd-dockview",
"/packages/docs/sandboxes/floatinggroup-dockview",
"/packages/docs/sandboxes/fullwidthtab-dockview", "/packages/docs/sandboxes/fullwidthtab-dockview",
"/packages/docs/sandboxes/headeractions-dockview",
"/packages/docs/sandboxes/groupcontol-dockview", "/packages/docs/sandboxes/groupcontol-dockview",
"/packages/docs/sandboxes/iframe-dockview",
"/packages/docs/sandboxes/keyboard-dockview",
"/packages/docs/sandboxes/layout-dockview", "/packages/docs/sandboxes/layout-dockview",
"/packages/docs/sandboxes/lockedgroup-dockview",
"/packages/docs/sandboxes/maximizegroup-dockview",
"/packages/docs/sandboxes/nativeapp-dockview", "/packages/docs/sandboxes/nativeapp-dockview",
"/packages/docs/sandboxes/nested-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/resize-dockview",
"/packages/docs/sandboxes/resizecontainer-dockview",
"/packages/docs/sandboxes/scrollbars-dockview",
"/packages/docs/sandboxes/simple-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/updatetitle-dockview",
"/packages/docs/sandboxes/watermark-dockview" "/packages/docs/sandboxes/watermark-dockview",
"/packages/docs/sandboxes/javascript/fullwidthtab-dockview",
"/packages/docs/sandboxes/javascript/simple-dockview",
"/packages/docs/sandboxes/javascript/tabheight-dockview",
"/packages/docs/sandboxes/javascript/vanilla-dockview"
], ],
"node": "16" "node": "18"
} }

View File

@ -2,18 +2,34 @@ module.exports = {
root: true, root: true,
parserOptions: { parserOptions: {
sourceType: 'module', sourceType: 'module',
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'], project: [
'./tsconfig.eslint.json',
'./packages/*/tsconfig.json',
'./packages/dockview-vue/tsconfig.app.json'
],
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,
}, },
plugins: ['@typescript-eslint'], plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
ignorePatterns: [
'packages/docs/**',
'**/__tests__/**',
'**/__mocks__/**',
'**/*.spec.*',
'**/*.test.*',
'dist/',
'node_modules/',
'*.scss'
],
rules: { rules: {
'no-case-declarations': 'off', 'no-case-declarations': 'off',
'@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/ban-types': 'off', '@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'warn',
'prefer-const': 'warn',
'@typescript-eslint/no-var-requires': 'error',
}, },
}; };

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
[dockview.dev](https://dockview.dev) provides a number of examples with Code Sandbox templates. Are you able to produce the bug by forking one of those templates? Sharing a link to the forked sandbox with the bug would be extremely helpful.
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

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

View File

@ -1,24 +1,20 @@
name: Deploy Docs name: Deploy Docs
on: on:
schedule: workflow_dispatch:
- cron: '0 3 * * *' # every day at 3 am UTC
jobs: jobs:
deploy-nightly-demo-app: deploy-nightly-demo-app:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout 🛎️ - name: Checkout 🛎️
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. uses: actions/checkout@v4
with:
persist-credentials: false
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v1 uses: actions/setup-node@v4
with: with:
node-version: '16.x' node-version: '20.x'
- uses: actions/cache@v2 - uses: actions/cache@v4
with: with:
path: ~/.npm path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
@ -26,15 +22,20 @@ jobs:
${{ runner.os }}-node- ${{ runner.os }}-node-
- run: yarn install - run: yarn install
- run: lerna bootstrap
- run: npm run build - run: npm run build
working-directory: packages/dockview-core working-directory: packages/dockview-core
- run: npm run build - run: npm run build
working-directory: packages/dockview 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 - run: npm run build
working-directory: packages/docs working-directory: packages/docs
- run: npm run deploy-docs - run: npm run docs
working-directory: packages/docs working-directory: .
- run: node scripts/package-docs.js
working-directory: .
- name: Deploy 🚀 - name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.7.1 uses: JamesIves/github-pages-deploy-action@3.7.1
with: with:

View File

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

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

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

2
.gitignore vendored
View File

@ -13,3 +13,5 @@ test-report.xml
*.code-workspace *.code-workspace
yarn-error.log yarn-error.log
/build /build
/docs/
/generated/

View File

@ -7,7 +7,8 @@
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"redhat.vscode-yaml", "redhat.vscode-yaml",
"dbaeumer.vscode-eslint", "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. // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [] "unwantedRecommendations": []

153
CLAUDE.md Normal file
View File

@ -0,0 +1,153 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Dockview is a zero-dependency layout manager supporting tabs, groups, grids and splitviews. It provides framework support for React, Vue, and vanilla TypeScript. The project is organized as a Lerna monorepo with multiple packages.
## Development Commands
### Build
- `npm run build` - Build core packages (dockview-core, dockview, dockview-vue, dockview-react)
- `npm run clean` - Clean all packages
### Testing
- `npm test` - Run Jest tests across all packages
- `npm run test:cov` - Run tests with coverage reporting
### Linting
- `npm run lint` - Run ESLint on TypeScript/JavaScript source files across packages
- `npm run lint:fix` - Run ESLint with automatic fixing of fixable issues
### Documentation
- `npm run docs` - Generate documentation using custom script
### Package Management
- `npm run version` - Version packages using Lerna
- `npm run build:bundle` - Package build artifacts
- `npm run generate-docs` - Package documentation
## Architecture
### Monorepo Structure
- **packages/dockview-core** - Core layout engine (TypeScript, framework-agnostic)
- **packages/dockview** - React bindings and components
- **packages/dockview-vue** - Vue bindings and components
- **packages/dockview-angular** - Angular bindings and components
- **packages/dockview-react** - Additional React utilities
- **packages/docs** - Documentation website (Docusaurus)
### Key Components
#### Core Architecture (dockview-core)
- **DockviewComponent** - Main container managing panels and groups
- **DockviewGroupPanel** - Container for related panels with tabs
- **DockviewPanel** - Individual content panels
- **Gridview/Splitview/Paneview** - Different layout strategies
- **API Layer** - Programmatic interfaces for each component type
#### Framework Integration
- Framework-specific packages provide thin wrappers around core components
- React package uses HOCs and hooks for component lifecycle management
- Vue package provides Vue 3 composition API integration
- All frameworks share the same core serialization/deserialization logic
#### Key Features
- Drag and drop with customizable drop zones
- Floating groups and popout windows
- Serialization/deserialization for state persistence
- Theming system with CSS custom properties
- Comprehensive API for programmatic control
### Testing Strategy
- Jest with ts-jest preset for TypeScript support
- Testing Library for React component testing
- Coverage reporting with SonarCloud integration
- Each package has its own jest.config.ts extending root configuration
### Build System
- Lerna for monorepo management
- Rollup for package bundling
- TypeScript for type checking and compilation
- Gulp for additional build tasks (SCSS processing)
### Code Quality
- ESLint configuration extends recommended TypeScript rules
- Linting targets source files in packages/\*/src/\*\* (excludes tests, docs, node_modules)
- Configuration centralized in .eslintrc.js with ignore patterns
- Current rules focus on TypeScript best practices while allowing some flexibility
- Most linting issues require manual fixes (type specifications, unused variables, null assertions)
## Development Notes
### Working with Packages
- Use Lerna commands for cross-package operations
- Each package can be built independently
- Core package must be built before framework packages
- Use workspaces for dependency management
### Adding New Features
- Start with core package implementation
- Add corresponding API methods in api/ directory
- Create framework-specific wrappers as needed
- Update TypeDoc documentation
- Add tests in **tests** directories
- Run `npm run lint` to check code quality before committing
### State Management
- Components use internal state with event-driven updates
- Serialization provides snapshot-based state persistence
- APIs provide reactive interfaces with event subscriptions
## Release Management
### Creating Release Notes
Release notes are stored in `packages/docs/blog/` with the naming format `YYYY-MM-DD-dockview-X.Y.Z.md`.
To create release notes for a new version:
1. Check git commits since the last release: `git log --oneline --since="YYYY-MM-DD"`
2. Create a new markdown file following the established format:
- Front matter with slug, title, and tags
- Sections for Features (🚀), Miscs (🛠), and Breaking changes (🔥)
- Reference GitHub PR numbers for significant changes
- Focus on user-facing changes, bug fixes, and new features
Example format:
```markdown
---
slug: dockview-X.Y.Z-release
title: Dockview X.Y.Z
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
- Feature description [#PR](link)
## 🛠 Miscs
- Bug: Fix description [#PR](link)
- Chore: Maintenance description [#PR](link)
## 🔥 Breaking changes
```

View File

@ -1,52 +1,38 @@
<div align="center"> <div align="center">
<h1>dockview</h1> <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> </div>
--- ---
[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview) [![npm version](https://badge.fury.io/js/dockview-core.svg)](https://www.npmjs.com/package/dockview-core)
[![npm](https://img.shields.io/npm/dm/dockview-core)](https://www.npmjs.com/package/dockview-core)
[![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI) [![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview)](https://bundlephobia.com/result?p=dockview) [![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview-core)](https://bundlephobia.com/result?p=dockview-core)
## ##
Please see the website: https://dockview.dev ![](packages/docs/static/img/splashscreen.gif)
Want to inspect the latest deployment? Go to https://unpkg.com/browse/dockview@latest/ Please see the website: https://dockview.dev
## Features ## Features
- Simple splitviews, nested splitviews (i.e. gridviews) supporting full layout managment with - Serialization / deserialization with full layout management
dockable and tabular views - Support for split-views, grid-views and 'dockable' views
- Extensive API support at the component level and view level - Themeable and customizable
- Themable and customizable - Tab and Group docking / Drag n' Drop
- Serialization / deserialization support - Popout Windows
- Tabular docking and Drag and Drop support - Floating Groups
- Documentation and examples - 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
This project was inspired by many popular IDE editors. Some parts of the core resizable panelling are inspired by code found in the VSCode codebase, [splitview](https://github.com/microsoft/vscode/tree/main/src/vs/base/browser/ui/splitview) and [gridview](https://github.com/microsoft/vscode/tree/main/src/vs/base/browser/ui/grid). Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#user-content-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). Please see the [Getting Started Guide](https://mathuo.github.io/dockview/docs/).
```
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>
```

8
SECURITY.md Normal file
View File

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

1
jest-setup.ts Normal file
View File

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

View File

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

View File

@ -2,8 +2,7 @@
"packages": [ "packages": [
"packages/*" "packages/*"
], ],
"useWorkspaces": true, "version": "4.7.0",
"version": "1.7.0",
"npmClient": "yarn", "npmClient": "yarn",
"command": { "command": {
"publish": { "publish": {

View File

@ -1,24 +0,0 @@
{
"compilerOptions": {
"module": "ES2020",
"declaration": true,
"target": "es6",
"moduleResolution": "node",
"esModuleInterop": true,
"downlevelIteration": true,
"incremental": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"allowUnreachableCode": false,
"forceConsistentCasingInFileNames": true,
"strict": true,
"lib": [
"ES2015",
"ES2016.Array.Include",
"ES2017.String",
"ES2018.Promise",
"ES2019",
"DOM",
]
}
}

View File

@ -1,68 +1,79 @@
{ {
"name": "dockview-monorepo-root", "name": "dockview-monorepo-root",
"private": true, "private": true,
"workspaces": [
"packages/*"
],
"nohoist": [
"**/babel-jest",
"**/babel-jest/**"
],
"description": "Monorepo for https://github.com/mathuo/dockview", "description": "Monorepo for https://github.com/mathuo/dockview",
"scripts": { "homepage": "https://github.com/mathuo/dockview#readme",
"test": "jest", "bugs": {
"lint": "eslint packages/**/src/** --ext .ts,.tsx,.js,.jsx", "url": "https://github.com/mathuo/dockview/issues"
"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"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/mathuo/dockview.git" "url": "git+https://github.com/mathuo/dockview.git"
}, },
"author": "https://github.com/mathuo",
"license": "MIT", "license": "MIT",
"bugs": { "author": "https://github.com/mathuo",
"url": "https://github.com/mathuo/dockview/issues" "workspaces": [
"packages/*"
],
"scripts": {
"build": "lerna run build --scope '{dockview-core,dockview,dockview-vue,dockview-react}'",
"build:bundle": "lerna run build:bundle --scope '{dockview-core,dockview,dockview-vue,dockview-react}'",
"clean": "lerna run clean",
"docs": "typedoc && node scripts/docs.mjs",
"lint": "eslint 'packages/*/src/**/*.{ts,tsx,js,jsx}'",
"lint:fix": "eslint 'packages/*/src/**/*.{ts,tsx,js,jsx}' --fix",
"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": { "devDependencies": {
"@testing-library/dom": "^8.20.0", "@rollup/plugin-node-resolve": "^15.2.3",
"@types/jest": "^29.4.0", "@rollup/plugin-terser": "^0.4.4",
"@typescript-eslint/eslint-plugin": "^5.52.0", "@rollup/plugin-typescript": "^11.1.5",
"@typescript-eslint/parser": "^5.52.0", "@testing-library/dom": "^9.3.3",
"codecov": "^3.8.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", "cross-env": "^7.0.3",
"css-loader": "^6.7.3", "eslint": "^8.56.0",
"eslint": "^8.34.0", "fs-extra": "^11.2.0",
"fs-extra": "^11.1.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
"gulp-dart-sass": "^1.0.2", "gulp-dart-sass": "^1.1.0",
"jest-environment-jsdom": "^29.4.3", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-sonar-reporter": "^2.0.0", "jest-sonar-reporter": "^2.0.0",
"jsdom": "^21.1.0", "jsdom": "^23.0.1",
"lerna": "^6.5.1", "lerna": "^8.2.1",
"merge2": "^1.4.1", "react": "^18.2.0",
"rimraf": "^4.1.2", "react-dom": "^18.2.0",
"sass": "^1.58.1", "rimraf": "^5.0.5",
"sass-loader": "^13.2.0", "rollup": "^4.9.2",
"style-loader": "^3.3.1", "rollup-plugin-postcss": "^4.0.2",
"ts-jest": "^29.0.5", "ts-jest": "^29.1.1",
"ts-loader": "^9.4.2", "ts-loader": "^9.5.1",
"tslib": "^2.5.0", "ts-node": "^10.9.2",
"typescript": "^4.9.5", "tslib": "^2.6.2",
"webpack": "^5.75.0", "typedoc": "^0.25.6",
"webpack-cli": "^5.0.1", "typescript": "^5.3.3",
"webpack-dev-server": "^4.11.1" "vite": "^5.1.5",
"vue": "^3.4.21",
"vue-sfc-loader": "^0.1.0",
"vue-tsc": "^2.0.5"
}, },
"dependencies": { "engines": {
"jest": "^29.5.0", "node": ">=18.0"
"ts-node": "^10.9.1"
} }
} }

View File

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

View File

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

View File

@ -0,0 +1,58 @@
{
"name": "dockview-angular",
"version": "4.7.0",
"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:bundle": "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": "npm run build:cjs && npm run build:esm && npm run build:css",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run build:bundle && 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.7.0"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,14 @@
<div align="center"> <div align="center">
<h1>dockview</h1> <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> </div>
--- ---
[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview) [![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview)
[![npm](https://img.shields.io/npm/dm/dockview)](https://www.npmjs.com/package/dockview)
[![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI) [![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
@ -15,38 +16,23 @@
## ##
Please see the website: https://dockview.dev ![](packages/docs/static/img/splashscreen.gif)
Want to inspect the latest deployment? Go to https://unpkg.com/browse/dockview@latest/ Please see the website: https://dockview.dev
## Features ## Features
- Simple splitviews, nested splitviews (i.e. gridviews) supporting full layout managment with - Serialization / deserialization with full layout management
dockable and tabular views - Support for split-views, grid-views and 'dockable' views
- Extensive API support at the component level and view level - Themeable and customizable
- Themable and customizable - Tab and Group docking / Drag n' Drop
- Serialization / deserialization support - Popout Windows
- Tabular docking and Drag and Drop support - Floating Groups
- Documentation and examples - 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
This project was inspired by many popular IDE editors. Some parts of the core resizable panelling are inspired by code found in the VSCode codebase, [splitview](https://github.com/microsoft/vscode/tree/main/src/vs/base/browser/ui/splitview) and [gridview](https://github.com/microsoft/vscode/tree/main/src/vs/base/browser/ui/grid). 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). Please see the [Getting Started Guide](https://mathuo.github.io/dockview/docs/).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

@ -1,6 +1,13 @@
const gulp = require('gulp'); 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'])); gulp.task('run', gulp.series(['sass']));

View File

@ -12,6 +12,7 @@ const config: JestConfigWithTsJest = {
setupFiles: [ setupFiles: [
'<rootDir>/packages/dockview-core/src/__tests__/__mocks__/resizeObserver.js', '<rootDir>/packages/dockview-core/src/__tests__/__mocks__/resizeObserver.js',
], ],
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
coveragePathIgnorePatterns: ['/node_modules/'], coveragePathIgnorePatterns: ['/node_modules/'],
modulePathIgnorePatterns: [ modulePathIgnorePatterns: [
'<rootDir>/packages/dockview-core/src/__tests__/__mocks__', '<rootDir>/packages/dockview-core/src/__tests__/__mocks__',
@ -20,6 +21,14 @@ const config: JestConfigWithTsJest = {
coverageDirectory: '<rootDir>/packages/dockview-core/coverage/', coverageDirectory: '<rootDir>/packages/dockview-core/coverage/',
testResultsProcessor: 'jest-sonar-reporter', testResultsProcessor: 'jest-sonar-reporter',
testEnvironment: 'jsdom', testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.test.json',
},
],
},
}; };
export default config; export default config;

View File

@ -1,37 +1,7 @@
{ {
"name": "dockview-core", "name": "dockview-core",
"version": "1.7.0", "version": "4.7.0",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"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:ci": "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:modulefiles": "rollup -c",
"build": "npm run build:ci && npm run build:modulefiles",
"clean": "rimraf dist/ .build/",
"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"
],
"keywords": [ "keywords": [
"splitview", "splitview",
"split-view", "split-view",
@ -53,16 +23,33 @@
"react", "react",
"react-component" "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", "license": "MIT",
"devDependencies": { "author": "https://github.com/mathuo",
"@rollup/plugin-typescript": "^11.0.0", "main": "./dist/cjs/index.js",
"cross-env": "^7.0.3", "module": "./dist/esm/index.js",
"postcss": "^8.4.21", "types": "./dist/cjs/index.d.ts",
"rimraf": "^4.1.2", "files": [
"rollup": "^3.15.0", "dist",
"rollup-plugin-postcss": "^4.0.2", "README.md"
"rollup-plugin-terser": "^7.0.2", ],
"typedoc": "^0.23.25" "scripts": {
"build:bundle": "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": "npm run build:cjs && npm run build:esm && npm run build:css",
"clean": "rimraf dist/ .build/ .rollup.cache/",
"prepublishOnly": "npm run rebuild && npm run build:bundle && npm run test",
"rebuild": "npm run clean && npm run build",
"test": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core",
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-core --coverage"
} }
} }

View File

@ -2,7 +2,7 @@
const { join } = require('path'); const { join } = require('path');
const typescript = require('@rollup/plugin-typescript'); const typescript = require('@rollup/plugin-typescript');
const { terser } = require('rollup-plugin-terser'); const terser = require('@rollup/plugin-terser');
const postcss = require('rollup-plugin-postcss'); const postcss = require('rollup-plugin-postcss');
const { name, version, homepage, license } = require('./package.json'); const { name, version, homepage, license } = require('./package.json');
@ -46,6 +46,7 @@ function createBundle(format, options) {
const output = { const output = {
file, file,
format, format,
sourcemap: true,
globals: {}, globals: {},
banner: [ banner: [
`/**`, `/**`,
@ -60,10 +61,6 @@ function createBundle(format, options) {
const plugins = [ const plugins = [
typescript({ typescript({
tsconfig: 'tsconfig.esm.json', tsconfig: 'tsconfig.esm.json',
incremental: false,
tsBuildInfoFile: undefined,
outDir: undefined,
declaration: false,
}), }),
]; ];

View File

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

View File

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

View File

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

View File

@ -1,4 +1,13 @@
import * as React from 'react'; import React from 'react';
/**
* useful utility type to erase readonly signatures for testing purposes
*
* @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#readonly-mapped-type-modifiers-and-readonly-arrays
*/
export type Writable<T> = T extends object
? { -readonly [K in keyof T]: Writable<T[K]> }
: T;
export function setMockRefElement(node: Partial<HTMLElement>): void { export function setMockRefElement(node: Partial<HTMLElement>): void {
const mockRef = { const mockRef = {
@ -12,3 +21,59 @@ export function setMockRefElement(node: Partial<HTMLElement>): void {
jest.spyOn(React, 'useRef').mockReturnValueOnce(mockRef); 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 function exhaustAnimationFrame(): Promise<void> {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => resolve());
});
}
export const mockGetBoundingClientRect = ({
left,
top,
height,
width,
}: {
left: number;
top: number;
height: number;
width: number;
}) => {
const result = {
left,
top,
height,
width,
right: left + width,
bottom: top + height,
x: left,
y: top,
};
return {
...result,
toJSON: () => result,
};
};

View File

@ -1,13 +1,30 @@
import { PanelApiImpl } from '../../api/panelApi'; import { PanelApiImpl } from '../../api/panelApi';
import { IPanel } from '../../panel/types';
describe('api', () => { describe('api', () => {
let api: PanelApiImpl; let api: PanelApiImpl;
beforeEach(() => { beforeEach(() => {
api = new PanelApiImpl('dummy_id'); api = new PanelApiImpl('dummy_id', 'fake-component');
}); });
it('should update isFcoused getter', () => { test('updateParameters', () => {
const panel = {
update: jest.fn(),
} as Partial<IPanel>;
api.initialize(panel as IPanel);
expect(panel.update).toHaveBeenCalledTimes(0);
api.updateParameters({ keyA: 'valueA' });
expect(panel.update).toHaveBeenCalledTimes(1);
expect(panel.update).toHaveBeenCalledWith({
params: { keyA: 'valueA' },
});
});
test('should update isFcoused getter', () => {
expect(api.isFocused).toBeFalsy(); expect(api.isFocused).toBeFalsy();
api._onDidChangeFocus.fire({ isFocused: true }); api._onDidChangeFocus.fire({ isFocused: true });
@ -17,7 +34,7 @@ describe('api', () => {
expect(api.isFocused).toBeFalsy(); expect(api.isFocused).toBeFalsy();
}); });
it('should update isActive getter', () => { test('should update isActive getter', () => {
expect(api.isFocused).toBeFalsy(); expect(api.isFocused).toBeFalsy();
api._onDidActiveChange.fire({ isActive: true }); api._onDidActiveChange.fire({ isActive: true });
@ -27,7 +44,7 @@ describe('api', () => {
expect(api.isActive).toBeFalsy(); expect(api.isActive).toBeFalsy();
}); });
it('should update isActive getter', () => { test('should update isActive getter', () => {
expect(api.isVisible).toBeTruthy(); expect(api.isVisible).toBeTruthy();
api._onDidVisibilityChange.fire({ isVisible: false }); api._onDidVisibilityChange.fire({ isVisible: false });
@ -37,7 +54,7 @@ describe('api', () => {
expect(api.isVisible).toBeTruthy(); expect(api.isVisible).toBeTruthy();
}); });
it('should update width and height getter', () => { test('should update width and height getter', () => {
expect(api.height).toBe(0); expect(api.height).toBe(0);
expect(api.width).toBe(0); expect(api.width).toBe(0);

View File

@ -1,42 +1,59 @@
import { DockviewPanelApiImpl, TitleEvent } from '../../api/dockviewPanelApi'; import { DockviewPanelApiImpl } from '../../api/dockviewPanelApi';
import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewComponent } from '../../dockview/dockviewComponent';
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewPanel } from '../../dockview/dockviewPanel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { fromPartial } from '@total-typescript/shoehorn';
describe('groupPanelApi', () => { describe('groupPanelApi', () => {
test('title', () => { test('title', () => {
const panelMock = jest.fn<DockviewPanel, []>(() => { const accessor = fromPartial<DockviewComponent>({
return {
update: jest.fn(),
} as any;
});
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panel = new panelMock();
const group = new groupMock();
const cut = new DockviewPanelApiImpl(panel, group);
cut.setTitle('test_title');
expect(panel.update).toBeCalledTimes(1);
expect(panel.update).toBeCalledWith({
params: { title: 'test_title' },
});
});
test('onDidGroupChange', () => {
const groupPanel: Partial<IDockviewPanel> = {
id: 'test_id',
};
const accessor: Partial<DockviewComponent> = {
onDidAddPanel: jest.fn(), onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(), onDidRemovePanel: jest.fn(),
options: {}, options: {},
onDidOptionsChange: jest.fn(),
});
const panelMock = jest.fn<DockviewPanel, []>(() => {
return {
update: jest.fn(),
setTitle: jest.fn(),
} as any;
});
const panel = new panelMock();
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
},
});
const cut = new DockviewPanelApiImpl(
panel,
group,
<DockviewComponent>accessor,
'fake-component'
);
cut.setTitle('test_title');
expect(panel.setTitle).toBeCalledTimes(1);
expect(panel.setTitle).toBeCalledWith('test_title');
});
test('updateParameters', () => {
const groupPanel: Partial<DockviewPanel> = {
id: 'test_id',
update: jest.fn(),
}; };
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupViewPanel = new DockviewGroupPanel( const groupViewPanel = new DockviewGroupPanel(
<DockviewComponent>accessor, <DockviewComponent>accessor,
'', '',
@ -44,8 +61,43 @@ describe('groupPanelApi', () => {
); );
const cut = new DockviewPanelApiImpl( const cut = new DockviewPanelApiImpl(
<IDockviewPanel>groupPanel, <DockviewPanel>groupPanel,
<DockviewGroupPanel>groupViewPanel <DockviewGroupPanel>groupViewPanel,
<DockviewComponent>accessor,
'fake-component'
);
cut.updateParameters({ keyA: 'valueA' });
expect(groupPanel.update).toHaveBeenCalledWith({
params: { keyA: 'valueA' },
});
expect(groupPanel.update).toHaveBeenCalledTimes(1);
});
test('onDidGroupChange', () => {
const groupPanel: Partial<DockviewPanel> = {
id: 'test_id',
};
const accessor = fromPartial<DockviewComponent>({
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
onDidOptionsChange: jest.fn(),
});
const groupViewPanel = new DockviewGroupPanel(
<DockviewComponent>accessor,
'',
{}
);
const cut = new DockviewPanelApiImpl(
<DockviewPanel>groupPanel,
<DockviewGroupPanel>groupViewPanel,
<DockviewComponent>accessor,
'fake-component'
); );
let events = 0; let events = 0;

View File

@ -3,6 +3,7 @@ import {
last, last,
pushToEnd, pushToEnd,
pushToStart, pushToStart,
remove,
sequenceEquals, sequenceEquals,
tail, tail,
} from '../array'; } from '../array';
@ -47,4 +48,22 @@ describe('array', () => {
expect(sequenceEquals([1, 2, 3, 4], [1, 2, 3])).toBeFalsy(); expect(sequenceEquals([1, 2, 3, 4], [1, 2, 3])).toBeFalsy();
expect(sequenceEquals([1, 2, 3, 4], [1, 2, 3, 4, 5])).toBeFalsy(); expect(sequenceEquals([1, 2, 3, 4], [1, 2, 3, 4, 5])).toBeFalsy();
}); });
test('remove', () => {
const arr1 = [1, 2, 3, 4];
remove(arr1, 2);
expect(arr1).toEqual([1, 3, 4]);
const arr2 = [1, 2, 2, 3, 4];
remove(arr2, 2);
expect(arr2).toEqual([1, 2, 3, 4]);
const arr3 = [1];
remove(arr3, 2);
expect(arr3).toEqual([1]);
remove(arr3, 1);
expect(arr3).toEqual([]);
remove(arr3, 1);
expect(arr3).toEqual([]);
});
}); });

View File

@ -20,10 +20,6 @@ describe('abstractDragHandler', () => {
}, },
}; };
} }
dispose(): void {
super.dispose();
}
})(element); })(element);
expect(element.classList.contains('dv-dragged')).toBeFalsy(); expect(element.classList.contains('dv-dragged')).toBeFalsy();
@ -62,10 +58,6 @@ describe('abstractDragHandler', () => {
}, },
}; };
} }
dispose(): void {
//
}
})(element); })(element);
expect(iframe.style.pointerEvents).toBeFalsy(); expect(iframe.style.pointerEvents).toBeFalsy();
@ -78,10 +70,110 @@ describe('abstractDragHandler', () => {
expect(span.style.pointerEvents).toBeFalsy(); expect(span.style.pointerEvents).toBeFalsy();
fireEvent.dragEnd(element); fireEvent.dragEnd(element);
expect(iframe.style.pointerEvents).toBe('auto'); expect(iframe.style.pointerEvents).toBe('');
expect(webview.style.pointerEvents).toBe('auto'); expect(webview.style.pointerEvents).toBe('');
expect(span.style.pointerEvents).toBeFalsy(); expect(span.style.pointerEvents).toBeFalsy();
handler.dispose(); handler.dispose();
}); });
test('that the disabling of pointerEvents is restored on a premature disposal of the handler', () => {
jest.useFakeTimers();
const element = document.createElement('div');
const iframe = document.createElement('iframe');
const webview = document.createElement('webview');
const span = document.createElement('span');
document.body.appendChild(element);
document.body.appendChild(iframe);
document.body.appendChild(webview);
document.body.appendChild(span);
const handler = new (class TestClass extends DragHandler {
constructor(el: HTMLElement) {
super(el);
}
getData(): IDisposable {
return {
dispose: () => {
// /
},
};
}
})(element);
expect(iframe.style.pointerEvents).toBeFalsy();
expect(webview.style.pointerEvents).toBeFalsy();
expect(span.style.pointerEvents).toBeFalsy();
fireEvent.dragStart(element);
expect(iframe.style.pointerEvents).toBe('none');
expect(webview.style.pointerEvents).toBe('none');
expect(span.style.pointerEvents).toBeFalsy();
handler.dispose();
expect(iframe.style.pointerEvents).toBe('');
expect(webview.style.pointerEvents).toBe('');
expect(span.style.pointerEvents).toBeFalsy();
});
test('that .preventDefault() is called for cancelled events', () => {
const element = document.createElement('div');
const handler = new (class TestClass extends DragHandler {
constructor(el: HTMLElement) {
super(el);
}
protected isCancelled(_event: DragEvent): boolean {
return true;
}
getData(): IDisposable {
return {
dispose: () => {
// /
},
};
}
})(element);
const event = new Event('dragstart');
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(1);
handler.dispose();
});
test('that .preventDefault() is not called for non-cancelled events', () => {
const element = document.createElement('div');
const handler = new (class TestClass extends DragHandler {
constructor(el: HTMLElement) {
super(el);
}
protected isCancelled(_event: DragEvent): boolean {
return false;
}
getData(): IDisposable {
return {
dispose: () => {
// /
},
};
}
})(element);
const event = new Event('dragstart');
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toHaveBeenCalledTimes(0);
handler.dispose();
});
}); });

View File

@ -7,19 +7,7 @@ import {
positionToDirection, positionToDirection,
} from '../../dnd/droptarget'; } from '../../dnd/droptarget';
import { fireEvent } from '@testing-library/dom'; import { fireEvent } from '@testing-library/dom';
import { createOffsetDragOverEvent } from '../__test_utils__/utils';
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;
}
describe('droptarget', () => { describe('droptarget', () => {
let element: HTMLElement; let element: HTMLElement;
@ -28,10 +16,52 @@ describe('droptarget', () => {
beforeEach(() => { beforeEach(() => {
element = document.createElement('div'); element = document.createElement('div');
jest.spyOn(element, 'clientHeight', 'get').mockImplementation( jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100 () => 100
); );
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200); jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 200);
});
test('that dragover events are marked', () => {
droptarget = new Droptarget(element, {
canDisplayOverlay: () => true,
acceptedTargetZones: ['center'],
});
fireEvent.dragEnter(element);
const event = new Event('dragover');
fireEvent(element, event);
expect(
(event as any)['__dockview_droptarget_event_is_used__']
).toBeTruthy();
});
test('that the drop target is removed when receiving a marked dragover event', () => {
let position: Position | undefined = undefined;
droptarget = new Droptarget(element, {
canDisplayOverlay: () => true,
acceptedTargetZones: ['center'],
});
droptarget.onDrop((event) => {
position = event.position;
});
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
const target = element.querySelector(
'.dv-drop-target-dropzone'
) as HTMLElement;
fireEvent.drop(target);
expect(position).toBe('center');
const event = new Event('dragover');
(event as any)['__dockview_droptarget_event_is_used__'] = true;
fireEvent(element, event);
expect(element.querySelector('.dv-drop-target-dropzone')).toBeNull();
}); });
test('directionToPosition', () => { test('directionToPosition', () => {
@ -72,7 +102,7 @@ describe('droptarget', () => {
fireEvent.dragOver(element); fireEvent.dragOver(element);
const target = element.querySelector( const target = element.querySelector(
'.drop-target-dropzone' '.dv-drop-target-dropzone'
) as HTMLElement; ) as HTMLElement;
fireEvent.drop(target); fireEvent.drop(target);
expect(position).toBe('center'); expect(position).toBe('center');
@ -94,7 +124,7 @@ describe('droptarget', () => {
fireEvent.dragOver(element); fireEvent.dragOver(element);
const target = element.querySelector( const target = element.querySelector(
'.drop-target-dropzone' '.dv-drop-target-dropzone'
) as HTMLElement; ) as HTMLElement;
jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100); jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100);
@ -125,12 +155,12 @@ describe('droptarget', () => {
fireEvent.dragOver(element); fireEvent.dragOver(element);
let viewQuery = element.querySelectorAll( 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); expect(viewQuery.length).toBe(1);
const target = element.querySelector( const target = element.querySelector(
'.drop-target-dropzone' '.dv-drop-target-dropzone'
) as HTMLElement; ) as HTMLElement;
jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100); jest.spyOn(target, 'clientHeight', 'get').mockImplementation(() => 100);
@ -141,18 +171,40 @@ describe('droptarget', () => {
createOffsetDragOverEvent({ clientX: 19, clientY: 0 }) createOffsetDragOverEvent({ clientX: 19, clientY: 0 })
); );
function check(
element: HTMLElement,
box: {
left: string;
top: string;
width: string;
height: string;
}
) {
// Check positioning (back to top/left with GPU layer maintained)
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);
// Ensure GPU layer is maintained
expect(element.style.transform).toBe('translate3d(0, 0, 0)');
}
viewQuery = element.querySelectorAll( 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(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('left'); expect(droptarget.state).toBe('left');
expect( check(
( element
element .getElementsByClassName('dv-drop-target-selection')
.getElementsByClassName('drop-target-selection') .item(0) as HTMLDivElement,
.item(0) as HTMLDivElement {
).style.transform top: '0px',
).toBe('translateX(-25%) scaleX(0.5)'); left: '0px',
width: '50%',
height: '100%',
}
);
fireEvent( fireEvent(
target, target,
@ -160,17 +212,21 @@ describe('droptarget', () => {
); );
viewQuery = element.querySelectorAll( 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(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('top'); expect(droptarget.state).toBe('top');
expect( check(
( element
element .getElementsByClassName('dv-drop-target-selection')
.getElementsByClassName('drop-target-selection') .item(0) as HTMLDivElement,
.item(0) as HTMLDivElement {
).style.transform top: '0px',
).toBe('translateY(-25%) scaleY(0.5)'); left: '0px',
width: '100%',
height: '50%',
}
);
fireEvent( fireEvent(
target, target,
@ -178,17 +234,21 @@ describe('droptarget', () => {
); );
viewQuery = element.querySelectorAll( 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(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('bottom'); expect(droptarget.state).toBe('bottom');
expect( check(
( element
element .getElementsByClassName('dv-drop-target-selection')
.getElementsByClassName('drop-target-selection') .item(0) as HTMLDivElement,
.item(0) as HTMLDivElement {
).style.transform top: '50%',
).toBe('translateY(25%) scaleY(0.5)'); left: '0px',
width: '100%',
height: '50%',
}
);
fireEvent( fireEvent(
target, target,
@ -196,34 +256,38 @@ describe('droptarget', () => {
); );
viewQuery = element.querySelectorAll( 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(viewQuery.length).toBe(1);
expect(droptarget.state).toBe('right'); expect(droptarget.state).toBe('right');
expect( check(
( element
element .getElementsByClassName('dv-drop-target-selection')
.getElementsByClassName('drop-target-selection') .item(0) as HTMLDivElement,
.item(0) as HTMLDivElement {
).style.transform top: '0px',
).toBe('translateX(25%) scaleX(0.5)'); left: '50%',
width: '50%',
height: '100%',
}
);
fireEvent( fireEvent(
target, target,
createOffsetDragOverEvent({ clientX: 100, clientY: 50 }) createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
); );
expect(droptarget.state).toBe('center'); expect(droptarget.state).toBe('center');
// With GPU optimizations, elements always have a base transform layer
expect( expect(
( (
element element
.getElementsByClassName('drop-target-selection') .getElementsByClassName('dv-drop-target-selection')
.item(0) as HTMLDivElement .item(0) as HTMLDivElement
).style.transform ).style.transform
).toBe(''); ).toBe('translate3d(0, 0, 0)');
fireEvent.dragLeave(target); fireEvent.dragLeave(target);
expect(droptarget.state).toBe('center'); expect(droptarget.state).toBe('center');
viewQuery = element.querySelectorAll('.drop-target'); viewQuery = element.querySelectorAll('.dv-drop-target');
expect(viewQuery.length).toBe(0); expect(viewQuery.length).toBe(0);
}); });

View File

@ -0,0 +1,114 @@
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', () => {
const element = document.createElement('div');
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
id: 'test_group_id',
api: { location: { type: 'grid' } } as any,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(
element,
{ id: 'test_accessor_id' } as DockviewComponent,
group
);
fireEvent.dragStart(element, new Event('dragstart'));
expect(
LocalSelectionTransfer.getInstance<PanelTransfer>().hasData(
PanelTransfer.prototype
)
).toBeTruthy();
const transferObject =
LocalSelectionTransfer.getInstance<PanelTransfer>().getData(
PanelTransfer.prototype
)![0];
expect(transferObject).toBeTruthy();
expect(transferObject.viewId).toBe('test_accessor_id');
expect(transferObject.groupId).toBe('test_group_id');
expect(transferObject.panelId).toBeNull();
fireEvent.dragStart(element, new Event('dragend'));
expect(
LocalSelectionTransfer.getInstance<PanelTransfer>().hasData(
PanelTransfer.prototype
)
).toBeFalsy();
cut.dispose();
});
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: { location: { type: 'floating' } } as any,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(
element,
{ id: 'accessor_id' } as DockviewComponent,
group
);
const event = new KeyboardEvent('dragstart', { shiftKey: false });
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(1);
const event2 = new KeyboardEvent('dragstart', { shiftKey: true });
const spy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(element, event);
expect(spy2).toBeCalledTimes(0);
cut.dispose();
});
test('that the event is never cancelled when the group is not floating', () => {
const element = document.createElement('div');
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
api: { location: { type: 'grid' } } as any,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(
element,
{ id: 'accessor_id' } as DockviewComponent,
group
);
const event = new KeyboardEvent('dragstart', { shiftKey: false });
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(0);
const event2 = new KeyboardEvent('dragstart', { shiftKey: true });
const spy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(element, event);
expect(spy2).toBeCalledTimes(0);
cut.dispose();
});
});

View File

@ -0,0 +1,174 @@
import { fireEvent } from '@testing-library/dom';
import { ContentContainer } from '../../../../dockview/components/panel/content';
import {
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
implements IContentRenderer
{
readonly element: HTMLElement;
constructor(public id: string) {
super();
this.element = document.createElement('div');
this.element.id = id;
}
init(parameters: GroupPanelPartInitParameters): void {
//
}
layout(width: number, height: number): void {
//
}
update(event: PanelUpdateEvent): void {
//
}
toJSON(): object {
return {};
}
focus(): void {
//
}
}
describe('contentContainer', () => {
beforeEach(() => {
jest.useFakeTimers();
});
test('basic focus test', () => {
let focus = 0;
let blur = 0;
const disposable = new CompositeDisposable();
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(() => {
focus++;
}),
cut.onDidBlur(() => {
blur++;
})
);
const contentRenderer = new TestContentRenderer('id-1');
const panel = fromPartial<IDockviewPanel>({
view: {
content: contentRenderer,
},
api: { renderer: 'onlyWhenVisible' },
});
cut.openPanel(panel as IDockviewPanel);
expect(focus).toBe(0);
expect(blur).toBe(0);
// container has focus within
fireEvent.focus(contentRenderer.element);
expect(focus).toBe(1);
expect(blur).toBe(0);
// container looses focus
fireEvent.blur(contentRenderer.element);
jest.runAllTimers();
expect(focus).toBe(1);
expect(blur).toBe(1);
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(1);
// new panel recieves focus
fireEvent.focus(contentRenderer2.element);
expect(focus).toBe(2);
expect(blur).toBe(1);
// new panel looses focus
fireEvent.blur(contentRenderer2.element);
jest.runAllTimers();
expect(focus).toBe(2);
expect(blur).toBe(2);
disposable.dispose();
});
test("that panels renderered as 'onlyWhenVisible' are removed when closed", () => {
const overlayRenderContainer = fromPartial<OverlayRenderContainer>({
detatch: jest.fn(),
});
const cut = new ContentContainer(
fromPartial<DockviewComponent>({
overlayRenderContainer,
}),
fromPartial<DockviewGroupPanelModel>({
renderContainer: overlayRenderContainer,
})
);
const panel1 = fromPartial<IDockviewPanel>({
api: {
renderer: 'onlyWhenVisible',
},
view: { content: new TestContentRenderer('panel_1') },
});
const panel2 = fromPartial<IDockviewPanel>({
api: {
renderer: 'onlyWhenVisible',
},
view: { content: new TestContentRenderer('panel_2') },
});
cut.openPanel(panel1);
expect(panel1.view.content.element.parentElement).toBe(cut.element);
expect(cut.element.childNodes.length).toBe(1);
cut.openPanel(panel2);
expect(panel1.view.content.element.parentElement).toBeNull();
expect(panel2.view.content.element.parentElement).toBe(cut.element);
expect(cut.element.childNodes.length).toBe(1);
});
});

View File

@ -0,0 +1,342 @@
import { fireEvent } from '@testing-library/dom';
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 accessor = fromPartial<DockviewComponent>({
options: {}
});
const groupMock = jest.fn();
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
new groupMock()
);
expect(cut.element.className).toBe('dv-tab dv-inactive-tab');
});
test('that active tab has active-tab class', () => {
const accessor = fromPartial<DockviewComponent>({
options: {}
});
const groupMock = jest.fn();
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
new groupMock()
);
cut.setActive(true);
expect(cut.element.className).toBe('dv-tab dv-active-tab');
cut.setActive(false);
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', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {}
});
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanel = fromPartial<DockviewGroupPanel>({
id: 'testgroupid',
model: groupView,
});
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toHaveBeenCalled();
expect(
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
test('that if you drag over yourself a drop target is shown', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {}
});
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanel = fromPartial<DockviewGroupPanel>({
id: 'testgroupid',
model: groupView,
});
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
expect(
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
test('that if you drag over another tab a drop target is shown', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {}
});
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanel = fromPartial<DockviewGroupPanel>({
id: 'testgroupid',
model: groupView,
});
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel2')],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(1);
});
test('that dropping on a tab with the same id but from a different component should not render a drop over and call through to the group model', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {}
});
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanel = fromPartial<DockviewGroupPanel>({
id: 'testgroupid',
model: groupView,
});
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel1'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
test('that dropping on a tab from a different component should not render a drop over and call through to the group model', () => {
const accessor = fromPartial<DockviewComponent>({
id: 'testcomponentid',
options: {}
});
const groupView = fromPartial<DockviewGroupPanelModel>({
canDisplayOverlay: jest.fn(),
});
const groupPanel = fromPartial<DockviewGroupPanel>({
id: 'testgroupid',
model: groupView,
});
const cut = new Tab(
{ id: 'panel1' } as IDockviewPanel,
accessor,
groupPanel
);
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel2'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
describe('disableDnd option', () => {
test('that tab is draggable by default (disableDnd not set)', () => {
const accessor = fromPartial<DockviewComponent>({
options: {}
});
const groupMock = jest.fn();
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
new groupMock()
);
expect(cut.element.draggable).toBe(true);
});
test('that tab is draggable when disableDnd is false', () => {
const accessor = fromPartial<DockviewComponent>({
options: { disableDnd: false }
});
const groupMock = jest.fn();
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
new groupMock()
);
expect(cut.element.draggable).toBe(true);
});
test('that tab is not draggable when disableDnd is true', () => {
const accessor = fromPartial<DockviewComponent>({
options: { disableDnd: true }
});
const groupMock = jest.fn();
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
new groupMock()
);
expect(cut.element.draggable).toBe(false);
});
test('that updateDragAndDropState updates draggable attribute based on disableDnd option', () => {
const options = { disableDnd: false };
const accessor = fromPartial<DockviewComponent>({
options
});
const groupMock = jest.fn();
const cut = new Tab(
{ id: 'panelId' } as IDockviewPanel,
accessor,
new groupMock()
);
expect(cut.element.draggable).toBe(true);
// Simulate option change
options.disableDnd = true;
cut.updateDragAndDropState();
expect(cut.element.draggable).toBe(false);
// Change back
options.disableDnd = false;
cut.updateDragAndDropState();
expect(cut.element.draggable).toBe(true);
});
});
});

View File

@ -0,0 +1,140 @@
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);
});
test('that close button prevents default behavior', () => {
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');
// Create a custom event to verify preventDefault is called
const clickEvent = new Event('click', { cancelable: true });
const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault');
el!.dispatchEvent(clickEvent);
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
expect(api.close).toHaveBeenCalledTimes(1);
});
test('that close button respects already prevented events', () => {
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');
// Create a custom event and prevent it before dispatching
const clickEvent = new Event('click', { cancelable: true });
clickEvent.preventDefault();
el!.dispatchEvent(clickEvent);
// Close should not be called if event was already prevented
expect(api.close).not.toHaveBeenCalled();
});
test('that close button is visible by default', () => {
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') as HTMLElement;
expect(el).toBeTruthy();
expect(el.style.display).not.toBe('none');
});
});

View File

@ -0,0 +1,95 @@
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);
});
});
describe('updateDragAndDropState', () => {
test('that updateDragAndDropState calls updateDragAndDropState on all tabs', () => {
const cut = new Tabs(
fromPartial<DockviewGroupPanel>({}),
fromPartial<DockviewComponent>({
options: {},
}),
{
showTabsOverflowControl: true,
}
);
// Mock tab to verify the method is called
const mockTab1 = { updateDragAndDropState: jest.fn() };
const mockTab2 = { updateDragAndDropState: jest.fn() };
// Add mock tabs to the internal tabs array
(cut as any)._tabs = [
{ value: mockTab1 },
{ value: mockTab2 }
];
cut.updateDragAndDropState();
expect(mockTab1.updateDragAndDropState).toHaveBeenCalledTimes(1);
expect(mockTab2.updateDragAndDropState).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -0,0 +1,115 @@
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(),
options: {}
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(accessor.doSetGroupActive).not.toHaveBeenCalled();
fireEvent.pointerDown(cut.element);
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(group);
});
describe('disableDnd option', () => {
test('that void container is draggable by default (disableDnd not set)', () => {
const accessor = fromPartial<DockviewComponent>({
options: {}
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(cut.element.draggable).toBe(true);
});
test('that void container is draggable when disableDnd is false', () => {
const accessor = fromPartial<DockviewComponent>({
options: { disableDnd: false }
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(cut.element.draggable).toBe(true);
});
test('that void container is not draggable when disableDnd is true', () => {
const accessor = fromPartial<DockviewComponent>({
options: { disableDnd: true }
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(cut.element.draggable).toBe(false);
});
test('that updateDragAndDropState updates draggable attribute based on disableDnd option', () => {
const options = { disableDnd: false };
const accessor = fromPartial<DockviewComponent>({
options
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(cut.element.draggable).toBe(true);
// Simulate option change
options.disableDnd = true;
cut.updateDragAndDropState();
expect(cut.element.draggable).toBe(false);
// Change back
options.disableDnd = false;
cut.updateDragAndDropState();
expect(cut.element.draggable).toBe(true);
});
test('that void container has dv-draggable class when draggable', () => {
const accessor = fromPartial<DockviewComponent>({
options: { disableDnd: false }
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(cut.element.classList.contains('dv-draggable')).toBe(true);
});
test('that void container does not have dv-draggable class when not draggable', () => {
const accessor = fromPartial<DockviewComponent>({
options: { disableDnd: true }
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(cut.element.classList.contains('dv-draggable')).toBe(false);
});
test('that updateDragAndDropState updates dv-draggable class based on disableDnd option', () => {
const options = { disableDnd: false };
const accessor = fromPartial<DockviewComponent>({
options
});
const group = fromPartial<DockviewGroupPanel>({});
const cut = new VoidContainer(accessor, group);
expect(cut.element.classList.contains('dv-draggable')).toBe(true);
// Simulate option change
options.disableDnd = true;
cut.updateDragAndDropState();
expect(cut.element.classList.contains('dv-draggable')).toBe(false);
// Change back
options.disableDnd = false;
cut.updateDragAndDropState();
expect(cut.element.classList.contains('dv-draggable')).toBe(true);
});
});
});

View File

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

View File

@ -1,6 +1,5 @@
import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewComponent } from '../../dockview/dockviewComponent';
import { import {
GroupPanelUpdateEvent,
GroupviewPanelState, GroupviewPanelState,
IGroupPanelInitParameters, IGroupPanelInitParameters,
GroupPanelPartInitParameters, GroupPanelPartInitParameters,
@ -8,7 +7,7 @@ import {
ITabRenderer, ITabRenderer,
IWatermarkRenderer, IWatermarkRenderer,
} from '../../dockview/types'; } from '../../dockview/types';
import { PanelUpdateEvent } from '../../panel/types'; import { PanelUpdateEvent, Parameters } from '../../panel/types';
import { import {
DockviewGroupPanelModel, DockviewGroupPanelModel,
GroupOptions, GroupOptions,
@ -18,11 +17,14 @@ import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
import { CompositeDisposable } from '../../lifecycle'; import { CompositeDisposable } from '../../lifecycle';
import { DockviewPanelApi } from '../../api/dockviewPanelApi'; import { DockviewPanelApi } from '../../api/dockviewPanelApi';
import { IDockviewPanel } from '../../dockview/dockviewPanel'; import { IDockviewPanel } from '../../dockview/dockviewPanel';
import { import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
IDockviewPanelModel,
DockviewPanelModel,
} from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; 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 { enum GroupChangeKind2 {
ADD_PANEL, ADD_PANEL,
@ -35,13 +37,17 @@ class TestModel implements IDockviewPanelModel {
readonly contentComponent: string; readonly contentComponent: string;
readonly tab: ITabRenderer; readonly tab: ITabRenderer;
constructor(id: string) { constructor(readonly id: string) {
this.content = new TestHeaderPart(id); this.content = new TestHeaderPart(id);
this.contentComponent = id; this.contentComponent = id;
this.tab = new TestContentPart(id); this.tab = new TestContentPart(id);
} }
update(event: GroupPanelUpdateEvent): void { createTabRenderer(tabLocation: TabLocation): ITabRenderer {
return new TestHeaderPart(this.id);
}
update(event: PanelUpdateEvent): void {
// //
} }
@ -76,7 +82,7 @@ class Watermark implements IWatermarkRenderer {
return 'watermark-id'; return 'watermark-id';
} }
init(params: GroupPanelPartInitParameters) { init(params: WatermarkRendererInitParameters) {
// //
} }
@ -96,10 +102,6 @@ class Watermark implements IWatermarkRenderer {
return {}; return {};
} }
updateParentGroup() {
//
}
dispose() { dispose() {
// //
} }
@ -170,7 +172,7 @@ class TestHeaderPart implements ITabRenderer {
export class TestPanel implements IDockviewPanel { export class TestPanel implements IDockviewPanel {
private _group: DockviewGroupPanel | undefined; private _group: DockviewGroupPanel | undefined;
private _params: IGroupPanelInitParameters; private _params: IGroupPanelInitParameters | undefined;
readonly view: IDockviewPanelModel; readonly view: IDockviewPanelModel;
get title() { get title() {
@ -181,7 +183,7 @@ export class TestPanel implements IDockviewPanel {
return this._group!; return this._group!;
} }
get params(): Record<string, any> { get params(): Parameters {
return {}; return {};
} }
@ -197,7 +199,11 @@ export class TestPanel implements IDockviewPanel {
this._params = params; this._params = params;
} }
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void { updateParentGroup(group: DockviewGroupPanel): void {
//
}
runEvents(): void {
// //
} }
@ -205,6 +211,10 @@ export class TestPanel implements IDockviewPanel {
//noop //noop
} }
setTitle(title: string): void {
//
}
update(event: PanelUpdateEvent) { update(event: PanelUpdateEvent) {
//noop //noop
} }
@ -225,7 +235,7 @@ export class TestPanel implements IDockviewPanel {
} }
} }
describe('groupview', () => { describe('dockviewGroupPanelModel', () => {
let groupview: DockviewGroupPanel; let groupview: DockviewGroupPanel;
let dockview: DockviewComponent; let dockview: DockviewComponent;
let options: GroupOptions; let options: GroupOptions;
@ -233,35 +243,46 @@ describe('groupview', () => {
let removePanelMock: jest.Mock; let removePanelMock: jest.Mock;
let removeGroupMock: jest.Mock; let removeGroupMock: jest.Mock;
let panelApi: DockviewPanelApi;
beforeEach(() => { beforeEach(() => {
removePanelMock = jest.fn(); removePanelMock = jest.fn();
removeGroupMock = jest.fn(); removeGroupMock = jest.fn();
dockview = (<Partial<DockviewComponent>>{ options = {};
panelApi = fromPartial<DockviewPanelApi>({
renderer: 'onlyWhenVisible',
onDidTitleChange: new Emitter().event,
onDidParametersChange: new Emitter().event,
});
dockview = fromPartial<DockviewComponent>({
options: {}, options: {},
createWatermarkComponent: () => new Watermark(), createWatermarkComponent: () => new Watermark(),
doSetGroupActive: jest.fn(), doSetGroupActive: jest.fn(),
id: 'dockview-1', id: 'dockview-1',
removePanel: removePanelMock, removePanel: removePanelMock,
removeGroup: removeGroupMock, removeGroup: removeGroupMock,
onDidAddPanel: jest.fn(), onDidAddPanel: () => ({ dispose: jest.fn() }),
onDidRemovePanel: jest.fn(), onDidRemovePanel: () => ({ dispose: jest.fn() }),
}) as DockviewComponent; overlayRenderContainer: new OverlayRenderContainer(
document.createElement('div'),
fromPartial<DockviewComponent>({})
),
onDidOptionsChange: () => ({ dispose: jest.fn() }),
});
options = {
tabHeight: 30,
};
groupview = new DockviewGroupPanel(dockview, 'groupview-1', options); groupview = new DockviewGroupPanel(dockview, 'groupview-1', options);
groupview.initialize(); groupview.initialize();
}); });
test('panel events are captured during de-serialization', () => { test('panel events are captured during de-serialization', () => {
const panel1 = new TestPanel('panel1', jest.fn() as any); const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', jest.fn() as any); const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', jest.fn() as any); const panel3 = new TestPanel('panel3', panelApi);
const groupview2 = new DockviewGroupPanel(dockview, 'groupview-2', { const groupview2 = new DockviewGroupPanel(dockview, 'groupview-2', {
tabHeight: 25,
panels: [panel1, panel2, panel3], panels: [panel1, panel2, panel3],
activePanel: panel2, activePanel: panel2,
}); });
@ -343,9 +364,9 @@ describe('groupview', () => {
}) })
); );
const panel1 = new TestPanel('panel1', jest.fn() as any); const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', jest.fn() as any); const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', jest.fn() as any); const panel3 = new TestPanel('panel3', panelApi);
expect(events.length).toBe(0); expect(events.length).toBe(0);
@ -423,9 +444,9 @@ describe('groupview', () => {
}); });
test('moveToPrevious and moveToNext', () => { test('moveToPrevious and moveToNext', () => {
const panel1 = new TestPanel('panel1', jest.fn() as any); const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', jest.fn() as any); const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', jest.fn() as any); const panel3 = new TestPanel('panel3', panelApi);
groupview.model.openPanel(panel1); groupview.model.openPanel(panel1);
groupview.model.openPanel(panel2); groupview.model.openPanel(panel2);
@ -458,20 +479,20 @@ describe('groupview', () => {
test('default', () => { test('default', () => {
let viewQuery = groupview.element.querySelectorAll( let viewQuery = groupview.element.querySelectorAll(
'.groupview > .tabs-and-actions-container' '.dv-groupview > .dv-tabs-and-actions-container'
); );
expect(viewQuery).toBeTruthy(); expect(viewQuery).toBeTruthy();
viewQuery = groupview.element.querySelectorAll( viewQuery = groupview.element.querySelectorAll(
'.groupview > .content-container' '.dv-groupview > .dv-content-container'
); );
expect(viewQuery).toBeTruthy(); expect(viewQuery).toBeTruthy();
}); });
test('closeAllPanels with panels', () => { test('closeAllPanels with panels', () => {
const panel1 = new TestPanel('panel1', jest.fn() as any); const panel1 = new TestPanel('panel1', panelApi);
const panel2 = new TestPanel('panel2', jest.fn() as any); const panel2 = new TestPanel('panel2', panelApi);
const panel3 = new TestPanel('panel3', jest.fn() as any); const panel3 = new TestPanel('panel3', panelApi);
groupview.model.openPanel(panel1); groupview.model.openPanel(panel1);
groupview.model.openPanel(panel2); groupview.model.openPanel(panel2);
@ -479,20 +500,25 @@ describe('groupview', () => {
groupview.model.closeAllPanels(); groupview.model.closeAllPanels();
expect(removePanelMock).toBeCalledWith(panel1); expect(removePanelMock).toHaveBeenCalledWith(panel1, undefined);
expect(removePanelMock).toBeCalledWith(panel2); expect(removePanelMock).toHaveBeenCalledWith(panel2, undefined);
expect(removePanelMock).toBeCalledWith(panel3); expect(removePanelMock).toHaveBeenCalledWith(panel3, undefined);
}); });
test('closeAllPanels with no panels', () => { test('closeAllPanels with no panels', () => {
groupview.model.closeAllPanels(); groupview.model.closeAllPanels();
expect(removeGroupMock).toBeCalledWith(groupview); expect(removeGroupMock).toHaveBeenCalledWith(groupview);
}); });
test('that group is set on panel during onDidAddPanel event', () => { test('that group is set on panel during onDidAddPanel event', () => {
const cut = new DockviewComponent(document.createElement('div'), { const cut = new DockviewComponent(document.createElement('div'), {
components: { createComponent(options) {
component: TestContentPart, switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
}, },
}); });
@ -508,8 +534,13 @@ describe('groupview', () => {
const dockviewComponent = new DockviewComponent( const dockviewComponent = new DockviewComponent(
document.createElement('div'), document.createElement('div'),
{ {
components: { createComponent(options) {
component: TestContentPart, switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
}, },
} }
); );
@ -519,7 +550,7 @@ describe('groupview', () => {
dockviewComponent, dockviewComponent,
'id', 'id',
{}, {},
null null as any
); );
expect(cut.toJSON()).toEqual({ expect(cut.toJSON()).toEqual({
@ -533,8 +564,13 @@ describe('groupview', () => {
const dockviewComponent = new DockviewComponent( const dockviewComponent = new DockviewComponent(
document.createElement('div'), document.createElement('div'),
{ {
components: { createComponent(options) {
component: TestContentPart, switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
}, },
} }
); );
@ -544,7 +580,7 @@ describe('groupview', () => {
dockviewComponent, dockviewComponent,
'id', 'id',
{}, {},
null null as any
); );
cut.locked = true; cut.locked = true;
@ -563,8 +599,13 @@ describe('groupview', () => {
const dockviewComponent = new DockviewComponent( const dockviewComponent = new DockviewComponent(
document.createElement('div'), document.createElement('div'),
{ {
components: { createComponent(options) {
component: TestContentPart, switch (options.name) {
case 'component':
return new TestContentPart(options.id);
default:
throw new Error(`unsupported`);
}
}, },
} }
); );
@ -575,27 +616,27 @@ describe('groupview', () => {
dockviewComponent, dockviewComponent,
'id', 'id',
{}, {},
null null as any
); );
const contentContainer = groupviewContainer const contentContainer = groupviewContainer
.getElementsByClassName('content-container') .getElementsByClassName('dv-content-container')
.item(0)!.childNodes; .item(0)!.childNodes;
const panel1 = new TestPanel('id_1', null); const panel1 = new TestPanel('id_1', panelApi);
cut.openPanel(panel1); cut.openPanel(panel1);
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel1.view.content.element); expect(contentContainer.item(0)).toBe(panel1.view.content.element);
const panel2 = new TestPanel('id_2', null); const panel2 = new TestPanel('id_2', panelApi);
cut.openPanel(panel2); cut.openPanel(panel2);
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel2.view.content.element); expect(contentContainer.item(0)).toBe(panel2.view.content.element);
const panel3 = new TestPanel('id_2', null); const panel3 = new TestPanel('id_2', panelApi);
cut.openPanel(panel3, { skipSetPanelActive: true }); cut.openPanel(panel3, { skipSetActive: true });
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel2.view.content.element); expect(contentContainer.item(0)).toBe(panel2.view.content.element);
@ -605,18 +646,15 @@ describe('groupview', () => {
}); });
test('that should not show drop target is external event', () => { test('that should not show drop target is external event', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessor = fromPartial<DockviewComponent>({
return { id: 'testcomponentid',
id: 'testcomponentid', options: {},
options: { getPanel: jest.fn(),
showDndOverlay: jest.fn(), onDidAddPanel: jest.fn(),
}, onDidRemovePanel: jest.fn(),
getPanel: jest.fn(), onDidOptionsChange: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
};
}); });
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>( const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => { () => {
return { return {
@ -645,39 +683,205 @@ describe('groupview', () => {
new groupPanelMock() as DockviewGroupPanel new groupPanelMock() as DockviewGroupPanel
); );
const element = container let counter = 0;
.getElementsByClassName('content-container')
.item(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 () => 100
); );
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
fireEvent.dragEnter(element); fireEvent.dragEnter(element);
fireEvent.dragOver(element); fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1); expect(counter).toBe(1);
expect( expect(
element.getElementsByClassName('drop-target-dropzone').length element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0); ).toBe(0);
}); });
test('that should not show drop target if dropping on self', () => { test('that the .locked behaviour is as', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { 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 { return {
id: 'testcomponentid', id: 'testgroupid',
options: { model: groupView,
showDndOverlay: jest.fn(),
},
getPanel: jest.fn(),
doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}; };
}); });
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>, []>( const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => { () => {
return { return {
@ -704,16 +908,23 @@ describe('groupview', () => {
new groupPanelMock() as DockviewGroupPanel 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 const element = container
.getElementsByClassName('content-container') .getElementsByClassName('dv-content-container')
.item(0)!; .item(0) as HTMLElement;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation( jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100 () => 100
); );
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData( LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')], [new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
@ -723,94 +934,28 @@ describe('groupview', () => {
fireEvent.dragEnter(element); fireEvent.dragEnter(element);
fireEvent.dragOver(element); fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(0); expect(counter).toBe(0);
expect( expect(
element.getElementsByClassName('drop-target-dropzone').length element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0); ).toBe(1);
});
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);
}); });
test('that should not allow drop when not dropping for different component id', () => { test('that should not allow drop when not dropping for different component id', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessor = fromPartial<DockviewComponent>({
return { id: 'testcomponentid',
id: 'testcomponentid', options: {},
options: { getPanel: jest.fn(),
showDndOverlay: jest.fn(), doSetGroupActive: jest.fn(),
}, onDidAddPanel: jest.fn(),
getPanel: jest.fn(), onDidRemovePanel: jest.fn(),
doSetGroupActive: jest.fn(), overlayRenderContainer: new OverlayRenderContainer(
onDidAddPanel: jest.fn(), document.createElement('div'),
onDidRemovePanel: jest.fn(), fromPartial<DockviewComponent>({})
}; ),
onDidOptionsChange: jest.fn(),
}); });
const accessor = new accessorMock() as DockviewComponent;
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>( const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => { () => {
return { return {
@ -837,17 +982,23 @@ describe('groupview', () => {
new groupPanelMock() as DockviewGroupPanel new groupPanelMock() as DockviewGroupPanel
); );
cut.openPanel(new TestPanel('panel1', jest.fn() as any)); let counter = 0;
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
cut.onUnhandledDragOverEvent(() => {
counter++;
});
cut.openPanel(new TestPanel('panel1', panelApi));
cut.openPanel(new TestPanel('panel2', panelApi));
const element = container const element = container
.getElementsByClassName('content-container') .getElementsByClassName('dv-content-container')
.item(0)!; .item(0) as HTMLElement;
jest.spyOn(element, 'clientHeight', 'get').mockImplementation( jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
() => 100 () => 100
); );
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
LocalSelectionTransfer.getInstance().setData( LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')], [new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')],
@ -857,10 +1008,51 @@ describe('groupview', () => {
fireEvent.dragEnter(element); fireEvent.dragEnter(element);
fireEvent.dragOver(element); fireEvent.dragOver(element);
expect(accessor.options.showDndOverlay).toBeCalledTimes(1); expect(counter).toBe(1);
expect( expect(
element.getElementsByClassName('drop-target-dropzone').length element.getElementsByClassName('dv-drop-target-dropzone').length
).toBe(0);
});
test('that the watermark is removed when dispose is called', () => {
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,
dockview,
'groupviewid',
{},
new groupPanelMock() as DockviewGroupPanel
);
cut.initialize();
expect(
container.getElementsByClassName('watermark-test-container').length
).toBe(1);
cut.dispose();
expect(
container.getElementsByClassName('watermark-test-container').length
).toBe(0); ).toBe(0);
}); });
@ -898,17 +1090,17 @@ describe('groupview', () => {
container.getElementsByClassName('watermark-test-container').length container.getElementsByClassName('watermark-test-container').length
).toBe(1); ).toBe(1);
cut.openPanel(new TestPanel('panel1', jest.fn() as any)); cut.openPanel(new TestPanel('panel1', panelApi));
expect( expect(
container.getElementsByClassName('watermark-test-container').length container.getElementsByClassName('watermark-test-container').length
).toBe(0); ).toBe(0);
expect( expect(
container.getElementsByClassName('tabs-and-actions-container') container.getElementsByClassName('dv-tabs-and-actions-container')
.length .length
).toBe(1); ).toBe(1);
cut.openPanel(new TestPanel('panel2', jest.fn() as any)); cut.openPanel(new TestPanel('panel2', panelApi));
expect( expect(
container.getElementsByClassName('watermark-test-container').length container.getElementsByClassName('watermark-test-container').length
@ -926,7 +1118,7 @@ describe('groupview', () => {
container.getElementsByClassName('watermark-test-container').length container.getElementsByClassName('watermark-test-container').length
).toBe(1); ).toBe(1);
cut.openPanel(new TestPanel('panel1', jest.fn() as any)); cut.openPanel(new TestPanel('panel1', panelApi));
expect( expect(
container.getElementsByClassName('watermark-test-container').length container.getElementsByClassName('watermark-test-container').length

View File

@ -3,33 +3,37 @@ import { DockviewApi } from '../../api/component.api';
import { DockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewPanel } from '../../dockview/dockviewPanel';
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel'; import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { fromPartial } from '@total-typescript/shoehorn';
describe('dockviewPanel', () => { describe('dockviewPanel', () => {
test('update title', () => { test('update title', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => { const api = fromPartial<DockviewApi>({});
return { const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(), onDidActiveChange: jest.fn(),
} as any; },
}); });
const accessorMock = jest.fn<DockviewComponent, []>(() => { const model = fromPartial<IDockviewPanelModel>({
return {} as any; update: jest.fn(),
}); init: jest.fn(),
const groupMock = jest.fn<DockviewGroupPanel, []>(() => { dispose: jest.fn(),
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
};
}); });
const api = new dockviewApiMock(); const cut = new DockviewPanel(
const accessor = new accessorMock(); 'fake-id',
const group = new groupMock(); 'fake-component',
const model = <IDockviewPanelModel>new panelModelMock(); undefined,
accessor,
const cut = new DockviewPanel('fake-id', accessor, api, group, model); api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
let latestTitle: string | undefined = undefined; let latestTitle: string | undefined = undefined;
@ -37,13 +41,13 @@ describe('dockviewPanel', () => {
latestTitle = event.title; latestTitle = event.title;
}); });
expect(cut.title).toBe(''); expect(cut.title).toBeUndefined();
cut.init({ title: 'new title', params: {} }); cut.init({ title: 'new title', params: {} });
expect(latestTitle).toBe('new title'); expect(latestTitle).toBe('new title');
expect(cut.title).toBe('new title'); expect(cut.title).toBe('new title');
cut.update({ params: { title: 'another title' } }); cut.setTitle('another title');
expect(latestTitle).toBe('another title'); expect(latestTitle).toBe('another title');
expect(cut.title).toBe('another title'); expect(cut.title).toBe('another title');
@ -51,62 +55,77 @@ describe('dockviewPanel', () => {
}); });
test('that .setTitle updates the title', () => { test('that .setTitle updates the title', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => { const api = fromPartial<DockviewApi>({});
return { const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(), onDidActiveChange: jest.fn(),
} as any; },
}); });
const accessorMock = jest.fn<DockviewComponent, []>(() => { const model = fromPartial<IDockviewPanelModel>({
return {} as any; update: jest.fn(),
}); init: jest.fn(),
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
};
}); });
const api = new dockviewApiMock(); const cut = new DockviewPanel(
const accessor = new accessorMock(); 'fake-id',
const group = new groupMock(); 'fake-component',
const model = <IDockviewPanelModel>new panelModelMock(); undefined,
accessor,
const cut = new DockviewPanel('fake-id', accessor, api, group, model); api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
cut.init({ title: 'myTitle', params: {} }); cut.init({ title: 'myTitle', params: {} });
expect(cut.title).toBe('myTitle'); expect(cut.title).toBe('myTitle');
cut.setTitle('newTitle'); cut.setTitle('newTitle');
expect(cut.title).toBe('newTitle'); expect(cut.title).toBe('newTitle');
cut.api.setTitle('new title 2');
expect(cut.title).toBe('new title 2');
}); });
test('dispose cleanup', () => { test('dispose cleanup', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => { const api = fromPartial<DockviewApi>({});
return {} as any; 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, []>(() => { const model = fromPartial<IDockviewPanelModel>({
return {} as any; update: jest.fn(),
}); init: jest.fn(),
const groupMock = jest.fn<DockviewGroupPanel, []>(() => { dispose: jest.fn(),
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
};
}); });
const api = new dockviewApiMock(); const cut = new DockviewPanel(
const accessor = new accessorMock(); 'fake-id',
const group = new groupMock(); 'fake-component',
const model = <IDockviewPanelModel>new panelModelMock(); undefined,
accessor,
const cut = new DockviewPanel('fake-id', accessor, api, group, model); api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
cut.init({ params: {}, title: 'title' }); cut.init({ params: {}, title: 'title' });
@ -116,69 +135,153 @@ describe('dockviewPanel', () => {
}); });
test('get params', () => { test('get params', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => { const api = fromPartial<DockviewApi>({});
return {} as any; const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
},
}); });
const accessorMock = jest.fn<DockviewComponent, []>(() => { const model = fromPartial<IDockviewPanelModel>({
return {} as any; update: jest.fn(),
}); init: jest.fn(),
const groupMock = jest.fn<DockviewGroupPanel, []>(() => { dispose: jest.fn(),
return {} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
};
}); });
const api = new dockviewApiMock(); const cut = new DockviewPanel(
const accessor = new accessorMock(); 'fake-id',
const group = new groupMock(); 'fake-component',
const model = <IDockviewPanelModel>new panelModelMock(); undefined,
accessor,
const cut = new DockviewPanel('fake-id', accessor, api, group, model); api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
expect(cut.params).toEqual(undefined); expect(cut.params).toEqual(undefined);
cut.update({ params: { params: { variableA: 'A', variableB: 'B' } } }); cut.update({ params: { variableA: 'A', variableB: 'B' } });
expect(cut.params).toEqual({ variableA: 'A', variableB: 'B' }); expect(cut.params).toEqual({ variableA: 'A', variableB: 'B' });
}); });
test('setSize propagates to underlying group', () => { test('setSize propagates to underlying group', () => {
const dockviewApiMock = jest.fn<DockviewApi, []>(() => { const api = fromPartial<DockviewApi>({});
return {} as any; 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, []>(() => { const model = fromPartial<IDockviewPanelModel>({
return {} as any; update: jest.fn(),
}); init: jest.fn(),
const groupMock = jest.fn<DockviewGroupPanel, []>(() => { dispose: jest.fn(),
return {
api: {
setSize: jest.fn(),
},
} as any;
});
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
return {
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
};
}); });
const api = new dockviewApiMock(); const cut = new DockviewPanel(
const accessor = new accessorMock(); 'fake-id',
const group = new groupMock(); 'fake-component',
const model = <IDockviewPanelModel>new panelModelMock(); undefined,
accessor,
const cut = new DockviewPanel('fake-id', accessor, api, group, model); api,
group,
model,
{
renderer: 'onlyWhenVisible',
}
);
cut.api.setSize({ height: 123, width: 456 }); cut.api.setSize({ height: 123, width: 456 });
expect(group.api.setSize).toBeCalledWith({ height: 123, width: 456 }); expect(group.api.setSize).toHaveBeenCalledWith({
expect(group.api.setSize).toBeCalledTimes(1); height: 123,
width: 456,
});
expect(group.api.setSize).toHaveBeenCalledTimes(1);
});
test('updateParameter', () => {
const api = fromPartial<DockviewApi>({});
const accessor = fromPartial<DockviewComponent>({});
const group = fromPartial<DockviewGroupPanel>({
api: {
onDidVisibilityChange: jest.fn(),
onDidLocationChange: jest.fn(),
onDidActiveChange: jest.fn(),
},
});
const model = fromPartial<IDockviewPanelModel>({
update: jest.fn(),
init: jest.fn(),
dispose: jest.fn(),
});
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' });
// 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({
a: '-1',
b: '2',
c: '3',
d: '4',
e: '5',
f: '6',
});
expect(model.update).toHaveBeenCalledWith({
params: { a: '-1', b: '2', c: '3', d: '4', e: '5', f: '6' },
});
cut.update({
params: {
d: '',
e: null,
f: undefined,
g: '',
h: null,
i: undefined,
},
});
expect(cut.params).toEqual({
a: '-1',
b: '2',
c: '3',
d: '',
e: null,
g: '',
h: null,
});
expect(model.update).toHaveBeenCalledWith({
params: { a: '-1', b: '2', c: '3', d: '', e: null, g: '', h: null },
});
}); });
}); });

View File

@ -1,43 +1,58 @@
import { import { DockviewComponent } from '../../dockview/dockviewComponent';
DockviewComponent,
IDockviewComponent,
} from '../../dockview/dockviewComponent';
import { DockviewPanelModel } from '../../dockview/dockviewPanelModel'; import { DockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { IContentRenderer, ITabRenderer } from '../../dockview/types'; import { IContentRenderer, ITabRenderer } from '../../dockview/types';
import { DefaultTab } from '../../dockview/components/tab/defaultTab';
import { fromPartial } from '@total-typescript/shoehorn';
describe('dockviewGroupPanel', () => { describe('dockviewGroupPanel', () => {
test('that dispose is called on content and tab renderers when present', () => { let contentMock: jest.Mock<IContentRenderer>;
const contentMock = jest.fn<IContentRenderer, []>(() => { let tabMock: jest.Mock<ITabRenderer>;
let accessorMock: DockviewComponent;
beforeEach(() => {
contentMock = jest.fn<IContentRenderer, []>(() => {
const partial: Partial<IContentRenderer> = { const partial: Partial<IContentRenderer> = {
element: document.createElement('div'), element: document.createElement('div'),
dispose: jest.fn(), dispose: jest.fn(),
update: jest.fn(),
}; };
return partial as IContentRenderer; return partial as IContentRenderer;
}); });
const tabMock = jest.fn<ITabRenderer, []>(() => { tabMock = jest.fn<ITabRenderer, []>(() => {
const partial: Partial<IContentRenderer> = { const partial: Partial<IContentRenderer> = {
element: document.createElement('div'), element: document.createElement('div'),
dispose: jest.fn(), dispose: jest.fn(),
update: jest.fn(),
}; };
return partial as IContentRenderer; return partial as IContentRenderer;
}); });
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { accessorMock = fromPartial<DockviewComponent>({
return { options: {
options: { createComponent(options) {
components: { switch (options.name) {
contentComponent: contentMock, case 'contentComponent':
}, return new contentMock(options.id, options.name);
tabComponents: { default:
tabComponent: tabMock, throw new Error(`unsupported`);
}, }
}, },
}; 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( const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(), accessorMock,
'id', 'id',
'contentComponent', 'contentComponent',
'tabComponent' 'tabComponent'
@ -50,36 +65,8 @@ describe('dockviewGroupPanel', () => {
}); });
test('that update is called on content and tab renderers when present', () => { test('that update is called on content and tab renderers when present', () => {
const contentMock = jest.fn<IContentRenderer, []>(() => {
const partial: Partial<IContentRenderer> = {
element: document.createElement('div'),
update: jest.fn(),
};
return partial as IContentRenderer;
});
const tabMock = jest.fn<ITabRenderer, []>(() => {
const partial: Partial<IContentRenderer> = {
element: document.createElement('div'),
update: jest.fn(),
};
return partial as IContentRenderer;
});
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: tabMock,
},
},
};
});
const cut = new DockviewPanelModel( const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(), accessorMock,
'id', 'id',
'contentComponent', 'contentComponent',
'tabComponent' 'tabComponent'
@ -93,79 +80,121 @@ describe('dockviewGroupPanel', () => {
expect(cut.tab.update).toHaveBeenCalled(); expect(cut.tab.update).toHaveBeenCalled();
}); });
test('that events are fired', () => { test('that the default tab is created', () => {
const contentMock = jest.fn<IContentRenderer, []>(() => { accessorMock = fromPartial<DockviewComponent>({
const partial: Partial<IContentRenderer> = { options: {
element: document.createElement('div'), createComponent(options) {
onGroupChange: jest.fn(), switch (options.name) {
onPanelVisibleChange: jest.fn(), case 'contentComponent':
}; return new contentMock(options.id, options.name);
return partial as IContentRenderer; default:
}); throw new Error(`unsupported`);
}
const tabMock = jest.fn<ITabRenderer, []>(() => {
const partial: Partial<IContentRenderer> = {
element: document.createElement('div'),
onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(),
};
return partial as IContentRenderer;
});
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {
options: {
components: {
contentComponent: contentMock,
},
tabComponents: {
tabComponent: tabMock,
},
}, },
}; createTabComponent(options) {
switch (options.name) {
case 'tabComponent':
return tabMock;
default:
throw new Error(`unsupported`);
}
},
},
}); });
const cut = new DockviewPanelModel( const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(), accessorMock,
'id', 'id',
'contentComponent', 'contentComponent',
'tabComponent' 'tabComponent'
); );
const group1 = jest.fn() as any; expect(cut.tab).toEqual(tabMock);
const group2 = jest.fn() as any; });
cut.updateParentGroup(group1, false);
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1); test('that the provided default tab is chosen when no implementation is provided', () => {
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1); accessorMock = fromPartial<DockviewComponent>({
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( options: {
1, defaultTabComponent: 'tabComponent',
false createComponent(options) {
switch (options.name) {
case 'contentComponent':
return new contentMock(options.id, options.name);
default:
throw new Error(`unsupported`);
}
},
createTabComponent(options) {
switch (options.name) {
case 'tabComponent':
return tabMock;
default:
throw new Error(`unsupported`);
}
},
},
});
const cut = new DockviewPanelModel(
accessorMock,
'id',
'contentComponent'
); );
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.tab).toEqual(tabMock);
});
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( test('that is library default tab instance is created when no alternative exists', () => {
2, accessorMock = fromPartial<DockviewComponent>({
true options: {
createComponent(options) {
switch (options.name) {
case 'contentComponent':
return new contentMock(options.id, options.name);
default:
throw new Error(`unsupported`);
}
},
},
});
const cut = new DockviewPanelModel(
accessorMock,
'id',
'contentComponent'
); );
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.tab instanceof DefaultTab).toBeTruthy();
});
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2); test('that the default content is created', () => {
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2); accessorMock = fromPartial<DockviewComponent>({
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2); options: {
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2); createComponent(options) {
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); switch (options.name) {
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); case 'contentComponent':
return contentMock;
default:
throw new Error(`unsupported`);
}
},
createTabComponent(options) {
switch (options.name) {
case 'tabComponent':
return tabMock;
default:
throw new Error(`unsupported`);
}
},
},
});
const cut = new DockviewPanelModel(
accessorMock,
'id',
'contentComponent'
);
expect(cut.content).toEqual(contentMock);
}); });
}); });

View File

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

View File

@ -1,7 +1,11 @@
import { Emitter, Event } from '../events'; import { AsapEvent, Emitter, Event, addDisposableListener } from '../events';
describe('events', () => { describe('events', () => {
describe('emitter', () => { describe('emitter', () => {
it('debug mode is off', () => {
expect(Emitter.ENABLE_TRACKING).toBeFalsy();
});
it('should emit values', () => { it('should emit values', () => {
const emitter = new Emitter<number>(); const emitter = new Emitter<number>();
let value: number | undefined = undefined; let value: number | undefined = undefined;
@ -58,7 +62,7 @@ describe('events', () => {
expect(value).toBeUndefined(); expect(value).toBeUndefined();
}); });
it('should relay last value in replay mode', () => { it('should replay last value in replay mode', () => {
const emitter = new Emitter<number>({ replay: true }); const emitter = new Emitter<number>({ replay: true });
let value: number | undefined = undefined; let value: number | undefined = undefined;
@ -71,6 +75,55 @@ describe('events', () => {
stream.dispose(); stream.dispose();
}); });
it('should not replay last value in replay mode', () => {
const emitter = new Emitter<number>();
let value: number | undefined = undefined;
emitter.fire(1);
const stream = emitter.event((x) => {
value = x;
});
expect(value).toBeUndefined();
stream.dispose();
});
});
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', () => { it('should emit a value when any event fires', () => {
@ -97,4 +150,138 @@ describe('events', () => {
emitter3.fire(3); emitter3.fire(3);
expect(value).toBe(3); expect(value).toBe(3);
}); });
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
);
});
it('addDisposableListener with capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const handler = jest.fn();
const disposable = addDisposableListener(
element as any,
'pointerdown',
handler,
true
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'pointerdown',
handler,
true
);
expect(element.removeEventListener).toBeCalledTimes(0);
disposable.dispose();
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'pointerdown',
handler,
true
);
});
it('addDisposableListener without capture options', () => {
const element = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
const handler = jest.fn();
const disposable = addDisposableListener(
element as any,
'pointerdown',
handler
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'pointerdown',
handler,
undefined
);
expect(element.removeEventListener).toBeCalledTimes(0);
disposable.dispose();
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'pointerdown',
handler,
undefined
);
});
}); });

View File

@ -17,13 +17,9 @@ class TestPanel implements IGridPanelView {
_onDidChange = new Emitter<IViewSize | undefined>(); _onDidChange = new Emitter<IViewSize | undefined>();
readonly onDidChange = this._onDidChange.event; readonly onDidChange = this._onDidChange.event;
get isActive(): boolean { isVisible: boolean = true;
return true; isActive: boolean = true;
} params: Parameters = {};
get params(): Record<string, any> {
return {};
}
constructor( constructor(
public readonly id: string, public readonly id: string,
@ -70,8 +66,10 @@ class TestPanel implements IGridPanelView {
} }
class ClassUnderTest extends BaseGrid<TestPanel> { class ClassUnderTest extends BaseGrid<TestPanel> {
constructor(element: HTMLElement, options: BaseGridOptions) { readonly gridview = this.gridview;
super(element, options);
constructor(parentElement: HTMLElement, options: BaseGridOptions) {
super(parentElement, options);
} }
doRemoveGroup( doRemoveGroup(
@ -107,23 +105,62 @@ class ClassUnderTest extends BaseGrid<TestPanel> {
} }
describe('baseComponentGridview', () => { describe('baseComponentGridview', () => {
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,
});
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', () => { test('can add group', () => {
const cut = new ClassUnderTest(document.createElement('div'), { const cut = new ClassUnderTest(document.createElement('div'), {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
proportionalLayout: true, proportionalLayout: true,
}); });
const events: TestPanel[] = []; const events: { type: string; panel: TestPanel | undefined }[] = [];
const disposable = new CompositeDisposable( const disposable = new CompositeDisposable(
cut.onDidAddGroup((event) => { cut.onDidAdd((event) => {
events.push(event); events.push({ type: 'add', panel: event });
}), }),
cut.onDidRemoveGroup((event) => { cut.onDidRemove((event) => {
events.push(event); events.push({ type: 'remove', panel: event });
}), }),
cut.onDidActiveGroupChange((event) => { cut.onDidActiveChange((event) => {
events.push(event); events.push({ type: 'active', panel: event });
}) })
); );
@ -140,9 +177,8 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel1); cut.doAddGroup(panel1);
expect(events.length).toBe(2); expect(events.length).toBe(1);
expect(events[0]).toBe(panel1); expect(events[0]).toEqual({ type: 'add', panel: panel1 });
expect(events[1]).toBe(panel1);
const panel2 = new TestPanel( const panel2 = new TestPanel(
'id', 'id',
@ -157,12 +193,12 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel2); cut.doAddGroup(panel2);
expect(events.length).toBe(4); expect(events.length).toBe(2);
expect(events[2]).toBe(panel2); expect(events[1]).toEqual({ type: 'add', panel: panel2 });
cut.doRemoveGroup(panel1); cut.doRemoveGroup(panel1);
expect(events.length).toBe(5); expect(events.length).toBe(3);
expect(events[4]).toBe(panel1); expect(events[2]).toEqual({ type: 'remove', panel: panel1 });
disposable.dispose(); disposable.dispose();
cut.dispose(); cut.dispose();

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,135 +0,0 @@
import { fireEvent } from '@testing-library/dom';
import { Emitter, Event } from '../../../events';
import { ContentContainer } from '../../../dockview/components/panel/content';
import {
GroupPanelContentPartInitParameters,
IContentRenderer,
} from '../../../dockview/types';
import { CompositeDisposable } from '../../../lifecycle';
import { PanelUpdateEvent } from '../../../panel/types';
import { IDockviewPanel } from '../../../dockview/dockviewPanel';
import { IDockviewPanelModel } from '../../../dockview/dockviewPanelModel';
class TestContentRenderer
extends CompositeDisposable
implements IContentRenderer
{
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');
}
init(parameters: GroupPanelContentPartInitParameters): void {
//
}
layout(width: number, height: number): void {
//
}
update(event: PanelUpdateEvent): void {
//
}
toJSON(): object {
return {};
}
focus(): void {
//
}
}
describe('contentContainer', () => {
beforeEach(() => {
jest.useFakeTimers();
});
test('basic focus test', () => {
let focus = 0;
let blur = 0;
const disposable = new CompositeDisposable();
const cut = new ContentContainer();
disposable.addDisposables(
cut.onDidFocus(() => {
focus++;
}),
cut.onDidBlur(() => {
blur++;
})
);
const contentRenderer = new TestContentRenderer('id-1');
const panel = {
view: {
content: contentRenderer,
} as Partial<IDockviewPanelModel>,
} as Partial<IDockviewPanel>;
cut.openPanel(panel as IDockviewPanel);
expect(focus).toBe(0);
expect(blur).toBe(0);
// container has focus within
fireEvent.focus(contentRenderer.element);
expect(focus).toBe(1);
expect(blur).toBe(0);
// container looses focus
fireEvent.blur(contentRenderer.element);
jest.runAllTimers();
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>,
} 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);
// new panel recieves focus
fireEvent.focus(contentRenderer2.element);
expect(focus).toBe(3);
expect(blur).toBe(2);
// new panel looses focus
fireEvent.blur(contentRenderer2.element);
jest.runAllTimers();
expect(focus).toBe(3);
expect(blur).toBe(3);
disposable.dispose();
});
});

View File

@ -1,287 +0,0 @@
import { fireEvent } from '@testing-library/dom';
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';
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());
expect(cut.element.className).toBe('tab 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());
cut.setActive(true);
expect(cut.element.className).toBe('tab active-tab');
cut.setActive(false);
expect(cut.element.className).toBe('tab inactive-tab');
});
test('that an external event does not render a drop target and calls through to the group model', () => {
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 groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panelId', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalled();
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that if you drag over yourself no 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 groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that if you drag over another tab 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 groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel2')],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(1);
});
test('that dropping on a tab with the same id but from a different component should not render a drop over and call through to the group model', () => {
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 groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel1'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
test('that dropping on a tab from a different component should not render a drop over and call through to the group model', () => {
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 groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new Tab('panel1', accessor, groupPanel);
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel2'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(cut.element);
fireEvent.dragOver(cut.element);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
});

View File

@ -1,334 +0,0 @@
import { DockviewComponent } from '../../../dockview/dockviewComponent';
import { TabsContainer } from '../../../dockview/components/titlebar/tabsContainer';
import { fireEvent } from '@testing-library/dom';
import {
LocalSelectionTransfer,
PanelTransfer,
} from '../../../dnd/dataTransfer';
import { TestPanel } from '../dockviewGroupPanelModel.spec';
import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel';
import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel';
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 groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
model: groupView,
};
});
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);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalled();
expect(
cut.element.getElementsByClassName('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 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,
panels: [],
};
});
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);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'testcomponentid',
'anothergroupid',
'anotherpanelid'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').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 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,
panels: [],
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel2')],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('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 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,
panels: [],
};
});
const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[new PanelTransfer('testcomponentid', 'anothergroupid', 'panel1')],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
expect(
cut.element.getElementsByClassName('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 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 accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new TabsContainer(accessor, groupPanel);
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
const emptySpace = cut.element
.getElementsByClassName('void-container')
.item(0);
if (!emptySpace) {
fail('element not found');
}
jest.spyOn(emptySpace, 'clientHeight', 'get').mockImplementation(
() => 100
);
jest.spyOn(emptySpace, 'clientWidth', 'get').mockImplementation(
() => 100
);
LocalSelectionTransfer.getInstance().setData(
[
new PanelTransfer(
'anothercomponentid',
'anothergroupid',
'panel1'
),
],
PanelTransfer.prototype
);
fireEvent.dragEnter(emptySpace);
fireEvent.dragOver(emptySpace);
expect(groupView.canDisplayOverlay).toBeCalledTimes(1);
expect(
cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0);
});
});

View File

@ -1,4 +1,8 @@
import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import {
CompositeDisposable,
Disposable,
MutableDisposable,
} from '../lifecycle';
describe('lifecycle', () => { describe('lifecycle', () => {
test('mutable disposable', () => { test('mutable disposable', () => {
@ -48,4 +52,32 @@ describe('lifecycle', () => {
expect(d3.dispose).toHaveBeenCalledTimes(1); expect(d3.dispose).toHaveBeenCalledTimes(1);
expect(d4.dispose).toHaveBeenCalledTimes(1); expect(d4.dispose).toHaveBeenCalledTimes(1);
}); });
test('that isDisposed=true once CompositeDisposable is disposed', () => {
class Test extends CompositeDisposable {
checkIsDisposed(): boolean {
return this.isDisposed;
}
}
const cut = new Test();
expect(cut.checkIsDisposed()).toBeFalsy();
cut.dispose();
expect(cut.checkIsDisposed()).toBeTruthy();
});
test('Disposable.from(...)', () => {
const func = jest.fn();
const disposable = Disposable.from(func);
expect(func).not.toHaveBeenCalled();
disposable.dispose();
expect(func).toHaveBeenCalledTimes(1);
});
}); });

View File

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

View File

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

View File

@ -0,0 +1,457 @@
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, exhaustAnimationFrame } 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();
await exhaustAnimationFrame();
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({});
await exhaustAnimationFrame();
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('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);
});
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)'
);
});
test('that frequent resize calls are batched to prevent shaking (issue #988)', 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',
},
},
},
});
jest.spyOn(referenceContainer.element, 'getBoundingClientRect')
.mockReturnValue(
fromPartial<DOMRect>({
left: 100,
top: 200,
width: 150,
height: 250,
})
);
jest.spyOn(parentContainer, 'getBoundingClientRect').mockReturnValue(
fromPartial<DOMRect>({
left: 50,
top: 100,
width: 200,
height: 300,
})
);
const container = cut.attach({ panel, referenceContainer });
// Wait for initial positioning
await exhaustMicrotaskQueue();
await exhaustAnimationFrame();
expect(container.style.left).toBe('50px');
expect(container.style.top).toBe('100px');
// Simulate rapid resize events that could cause shaking
onDidDimensionsChange.fire({});
onDidDimensionsChange.fire({});
onDidDimensionsChange.fire({});
onDidDimensionsChange.fire({});
onDidDimensionsChange.fire({});
// Even with multiple rapid events, only one RAF should be scheduled
await exhaustAnimationFrame();
expect(container.style.left).toBe('50px');
expect(container.style.top).toBe('100px');
expect(container.style.width).toBe('150px');
expect(container.style.height).toBe('250px');
// Verify that DOM measurements are cached within the same frame
// Should be called initially and possibly one more time for visibility change
expect(referenceContainer.element.getBoundingClientRect).toHaveBeenCalledTimes(2);
expect(parentContainer.getBoundingClientRect).toHaveBeenCalledTimes(2);
});
test('updateAllPositions forces position recalculation for visible panels', async () => {
const cut = new OverlayRenderContainer(
parentContainer,
fromPartial<DockviewComponent>({})
);
const panelContentEl1 = document.createElement('div');
const panelContentEl2 = document.createElement('div');
const onDidVisibilityChange1 = new Emitter<any>();
const onDidDimensionsChange1 = new Emitter<any>();
const onDidLocationChange1 = new Emitter<any>();
const onDidVisibilityChange2 = new Emitter<any>();
const onDidDimensionsChange2 = new Emitter<any>();
const onDidLocationChange2 = new Emitter<any>();
const panel1 = fromPartial<IDockviewPanel>({
api: {
id: 'panel1',
onDidVisibilityChange: onDidVisibilityChange1.event,
onDidDimensionsChange: onDidDimensionsChange1.event,
onDidLocationChange: onDidLocationChange1.event,
isVisible: true,
location: { type: 'grid' },
},
view: {
content: {
element: panelContentEl1,
},
},
group: {
api: {
location: { type: 'grid' },
},
},
});
const panel2 = fromPartial<IDockviewPanel>({
api: {
id: 'panel2',
onDidVisibilityChange: onDidVisibilityChange2.event,
onDidDimensionsChange: onDidDimensionsChange2.event,
onDidLocationChange: onDidLocationChange2.event,
isVisible: false, // This panel is not visible
location: { type: 'grid' },
},
view: {
content: {
element: panelContentEl2,
},
},
group: {
api: {
location: { type: 'grid' },
},
},
});
// Mock getBoundingClientRect for consistent testing
jest.spyOn(referenceContainer.element, 'getBoundingClientRect')
.mockReturnValue(
fromPartial<DOMRect>({
left: 100,
top: 200,
width: 150,
height: 250,
})
);
jest.spyOn(parentContainer, 'getBoundingClientRect').mockReturnValue(
fromPartial<DOMRect>({
left: 50,
top: 100,
width: 200,
height: 300,
})
);
// Attach both panels
const container1 = cut.attach({ panel: panel1, referenceContainer });
const container2 = cut.attach({ panel: panel2, referenceContainer });
await exhaustMicrotaskQueue();
await exhaustAnimationFrame();
// Clear previous calls to getBoundingClientRect
jest.clearAllMocks();
// Call updateAllPositions
cut.updateAllPositions();
// Should trigger resize for visible panels only
await exhaustAnimationFrame();
// Verify that positioning was updated for visible panel
expect(container1.style.left).toBe('50px');
expect(container1.style.top).toBe('100px');
expect(container1.style.width).toBe('150px');
expect(container1.style.height).toBe('250px');
// Verify getBoundingClientRect was called for visible panel only
// updateAllPositions should call the resize function which triggers getBoundingClientRect
expect(referenceContainer.element.getBoundingClientRect).toHaveBeenCalled();
expect(parentContainer.getBoundingClientRect).toHaveBeenCalled();
});
});

View File

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

View File

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

View File

@ -4,19 +4,28 @@ import { PanelUpdateEvent } from '../../panel/types';
import { PaneviewComponent } from '../../paneview/paneviewComponent'; import { PaneviewComponent } from '../../paneview/paneviewComponent';
import { import {
PaneviewPanel, PaneviewPanel,
IPaneBodyPart, IPanePart,
IPaneHeaderPart,
PanePanelComponentInitParameter, PanePanelComponentInitParameter,
} from '../../paneview/paneviewPanel'; } from '../../paneview/paneviewPanel';
import { Orientation } from '../../splitview/splitview'; import { Orientation } from '../../splitview/splitview';
class TestPanel extends PaneviewPanel { class TestPanel extends PaneviewPanel {
constructor(id: string, component: string) { 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() { getHeaderComponent() {
return new (class Header implements IPaneHeaderPart { return new (class Header implements IPanePart {
private _element: HTMLElement = document.createElement('div'); private _element: HTMLElement = document.createElement('div');
get element() { get element() {
@ -38,7 +47,7 @@ class TestPanel extends PaneviewPanel {
} }
getBodyComponent() { getBodyComponent() {
return new (class Header implements IPaneBodyPart { return new (class Header implements IPanePart {
private _element: HTMLElement = document.createElement('div'); private _element: HTMLElement = document.createElement('div');
get element() { get element() {
@ -60,7 +69,7 @@ class TestPanel extends PaneviewPanel {
} }
} }
describe('componentPaneview', () => { describe('paneviewComponent', () => {
let container: HTMLElement; let container: HTMLElement;
beforeEach(() => { beforeEach(() => {
@ -68,25 +77,52 @@ describe('componentPaneview', () => {
container.className = 'container'; 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 paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
paneview.dispose();
expect(container.parentElement).toBe(root);
expect(container.children.length).toBe(0);
});
test('vertical panels', () => { test('vertical panels', () => {
const disposables = new CompositeDisposable(); const disposables = new CompositeDisposable();
const paneview = new PaneviewComponent(container, { const paneview = new PaneviewComponent(container, {
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
paneview.layout(600, 400); paneview.layout(300, 200);
paneview.addPanel({ paneview.addPanel({
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
title: 'Panel 1', title: 'Panel 1',
}); });
paneview.addPanel({ paneview.addPanel({
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
title: 'Panel2', title: 'Panel2',
}); });
@ -107,6 +143,8 @@ describe('componentPaneview', () => {
}) })
); );
paneview.layout(600, 400);
expect(panel1Dimensions).toEqual({ width: 600, height: 22 }); expect(panel1Dimensions).toEqual({ width: 600, height: 22 });
expect(panel2Dimensions).toEqual({ width: 600, height: 22 }); expect(panel2Dimensions).toEqual({ width: 600, height: 22 });
@ -142,11 +180,18 @@ describe('componentPaneview', () => {
test('serialization', () => { test('serialization', () => {
const paneview = new PaneviewComponent(container, { const paneview = new PaneviewComponent(container, {
components: { createComponent: (options) => {
testPanel: TestPanel, 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({ paneview.fromJSON({
size: 6, size: 6,
views: [ views: [
@ -154,7 +199,7 @@ describe('componentPaneview', () => {
size: 1, size: 1,
data: { data: {
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
title: 'Panel 1', title: 'Panel 1',
}, },
expanded: true, expanded: true,
@ -163,7 +208,7 @@ describe('componentPaneview', () => {
size: 2, size: 2,
data: { data: {
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
title: 'Panel 2', title: 'Panel 2',
}, },
expanded: false, expanded: false,
@ -172,13 +217,15 @@ describe('componentPaneview', () => {
size: 3, size: 3,
data: { data: {
id: 'panel3', id: 'panel3',
component: 'testPanel', component: 'default',
title: 'Panel 3', title: 'Panel 3',
}, },
}, },
], ],
}); });
expect(container.querySelectorAll('.dv-pane-container').length).toBe(1);
paneview.layout(400, 800); paneview.layout(400, 800);
const panel1 = paneview.getPanel('panel1'); const panel1 = paneview.getPanel('panel1');
@ -218,31 +265,31 @@ describe('componentPaneview', () => {
size: 756, size: 756,
data: { data: {
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
title: 'Panel 1', title: 'Panel 1',
}, },
expanded: true, expanded: true,
minimumSize: 100, headerSize: 22,
}, },
{ {
size: 22, size: 22,
data: { data: {
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
title: 'Panel 2', title: 'Panel 2',
}, },
expanded: false, expanded: false,
minimumSize: 100, headerSize: 22,
}, },
{ {
size: 22, size: 22,
data: { data: {
id: 'panel3', id: 'panel3',
component: 'testPanel', component: 'default',
title: 'Panel 3', title: 'Panel 3',
}, },
expanded: false, expanded: false,
minimumSize: 100, headerSize: 22,
}, },
], ],
}); });
@ -250,20 +297,25 @@ describe('componentPaneview', () => {
test('toJSON shouldnt fire any layout events', () => { test('toJSON shouldnt fire any layout events', () => {
const paneview = new PaneviewComponent(container, { const paneview = new PaneviewComponent(container, {
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
paneview.layout(1000, 1000); paneview.layout(1000, 1000);
paneview.addPanel({ paneview.addPanel({
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
title: 'Panel 1', title: 'Panel 1',
}); });
paneview.addPanel({ paneview.addPanel({
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
title: 'Panel 2', title: 'Panel 2',
}); });
@ -277,39 +329,15 @@ describe('componentPaneview', () => {
disposable.dispose(); disposable.dispose();
}); });
test('dispose of paneviewComponent', () => {
expect(container.childNodes.length).toBe(0);
const paneview = new PaneviewComponent(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', () => { test('panel is disposed of when component is disposed', () => {
const paneview = new PaneviewComponent(container, { const paneview = new PaneviewComponent(container, {
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
@ -317,17 +345,17 @@ describe('componentPaneview', () => {
paneview.addPanel({ paneview.addPanel({
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
title: 'Panel 1', title: 'Panel 1',
}); });
paneview.addPanel({ paneview.addPanel({
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
title: 'Panel 2', title: 'Panel 2',
}); });
const panel1 = paneview.getPanel('panel1'); const panel1 = paneview.getPanel('panel1')!;
const panel2 = paneview.getPanel('panel2'); const panel2 = paneview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -340,8 +368,13 @@ describe('componentPaneview', () => {
test('panel is disposed of when removed', () => { test('panel is disposed of when removed', () => {
const paneview = new PaneviewComponent(container, { const paneview = new PaneviewComponent(container, {
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
@ -349,17 +382,17 @@ describe('componentPaneview', () => {
paneview.addPanel({ paneview.addPanel({
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
title: 'Panel 1', title: 'Panel 1',
}); });
paneview.addPanel({ paneview.addPanel({
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
title: 'Panel 2', title: 'Panel 2',
}); });
const panel1 = paneview.getPanel('panel1'); const panel1 = paneview.getPanel('panel1')!;
const panel2 = paneview.getPanel('panel2'); const panel2 = paneview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -372,8 +405,13 @@ describe('componentPaneview', () => {
test('panel is disposed of when fromJSON is called', () => { test('panel is disposed of when fromJSON is called', () => {
const paneview = new PaneviewComponent(container, { const paneview = new PaneviewComponent(container, {
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
@ -381,17 +419,17 @@ describe('componentPaneview', () => {
paneview.addPanel({ paneview.addPanel({
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
title: 'Panel 1', title: 'Panel 1',
}); });
paneview.addPanel({ paneview.addPanel({
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
title: 'Panel 2', title: 'Panel 2',
}); });
const panel1 = paneview.getPanel('panel1'); const panel1 = paneview.getPanel('panel1')!;
const panel2 = paneview.getPanel('panel2'); const panel2 = paneview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -401,4 +439,181 @@ describe('componentPaneview', () => {
expect(panel1Spy).toHaveBeenCalledTimes(1); expect(panel1Spy).toHaveBeenCalledTimes(1);
expect(panel2Spy).toHaveBeenCalledTimes(1); expect(panel2Spy).toHaveBeenCalledTimes(1);
}); });
test('that fromJSON layouts are resized to the current dimensions', async () => {
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(400, 600);
paneview.fromJSON({
size: 6,
views: [
{
size: 1,
data: {
id: 'panel1',
component: 'default',
title: 'Panel 1',
},
minimumSize: 100,
expanded: true,
},
{
size: 2,
data: {
id: 'panel2',
component: 'default',
title: 'Panel 2',
},
expanded: true,
},
{
size: 3,
data: {
id: 'panel3',
component: 'default',
title: 'Panel 3',
},
expanded: true,
},
],
});
// heights slightly differ because header height isn't accounted for
expect(JSON.parse(JSON.stringify(paneview.toJSON()))).toEqual({
size: 600,
views: [
{
size: 122,
data: {
id: 'panel1',
component: 'default',
title: 'Panel 1',
},
expanded: true,
minimumSize: 100,
headerSize: 22,
},
{
size: 22,
data: {
id: 'panel2',
component: 'default',
title: 'Panel 2',
},
expanded: true,
headerSize: 22,
},
{
size: 456,
data: {
id: 'panel3',
component: 'default',
title: 'Panel 3',
},
expanded: true,
headerSize: 22,
},
],
});
});
test('that disableAutoResizing is false by default', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
expect(paneview.disableResizing).toBeFalsy();
});
test('that disableAutoResizing can be enabled', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
expect(paneview.disableResizing).toBeTruthy();
});
test('that setVisible toggles visiblity', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
paneview.layout(1000, 1000);
const panel1 = paneview.addPanel({
id: 'panel1',
component: 'default',
title: 'panel1',
});
const panel2 = paneview.addPanel({
id: 'panel2',
component: 'default',
title: 'panel2',
});
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(false);
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(true);
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
});
test('update className', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
className: 'test-a test-b',
});
expect(paneview.element.className).toBe('test-a test-b');
paneview.updateOptions({ className: 'test-b test-c' });
expect(paneview.element.className).toBe('test-b test-c');
});
}); });

View File

@ -7,7 +7,7 @@ import {
Sizing, Sizing,
Splitview, Splitview,
} from '../../splitview/splitview'; } from '../../splitview/splitview';
import { fireEvent } from '@testing-library/dom';
class Testview implements IView { class Testview implements IView {
private _element: HTMLElement = document.createElement('div'); private _element: HTMLElement = document.createElement('div');
private _size = 0; private _size = 0;
@ -84,6 +84,8 @@ describe('splitview', () => {
beforeEach(() => { beforeEach(() => {
container = document.createElement('div'); container = document.createElement('div');
container.className = 'container'; container.className = 'container';
jest.clearAllMocks();
}); });
test('vertical splitview', () => { test('vertical splitview', () => {
@ -94,7 +96,7 @@ describe('splitview', () => {
expect(splitview.orientation).toBe(Orientation.HORIZONTAL); expect(splitview.orientation).toBe(Orientation.HORIZONTAL);
const viewQuery = container.querySelectorAll( const viewQuery = container.querySelectorAll(
'.split-view-container horizontal' '.dv-split-view-container dv-horizontal'
); );
expect(viewQuery).toBeTruthy(); expect(viewQuery).toBeTruthy();
@ -109,7 +111,7 @@ describe('splitview', () => {
expect(splitview.orientation).toBe(Orientation.VERTICAL); expect(splitview.orientation).toBe(Orientation.VERTICAL);
const viewQuery = container.querySelectorAll( const viewQuery = container.querySelectorAll(
'.split-view-container vertical' '.dv-split-view-container dv-vertical'
); );
expect(viewQuery).toBeTruthy(); expect(viewQuery).toBeTruthy();
@ -126,48 +128,48 @@ describe('splitview', () => {
splitview.addView(new Testview(50, 50)); splitview.addView(new Testview(50, 50));
let viewQuery = container.querySelectorAll( let viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view' '.dv-split-view-container > .dv-view-container > .dv-view'
); );
expect(viewQuery.length).toBe(3); expect(viewQuery.length).toBe(3);
let sashQuery = container.querySelectorAll( let sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash' '.dv-split-view-container > .dv-sash-container > .dv-sash'
); );
expect(sashQuery.length).toBe(2); expect(sashQuery.length).toBe(2);
splitview.removeView(2); splitview.removeView(2);
viewQuery = container.querySelectorAll( viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view' '.dv-split-view-container > .dv-view-container > .dv-view'
); );
expect(viewQuery.length).toBe(2); expect(viewQuery.length).toBe(2);
sashQuery = container.querySelectorAll( sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash' '.dv-split-view-container > .dv-sash-container > .dv-sash'
); );
expect(sashQuery.length).toBe(1); expect(sashQuery.length).toBe(1);
splitview.removeView(0); splitview.removeView(0);
viewQuery = container.querySelectorAll( viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view' '.dv-split-view-container > .dv-view-container > .dv-view'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
sashQuery = container.querySelectorAll( sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash' '.dv-split-view-container > .dv-sash-container > .dv-sash'
); );
expect(sashQuery.length).toBe(0); expect(sashQuery.length).toBe(0);
splitview.removeView(0); splitview.removeView(0);
viewQuery = container.querySelectorAll( viewQuery = container.querySelectorAll(
'.split-view-container > .view-container > .view' '.dv-split-view-container > .dv-view-container > .dv-view'
); );
expect(viewQuery.length).toBe(0); expect(viewQuery.length).toBe(0);
sashQuery = container.querySelectorAll( sashQuery = container.querySelectorAll(
'.split-view-container > .sash-container > .sash' '.dv-split-view-container > .dv-sash-container > .dv-sash'
); );
expect(sashQuery.length).toBe(0); expect(sashQuery.length).toBe(0);
@ -186,14 +188,14 @@ describe('splitview', () => {
splitview.addView(view2); splitview.addView(view2);
let viewQuery = container.querySelectorAll( 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); expect(viewQuery.length).toBe(2);
splitview.setViewVisible(1, false); splitview.setViewVisible(1, false);
viewQuery = container.querySelectorAll( 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); expect(viewQuery.length).toBe(1);
@ -585,8 +587,315 @@ describe('splitview', () => {
expect(container.childNodes.length).toBeGreaterThan(0); expect(container.childNodes.length).toBeGreaterThan(0);
splitview.dispose(); let anyEvents = false;
const listener = splitview.onDidRemoveView((e) => {
anyEvents = true; // disposing of the splitview shouldn't fire onDidRemoveView events
});
splitview.dispose();
listener.dispose();
expect(anyEvents).toBeFalsy();
expect(container.childNodes.length).toBe(0); expect(container.childNodes.length).toBe(0);
}); });
test('dnd: pointer events to move sash', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(400, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
const removeEventListenerSpy = jest.spyOn(
document,
'removeEventListener'
);
const sashElement = container
.getElementsByClassName('dv-sash')
.item(0) as HTMLElement;
// validate the expected state before drag
expect([view1.size, view2.size]).toEqual([200, 200]);
expect(sashElement).toBeTruthy();
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
// start the drag event
fireEvent(
sashElement,
new MouseEvent('pointerdown', { clientX: 50, clientY: 100 })
);
expect(addEventListenerSpy).toBeCalledTimes(3);
// during a sash drag the views should have pointer-events disabled
expect(view1.element.parentElement!.style.pointerEvents).toBe('none');
expect(view2.element.parentElement!.style.pointerEvents).toBe('none');
// expect a delta move of 70 - 50 = 20
fireEvent(
document,
new MouseEvent('pointermove', { clientX: 70, clientY: 110 })
);
expect([view1.size, view2.size]).toEqual([220, 180]);
// expect a delta move of 75 - 70 = 5
fireEvent(
document,
new MouseEvent('pointermove', { clientX: 75, clientY: 110 })
);
expect([view1.size, view2.size]).toEqual([225, 175]);
// end the drag event
fireEvent(
document,
new MouseEvent('pointerup', { clientX: 70, clientY: 110 })
);
expect(removeEventListenerSpy).toBeCalledTimes(3);
// expect pointer-eventes on views to be restored
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
fireEvent(
document,
new MouseEvent('pointermove', { clientX: 100, clientY: 100 })
);
// expect no additional resizes
expect([view1.size, view2.size]).toEqual([225, 175]);
// expect no additional document listeners
expect(addEventListenerSpy).toBeCalledTimes(3);
expect(removeEventListenerSpy).toBeCalledTimes(3);
});
test('setViewVisible', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
splitview.setViewVisible(0, false);
expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]);
splitview.setViewVisible(0, true);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('setViewVisible with one view having high layout priority', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000, LayoutPriority.High);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
splitview.setViewVisible(0, false);
expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]);
splitview.setViewVisible(0, true);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('set view size', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
view1.fireChangeEvent({ size: 0 });
expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]);
view1.fireChangeEvent({ size: 300 });
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('set view size with one view having high layout priority', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
});
splitview.layout(900, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000, LayoutPriority.High);
const view3 = new Testview(0, 1000);
splitview.addView(view1);
splitview.addView(view2);
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
view1.fireChangeEvent({ size: 0 });
expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]);
view1.fireChangeEvent({ size: 300 });
expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]);
});
test('that margins are applied to view sizing', () => {
const splitview = new Splitview(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: false,
margin: 24,
});
splitview.layout(924, 500);
const view1 = new Testview(0, 1000);
const view2 = new Testview(0, 1000);
const view3 = new Testview(0, 1000);
const view4 = new Testview(0, 1000);
splitview.addView(view1);
expect([view1.size]).toEqual([924]);
splitview.addView(view2);
expect([view1.size, view2.size]).toEqual([450, 450]); // 450 + 24 + 450 = 924
splitview.addView(view3);
expect([view1.size, view2.size, view3.size]).toEqual([292, 292, 292]); // 292 + 24 + 292 + 24 + 292 = 924
splitview.addView(view4);
expect([view1.size, view2.size, view3.size, view4.size]).toEqual([
213, 213, 213, 213,
]); // 213 + 24 + 213 + 24 + 213 + 24 + 213 = 924
let viewQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-view-container > .dv-view'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
height: e.style.height,
width: e.style.width,
}));
let sashQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-sash-container > .dv-sash'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
}));
// check HTMLElement positions since these are the ones that really matter
expect(viewQuery).toEqual([
{ left: '0px', top: '', width: '213px', height: '' },
// 213 + 24 = 237
{ left: '237px', top: '', width: '213px', height: '' },
// 237 + 213 + 24 = 474
{ left: '474px', top: '', width: '213px', height: '' },
// 474 + 213 + 24 = 474
{ left: '711px', top: '', width: '213px', height: '' },
// 711 + 213 = 924
]);
// 924 / 4 = 231 view size
// 231 - (24*3/4) = 213 margin adjusted view size
// 213 - 4/2 + 24/2 = 223
expect(sashQuery).toEqual([
// 213 - 4/2 + 24/2 = 223
{ left: '223px', top: '0px' },
// 213 + 24 + 213 = 450
// 450 - 4/2 + 24/2 = 460
{ left: '460px', top: '0px' },
// 213 + 24 + 213 + 24 + 213 = 687
// 687 - 4/2 + 24/2 = 697
{ left: '697px', top: '0px' },
]);
splitview.setViewVisible(0, false);
viewQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-view-container > .dv-view'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
height: e.style.height,
width: e.style.width,
}));
sashQuery = Array.from(
container
.querySelectorAll(
'.dv-split-view-container > .dv-sash-container > .dv-sash'
)
.entries()
)
.map(([i, e]) => e as HTMLElement)
.map((e) => ({
left: e.style.left,
top: e.style.top,
}));
expect(viewQuery).toEqual([
{ left: '0px', top: '', width: '0px', height: '' },
{ left: '0px', top: '', width: '215px', height: '' },
{ left: '239px', top: '', width: '215px', height: '' },
{ left: '478px', top: '', width: '446px', height: '' },
]);
expect(sashQuery).toEqual([
{ left: '0px', top: '0px' },
{ left: '225px', top: '0px' },
{ left: '464px', top: '0px' },
]);
});
}); });

View File

@ -1,4 +1,5 @@
import { PanelDimensionChangeEvent } from '../../api/panelApi'; import { PanelDimensionChangeEvent } from '../../api/panelApi';
import { Emitter } from '../../events';
import { CompositeDisposable } from '../../lifecycle'; import { CompositeDisposable } from '../../lifecycle';
import { Orientation } from '../../splitview/splitview'; import { Orientation } from '../../splitview/splitview';
import { SplitviewComponent } from '../../splitview/splitviewComponent'; import { SplitviewComponent } from '../../splitview/splitviewComponent';
@ -25,22 +26,93 @@ describe('componentSplitview', () => {
container.className = 'container'; container.className = 'container';
}); });
test('remove panel', () => { 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, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
components: { createComponent: (options) => {
testPanel: TestPanel, 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(container, {
orientation: Orientation.VERTICAL,
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(600, 400);
splitview.addPanel({ id: 'panel1', component: 'testPanel' }); const panel1 = splitview.addPanel({
splitview.addPanel({ id: 'panel2', component: 'testPanel' }); id: 'panel1',
splitview.addPanel({ id: 'panel3', component: 'testPanel' }); component: 'default',
});
const panel2 = splitview.addPanel({
id: 'panel2',
component: 'default',
});
const panel1 = splitview.getPanel('panel1'); splitview.movePanel(0, 1);
const panel2 = splitview.getPanel('panel2');
const panel3 = splitview.getPanel('panel3'); splitview.removePanel(panel1);
splitview.dispose();
if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
for (const entry of Array.from(
Emitter.MEMORY_LEAK_WATCHER.events
)) {
console.log(entry[1]);
}
throw new Error('not all listeners disposed');
}
Emitter.setLeakageMonitorEnabled(false);
});
test('remove panel', () => {
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.layout(600, 400);
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')!;
const panel3 = splitview.getPanel('panel3')!;
expect(panel1.api.isActive).toBeFalsy(); expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeFalsy(); expect(panel2.api.isActive).toBeFalsy();
@ -63,8 +135,13 @@ describe('componentSplitview', () => {
test('horizontal dimensions', () => { test('horizontal dimensions', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
splitview.layout(600, 400); splitview.layout(600, 400);
@ -76,8 +153,13 @@ describe('componentSplitview', () => {
test('vertical dimensions', () => { test('vertical dimensions', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
splitview.layout(600, 400); splitview.layout(600, 400);
@ -89,19 +171,24 @@ describe('componentSplitview', () => {
test('api resize', () => { test('api resize', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
splitview.layout(400, 600); splitview.layout(400, 600);
splitview.addPanel({ id: 'panel1', component: 'testPanel' }); splitview.addPanel({ id: 'panel1', component: 'default' });
splitview.addPanel({ id: 'panel2', component: 'testPanel' }); splitview.addPanel({ id: 'panel2', component: 'default' });
splitview.addPanel({ id: 'panel3', component: 'testPanel' }); splitview.addPanel({ id: 'panel3', component: 'default' });
const panel1 = splitview.getPanel('panel1'); const panel1 = splitview.getPanel('panel1')!;
const panel2 = splitview.getPanel('panel2'); const panel2 = splitview.getPanel('panel2')!;
const panel3 = splitview.getPanel('panel3'); const panel3 = splitview.getPanel('panel3')!;
expect(panel1.width).toBe(400); expect(panel1.width).toBe(400);
expect(panel1.height).toBe(200); expect(panel1.height).toBe(200);
@ -141,13 +228,18 @@ describe('componentSplitview', () => {
test('api', () => { test('api', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
splitview.layout(600, 400); splitview.layout(600, 400);
splitview.addPanel({ id: 'panel1', component: 'testPanel' }); splitview.addPanel({ id: 'panel1', component: 'default' });
const panel1 = splitview.getPanel('panel1'); const panel1 = splitview.getPanel('panel1');
@ -158,7 +250,7 @@ describe('componentSplitview', () => {
// expect(panel1?.api.isFocused).toBeFalsy(); // expect(panel1?.api.isFocused).toBeFalsy();
expect(panel1!.api.isVisible).toBeTruthy(); expect(panel1!.api.isVisible).toBeTruthy();
splitview.addPanel({ id: 'panel2', component: 'testPanel' }); splitview.addPanel({ id: 'panel2', component: 'default' });
const panel2 = splitview.getPanel('panel2'); const panel2 = splitview.getPanel('panel2');
@ -182,15 +274,20 @@ describe('componentSplitview', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
components: { createComponent: (options) => {
testPanel: TestPanel, 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: 'panel1', component: 'default' });
splitview.addPanel({ id: 'panel2', component: 'testPanel' }); splitview.addPanel({ id: 'panel2', component: 'default' });
const panel1 = splitview.getPanel('panel1') as SplitviewPanel; const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
const panel2 = splitview.getPanel('panel2') as SplitviewPanel; const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
@ -209,6 +306,8 @@ describe('componentSplitview', () => {
}) })
); );
splitview.layout(600, 400);
expect(panel1Dimensions).toEqual({ width: 600, height: 200 }); expect(panel1Dimensions).toEqual({ width: 600, height: 200 });
expect(panel2Dimensions).toEqual({ width: 600, height: 200 }); expect(panel2Dimensions).toEqual({ width: 600, height: 200 });
@ -231,15 +330,20 @@ describe('componentSplitview', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
components: { createComponent: (options) => {
testPanel: TestPanel, 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: 'panel1', component: 'default' });
splitview.addPanel({ id: 'panel2', component: 'testPanel' }); splitview.addPanel({ id: 'panel2', component: 'default' });
const panel1 = splitview.getPanel('panel1') as SplitviewPanel; const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
const panel2 = splitview.getPanel('panel2') as SplitviewPanel; const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
@ -258,6 +362,8 @@ describe('componentSplitview', () => {
}) })
); );
splitview.layout(600, 400);
expect(panel1Dimensions).toEqual({ width: 300, height: 400 }); expect(panel1Dimensions).toEqual({ width: 300, height: 400 });
expect(panel2Dimensions).toEqual({ width: 300, height: 400 }); expect(panel2Dimensions).toEqual({ width: 300, height: 400 });
@ -278,48 +384,61 @@ describe('componentSplitview', () => {
test('serialization', () => { test('serialization', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
splitview.layout(600, 400); splitview.layout(400, 6);
expect(
container.querySelectorAll('.dv-split-view-container').length
).toBe(1);
splitview.fromJSON({ splitview.fromJSON({
views: [ views: [
{ {
size: 1, size: 1,
data: { id: 'panel1', component: 'testPanel' }, data: { id: 'panel1', component: 'default' },
snap: false, snap: false,
}, },
{ {
size: 2, size: 2,
data: { id: 'panel2', component: 'testPanel' }, data: { id: 'panel2', component: 'default' },
snap: true, snap: true,
}, },
{ size: 3, data: { id: 'panel3', component: 'testPanel' } }, { size: 3, data: { id: 'panel3', component: 'default' } },
], ],
size: 6, size: 6,
orientation: Orientation.VERTICAL, orientation: Orientation.VERTICAL,
activeView: 'panel1', activeView: 'panel1',
}); });
expect(
container.querySelectorAll('.dv-split-view-container').length
).toBe(1);
expect(splitview.length).toBe(3); expect(splitview.length).toBe(3);
expect(JSON.parse(JSON.stringify(splitview.toJSON()))).toEqual({ expect(JSON.parse(JSON.stringify(splitview.toJSON()))).toEqual({
views: [ views: [
{ {
size: 1, size: 1,
data: { id: 'panel1', component: 'testPanel' }, data: { id: 'panel1', component: 'default' },
snap: false, snap: false,
}, },
{ {
size: 2, size: 2,
data: { id: 'panel2', component: 'testPanel' }, data: { id: 'panel2', component: 'default' },
snap: true, snap: true,
}, },
{ {
size: 3, size: 3,
data: { id: 'panel3', component: 'testPanel' }, data: { id: 'panel3', component: 'default' },
snap: false, snap: false,
}, },
], ],
@ -332,8 +451,13 @@ describe('componentSplitview', () => {
test('toJSON shouldnt fire any layout events', () => { test('toJSON shouldnt fire any layout events', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
components: { createComponent: (options) => {
testPanel: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
@ -341,11 +465,11 @@ describe('componentSplitview', () => {
splitview.addPanel({ splitview.addPanel({
id: 'panel1', id: 'panel1',
component: 'testPanel', component: 'default',
}); });
splitview.addPanel({ splitview.addPanel({
id: 'panel2', id: 'panel2',
component: 'testPanel', component: 'default',
}); });
const disposable = splitview.onDidLayoutChange(() => { const disposable = splitview.onDidLayoutChange(() => {
@ -358,39 +482,16 @@ describe('componentSplitview', () => {
disposable.dispose(); disposable.dispose();
}); });
test('dispose of splitviewComponent', () => {
expect(container.childNodes.length).toBe(0);
const splitview = new SplitviewComponent(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', () => { test('panel is disposed of when component is disposed', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
components: { createComponent: (options) => {
default: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
@ -405,8 +506,8 @@ describe('componentSplitview', () => {
component: 'default', component: 'default',
}); });
const panel1 = splitview.getPanel('panel1'); const panel1 = splitview.getPanel('panel1')!;
const panel2 = splitview.getPanel('panel2'); const panel2 = splitview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -420,8 +521,13 @@ describe('componentSplitview', () => {
test('panel is disposed of when removed', () => { test('panel is disposed of when removed', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
components: { createComponent: (options) => {
default: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
@ -436,8 +542,8 @@ describe('componentSplitview', () => {
component: 'default', component: 'default',
}); });
const panel1 = splitview.getPanel('panel1'); const panel1 = splitview.getPanel('panel1')!;
const panel2 = splitview.getPanel('panel2'); const panel2 = splitview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -451,8 +557,13 @@ describe('componentSplitview', () => {
test('panel is disposed of when fromJSON is called', () => { test('panel is disposed of when fromJSON is called', () => {
const splitview = new SplitviewComponent(container, { const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL, orientation: Orientation.HORIZONTAL,
components: { createComponent: (options) => {
default: TestPanel, switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
}, },
}); });
@ -467,8 +578,8 @@ describe('componentSplitview', () => {
component: 'default', component: 'default',
}); });
const panel1 = splitview.getPanel('panel1'); const panel1 = splitview.getPanel('panel1')!;
const panel2 = splitview.getPanel('panel2'); const panel2 = splitview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -482,4 +593,151 @@ describe('componentSplitview', () => {
expect(panel1Spy).toHaveBeenCalledTimes(1); expect(panel1Spy).toHaveBeenCalledTimes(1);
expect(panel2Spy).toHaveBeenCalledTimes(1); expect(panel2Spy).toHaveBeenCalledTimes(1);
}); });
test('that fromJSON layouts are resized to the current dimensions', async () => {
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.layout(400, 600);
splitview.fromJSON({
views: [
{
size: 1,
data: { id: 'panel1', component: 'default' },
snap: false,
},
{
size: 2,
data: { id: 'panel2', component: 'default' },
snap: true,
},
{ size: 3, data: { id: 'panel3', component: 'default' } },
],
size: 6,
orientation: Orientation.VERTICAL,
activeView: 'panel1',
});
expect(JSON.parse(JSON.stringify(splitview.toJSON()))).toEqual({
views: [
{
size: 100,
data: { id: 'panel1', component: 'default' },
snap: false,
},
{
size: 200,
data: { id: 'panel2', component: 'default' },
snap: true,
},
{
size: 300,
data: { id: 'panel3', component: 'default' },
snap: false,
},
],
size: 600,
orientation: Orientation.VERTICAL,
activeView: 'panel1',
});
});
test('that disableAutoResizing is false by default', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
expect(splitview.disableResizing).toBeFalsy();
});
test('that disableAutoResizing can be enabled', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
disableAutoResizing: true,
});
expect(splitview.disableResizing).toBeTruthy();
});
test('that setVisible toggles visiblity', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(1000, 1000);
const panel1 = splitview.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = splitview.addPanel({
id: 'panel2',
component: 'default',
});
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(false);
expect(panel1.api.isVisible).toBeFalsy();
expect(panel2.api.isVisible).toBeTruthy();
panel1.api.setVisible(true);
expect(panel1.api.isVisible).toBeTruthy();
expect(panel2.api.isVisible).toBeTruthy();
});
test('update className', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
className: 'test-a test-b',
});
expect(splitview.element.className).toBe('test-a test-b');
splitview.updateOptions({ className: 'test-b test-c' });
expect(splitview.element.className).toBe('test-b test-c');
});
}); });

View File

@ -1,13 +1,20 @@
import { import {
DockviewDropEvent, DockviewMaximizedGroupChanged,
FloatingGroupOptions,
IDockviewComponent, IDockviewComponent,
MovePanelEvent,
PopoutGroupChangePositionEvent,
PopoutGroupChangeSizeEvent,
SerializedDockview, SerializedDockview,
} from '../dockview/dockviewComponent'; } from '../dockview/dockviewComponent';
import { import {
AddGroupOptions, AddGroupOptions,
AddPanelOptions, AddPanelOptions,
DockviewComponentOptions,
DockviewDndOverlayEvent,
MovementOptions, MovementOptions,
} from '../dockview/options'; } from '../dockview/options';
import { Parameters } from '../panel/types';
import { Direction } from '../gridview/baseComponentGridview'; import { Direction } from '../gridview/baseComponentGridview';
import { import {
AddComponentOptions, AddComponentOptions,
@ -26,7 +33,6 @@ import {
AddSplitviewComponentOptions, AddSplitviewComponentOptions,
ISplitviewComponent, ISplitviewComponent,
SerializedSplitview, SerializedSplitview,
SplitviewComponentUpdateOptions,
} from '../splitview/splitviewComponent'; } from '../splitview/splitviewComponent';
import { IView, Orientation, Sizing } from '../splitview/splitview'; import { IView, Orientation, Sizing } from '../splitview/splitview';
import { ISplitviewPanel } from '../splitview/splitviewPanel'; import { ISplitviewPanel } from '../splitview/splitviewPanel';
@ -34,9 +40,25 @@ import {
DockviewGroupPanel, DockviewGroupPanel,
IDockviewGroupPanel, IDockviewGroupPanel,
} from '../dockview/dockviewGroupPanel'; } from '../dockview/dockviewGroupPanel';
import { Emitter, Event } from '../events'; import { Event } from '../events';
import { IDockviewPanel } from '../dockview/dockviewPanel'; 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,
} from '../dockview/dockviewGroupPanelModel';
import { WillShowOverlayLocationEvent } from '../dockview/events';
import {
PaneviewComponentOptions,
PaneviewDndOverlayEvent,
} from '../paneview/options';
import { SplitviewComponentOptions } from '../splitview/options';
import { GridviewComponentOptions } from '../gridview/options';
export interface CommonApi<T = any> { export interface CommonApi<T = any> {
readonly height: number; readonly height: number;
@ -48,236 +70,413 @@ export interface CommonApi<T = any> {
fromJSON(data: T): void; fromJSON(data: T): void;
toJSON(): T; toJSON(): T;
clear(): void; clear(): void;
dispose(): void;
} }
export class SplitviewApi implements CommonApi<SerializedSplitview> { 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 { get minimumSize(): number {
return this.component.minimumSize; return this.component.minimumSize;
} }
/**
* The maximum size the component can reach where size is measured in the direction of orientation provided.
*/
get maximumSize(): number { get maximumSize(): number {
return this.component.maximumSize; return this.component.maximumSize;
} }
get height(): number { /**
return this.component.height; * Width of the component.
} */
get width(): number { get width(): number {
return this.component.width; return this.component.width;
} }
/**
* Height of the component.
*/
get height(): number {
return this.component.height;
}
/**
* The current number of panels.
*/
get length(): number { get length(): number {
return this.component.length; return this.component.length;
} }
/**
* The current orientation of the component.
*/
get orientation(): Orientation { get orientation(): Orientation {
return this.component.orientation; return this.component.orientation;
} }
/**
* The list of current panels.
*/
get panels(): ISplitviewPanel[] { get panels(): ISplitviewPanel[] {
return this.component.panels; return this.component.panels;
} }
/**
* Invoked after a layout is loaded through the `fromJSON` method.
*/
get onDidLayoutFromJSON(): Event<void> { get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON; 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> { get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange; return this.component.onDidLayoutChange;
} }
/**
* Invoked when a view is added.
*/
get onDidAddView(): Event<IView> { get onDidAddView(): Event<IView> {
return this.component.onDidAddView; return this.component.onDidAddView;
} }
/**
* Invoked when a view is removed.
*/
get onDidRemoveView(): Event<IView> { get onDidRemoveView(): Event<IView> {
return this.component.onDidRemoveView; return this.component.onDidRemoveView;
} }
constructor(private readonly component: ISplitviewComponent) {} 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 { removePanel(panel: ISplitviewPanel, sizing?: Sizing): void {
this.component.removePanel(panel, sizing); this.component.removePanel(panel, sizing);
} }
/**
* Focus the component.
*/
focus(): void { focus(): void {
this.component.focus(); this.component.focus();
} }
/**
* Get the reference to a panel given it's `string` id.
*/
getPanel(id: string): ISplitviewPanel | undefined { getPanel(id: string): ISplitviewPanel | undefined {
return this.component.getPanel(id); return this.component.getPanel(id);
} }
/**
* Layout the panel with a width and height.
*/
layout(width: number, height: number): void { layout(width: number, height: number): void {
return this.component.layout(width, height); 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); return this.component.addPanel(options);
} }
/**
* Move a panel given it's current and desired index.
*/
movePanel(from: number, to: number): void { movePanel(from: number, to: number): void {
this.component.movePanel(from, to); this.component.movePanel(from, to);
} }
/**
* Deserialize a layout to built a splitivew.
*/
fromJSON(data: SerializedSplitview): void { fromJSON(data: SerializedSplitview): void {
this.component.fromJSON(data); this.component.fromJSON(data);
} }
/** Serialize a layout */
toJSON(): SerializedSplitview { toJSON(): SerializedSplitview {
return this.component.toJSON(); return this.component.toJSON();
} }
/**
* Remove all panels and clear the component.
*/
clear(): void { clear(): void {
this.component.clear(); 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> { 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 { get minimumSize(): number {
return this.component.minimumSize; return this.component.minimumSize;
} }
/**
* The maximum size the component can reach where size is measured in the direction of orientation provided.
*/
get maximumSize(): number { get maximumSize(): number {
return this.component.maximumSize; return this.component.maximumSize;
} }
get height(): number { /**
return this.component.height; * Width of the component.
} */
get width(): number { get width(): number {
return this.component.width; return this.component.width;
} }
/**
* Height of the component.
*/
get height(): number {
return this.component.height;
}
/**
* All panel objects.
*/
get panels(): IPaneviewPanel[] { get panels(): IPaneviewPanel[] {
return this.component.panels; return this.component.panels;
} }
/**
* Invoked when any layout change occures, an aggregation of many events.
*/
get onDidLayoutChange(): Event<void> { get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange; return this.component.onDidLayoutChange;
} }
/**
* Invoked after a layout is deserialzied using the `fromJSON` method.
*/
get onDidLayoutFromJSON(): Event<void> { get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON; return this.component.onDidLayoutFromJSON;
} }
/**
* Invoked when a panel is added. May be called multiple times when moving panels.
*/
get onDidAddView(): Event<IPaneviewPanel> { get onDidAddView(): Event<IPaneviewPanel> {
return this.component.onDidAddView; return this.component.onDidAddView;
} }
/**
* Invoked when a panel is removed. May be called multiple times when moving panels.
*/
get onDidRemoveView(): Event<IPaneviewPanel> { get onDidRemoveView(): Event<IPaneviewPanel> {
return this.component.onDidRemoveView; 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) => { get onUnhandledDragOverEvent(): Event<PaneviewDndOverlayEvent> {
emitter.fire({ ...e, api: this }); return this.component.onUnhandledDragOverEvent;
});
emitter.dispose = () => {
disposable.dispose();
emitter.dispose();
};
return emitter.event;
} }
constructor(private readonly component: IPaneviewComponent) {} constructor(private readonly component: IPaneviewComponent) {}
/**
* Remove a panel given the panel object.
*/
removePanel(panel: IPaneviewPanel): void { removePanel(panel: IPaneviewPanel): void {
this.component.removePanel(panel); this.component.removePanel(panel);
} }
/**
* Get a panel object given a `string` id. May return `undefined`.
*/
getPanel(id: string): IPaneviewPanel | undefined { getPanel(id: string): IPaneviewPanel | undefined {
return this.component.getPanel(id); return this.component.getPanel(id);
} }
/**
* Move a panel given it's current and desired index.
*/
movePanel(from: number, to: number): void { movePanel(from: number, to: number): void {
this.component.movePanel(from, to); this.component.movePanel(from, to);
} }
/**
* Focus the component. Will try to focus an active panel if one exists.
*/
focus(): void { focus(): void {
this.component.focus(); 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 { layout(width: number, height: number): void {
this.component.layout(width, height); 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); return this.component.addPanel(options);
} }
/**
* Create a component from a serialized object.
*/
fromJSON(data: SerializedPaneview): void { fromJSON(data: SerializedPaneview): void {
this.component.fromJSON(data); this.component.fromJSON(data);
} }
/**
* Create a serialized object of the current component.
*/
toJSON(): SerializedPaneview { toJSON(): SerializedPaneview {
return this.component.toJSON(); return this.component.toJSON();
} }
/**
* Reset the component back to an empty and default state.
*/
clear(): void { clear(): void {
this.component.clear(); 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> { export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
get minimumHeight(): number { /**
return this.component.minimumHeight; * Width of the component.
} */
get maximumHeight(): number {
return this.component.maximumHeight;
}
get minimumWidth(): number {
return this.component.minimumWidth;
}
get maximumWidth(): number {
return this.component.maximumWidth;
}
get width(): number { get width(): number {
return this.component.width; return this.component.width;
} }
/**
* Height of the component.
*/
get height(): number { get height(): number {
return this.component.height; 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> { get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange; return this.component.onDidLayoutChange;
} }
/**
* Invoked when a panel is added. May be called multiple times when moving panels.
*/
get onDidAddPanel(): Event<IGridviewPanel> { get onDidAddPanel(): Event<IGridviewPanel> {
return this.component.onDidAddGroup; return this.component.onDidAddGroup;
} }
/**
* Invoked when a panel is removed. May be called multiple times when moving panels.
*/
get onDidRemovePanel(): Event<IGridviewPanel> { get onDidRemovePanel(): Event<IGridviewPanel> {
return this.component.onDidRemoveGroup; return this.component.onDidRemoveGroup;
} }
/**
* Invoked when the active panel changes. May be undefined if no panel is active.
*/
get onDidActivePanelChange(): Event<IGridviewPanel | undefined> { get onDidActivePanelChange(): Event<IGridviewPanel | undefined> {
return this.component.onDidActiveGroupChange; return this.component.onDidActiveGroupChange;
} }
/**
* Invoked after a layout is deserialzied using the `fromJSON` method.
*/
get onDidLayoutFromJSON(): Event<void> { get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON; return this.component.onDidLayoutFromJSON;
} }
/**
* All panel objects.
*/
get panels(): IGridviewPanel[] { get panels(): IGridviewPanel[] {
return this.component.groups; return this.component.groups;
} }
/**
* Current orientation. Can be changed after initialization.
*/
get orientation(): Orientation { get orientation(): Orientation {
return this.component.orientation; return this.component.orientation;
} }
@ -288,22 +487,39 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
constructor(private readonly component: IGridviewComponent) {} constructor(private readonly component: IGridviewComponent) {}
/**
* Focus the component. Will try to focus an active panel if one exists.
*/
focus(): void { focus(): void {
this.component.focus(); 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 { layout(width: number, height: number, force = false): void {
this.component.layout(width, height, force); 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); return this.component.addPanel(options);
} }
/**
* Remove a panel given the panel object.
*/
removePanel(panel: IGridviewPanel, sizing?: Sizing): void { removePanel(panel: IGridviewPanel, sizing?: Sizing): void {
this.component.removePanel(panel, sizing); this.component.removePanel(panel, sizing);
} }
/**
* Move a panel in a particular direction relative to another panel.
*/
movePanel( movePanel(
panel: IGridviewPanel, panel: IGridviewPanel,
options: { direction: Direction; reference: string; size?: number } options: { direction: Direction; reference: string; size?: number }
@ -311,171 +527,411 @@ export class GridviewApi implements CommonApi<SerializedGridviewComponent> {
this.component.movePanel(panel, options); this.component.movePanel(panel, options);
} }
/**
* Get a panel object given a `string` id. May return `undefined`.
*/
getPanel(id: string): IGridviewPanel | undefined { getPanel(id: string): IGridviewPanel | undefined {
return this.component.getPanel(id); return this.component.getPanel(id);
} }
/**
* Create a component from a serialized object.
*/
fromJSON(data: SerializedGridviewComponent): void { fromJSON(data: SerializedGridviewComponent): void {
return this.component.fromJSON(data); return this.component.fromJSON(data);
} }
/**
* Create a serialized object of the current component.
*/
toJSON(): SerializedGridviewComponent { toJSON(): SerializedGridviewComponent {
return this.component.toJSON(); return this.component.toJSON();
} }
/**
* Reset the component back to an empty and default state.
*/
clear(): void { clear(): void {
this.component.clear(); 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> { export class DockviewApi implements CommonApi<SerializedDockview> {
/**
* The unique identifier for this instance. Used to manage scope of Drag'n'Drop events.
*/
get id(): string { get id(): string {
return this.component.id; return this.component.id;
} }
/**
* Width of the component.
*/
get width(): number { get width(): number {
return this.component.width; return this.component.width;
} }
/**
* Height of the component.
*/
get height(): number { get height(): number {
return this.component.height; return this.component.height;
} }
/**
* Minimum height of the component.
*/
get minimumHeight(): number { get minimumHeight(): number {
return this.component.minimumHeight; return this.component.minimumHeight;
} }
/**
* Maximum height of the component.
*/
get maximumHeight(): number { get maximumHeight(): number {
return this.component.maximumHeight; return this.component.maximumHeight;
} }
/**
* Minimum width of the component.
*/
get minimumWidth(): number { get minimumWidth(): number {
return this.component.minimumWidth; return this.component.minimumWidth;
} }
/**
* Maximum width of the component.
*/
get maximumWidth(): number { get maximumWidth(): number {
return this.component.maximumWidth; return this.component.maximumWidth;
} }
/**
* Total number of groups.
*/
get size(): number { get size(): number {
return this.component.size; return this.component.size;
} }
/**
* Total number of panels.
*/
get totalPanels(): number { get totalPanels(): number {
return this.component.totalPanels; return this.component.totalPanels;
} }
/**
* Invoked when the active group changes. May be undefined if no group is active.
*/
get onDidActiveGroupChange(): Event<DockviewGroupPanel | undefined> { get onDidActiveGroupChange(): Event<DockviewGroupPanel | undefined> {
return this.component.onDidActiveGroupChange; return this.component.onDidActiveGroupChange;
} }
/**
* Invoked when a group is added. May be called multiple times when moving groups.
*/
get onDidAddGroup(): Event<DockviewGroupPanel> { get onDidAddGroup(): Event<DockviewGroupPanel> {
return this.component.onDidAddGroup; return this.component.onDidAddGroup;
} }
/**
* Invoked when a group is removed. May be called multiple times when moving groups.
*/
get onDidRemoveGroup(): Event<DockviewGroupPanel> { get onDidRemoveGroup(): Event<DockviewGroupPanel> {
return this.component.onDidRemoveGroup; return this.component.onDidRemoveGroup;
} }
/**
* Invoked when the active panel changes. May be undefined if no panel is active.
*/
get onDidActivePanelChange(): Event<IDockviewPanel | undefined> { get onDidActivePanelChange(): Event<IDockviewPanel | undefined> {
return this.component.onDidActivePanelChange; return this.component.onDidActivePanelChange;
} }
/**
* Invoked when a panel is added. May be called multiple times when moving panels.
*/
get onDidAddPanel(): Event<IDockviewPanel> { get onDidAddPanel(): Event<IDockviewPanel> {
return this.component.onDidAddPanel; return this.component.onDidAddPanel;
} }
/**
* Invoked when a panel is removed. May be called multiple times when moving panels.
*/
get onDidRemovePanel(): Event<IDockviewPanel> { get onDidRemovePanel(): Event<IDockviewPanel> {
return this.component.onDidRemovePanel; 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> { get onDidLayoutFromJSON(): Event<void> {
return this.component.onDidLayoutFromJSON; return this.component.onDidLayoutFromJSON;
} }
/**
* Invoked when any layout change occures, an aggregation of many events.
*/
get onDidLayoutChange(): Event<void> { get onDidLayoutChange(): Event<void> {
return this.component.onDidLayoutChange; 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; 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;
}
get onDidOpenPopoutWindowFail(): Event<void> {
return this.component.onDidOpenPopoutWindowFail;
}
/**
* All panel objects.
*/
get panels(): IDockviewPanel[] { get panels(): IDockviewPanel[] {
return this.component.panels; return this.component.panels;
} }
/**
* All group objects.
*/
get groups(): DockviewGroupPanel[] { get groups(): DockviewGroupPanel[] {
return this.component.groups; return this.component.groups;
} }
/**
* Active panel object.
*/
get activePanel(): IDockviewPanel | undefined { get activePanel(): IDockviewPanel | undefined {
return this.component.activePanel; return this.component.activePanel;
} }
/**
* Active group object.
*/
get activeGroup(): DockviewGroupPanel | undefined { get activeGroup(): DockviewGroupPanel | undefined {
return this.component.activeGroup; return this.component.activeGroup;
} }
constructor(private readonly component: IDockviewComponent) {} constructor(private readonly component: IDockviewComponent) {}
getTabHeight(): number | undefined { /**
return this.component.tabHeight; * Focus the component. Will try to focus an active panel if one exists.
} */
setTabHeight(height: number | undefined): void {
this.component.tabHeight = height;
}
focus(): void { focus(): void {
this.component.focus(); this.component.focus();
} }
/**
* Get a panel object given a `string` id. May return `undefined`.
*/
getPanel(id: string): IDockviewPanel | undefined { getPanel(id: string): IDockviewPanel | undefined {
return this.component.getGroupPanel(id); 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 { layout(width: number, height: number, force = false): void {
this.component.layout(width, height, force); 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); return this.component.addPanel(options);
} }
addGroup(options?: AddGroupOptions): IDockviewGroupPanel { /**
* 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); return this.component.addGroup(options);
} }
moveToNext(options?: MovementOptions): void { /**
this.component.moveToNext(options); * Close all groups and panels.
} */
moveToPrevious(options?: MovementOptions): void {
this.component.moveToPrevious(options);
}
closeAllGroups(): void { closeAllGroups(): void {
return this.component.closeAllGroups(); return this.component.closeAllGroups();
} }
/**
* Remove a group and any panels within the group.
*/
removeGroup(group: IDockviewGroupPanel): void { removeGroup(group: IDockviewGroupPanel): void {
this.component.removeGroup(<DockviewGroupPanel>group); this.component.removeGroup(<DockviewGroupPanel>group);
} }
getGroup(id: string): DockviewGroupPanel | undefined { /**
* Get a group object given a `string` id. May return undefined.
*/
getGroup(id: string): IDockviewGroupPanel | undefined {
return this.component.getPanel(id); return this.component.getPanel(id);
} }
/**
* Add a floating group
*/
addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel,
options?: FloatingGroupOptions
): void {
return this.component.addFloatingGroup(item, options);
}
/**
* Create a component from a serialized object.
*/
fromJSON(data: SerializedDockview): void { fromJSON(data: SerializedDockview): void {
this.component.fromJSON(data); this.component.fromJSON(data);
} }
/**
* Create a serialized object of the current component.
*/
toJSON(): SerializedDockview { toJSON(): SerializedDockview {
return this.component.toJSON(); return this.component.toJSON();
} }
/**
* Reset the component back to an empty and default state.
*/
clear(): void { clear(): void {
this.component.clear(); this.component.clear();
} }
/**
* Move the focus progmatically to the next panel or group.
*/
moveToNext(options?: MovementOptions): void {
this.component.moveToNext(options);
}
/**
* Move the focus progmatically to the previous panel or group.
*/
moveToPrevious(options?: MovementOptions): void {
this.component.moveToPrevious(options);
}
maximizeGroup(panel: IDockviewPanel): void {
this.component.maximizeGroup(panel.group);
}
hasMaximizedGroup(): boolean {
return this.component.hasMaximizedGroup();
}
exitMaximizedGroup(): void {
this.component.exitMaximizedGroup();
}
get onDidMaximizedGroupChange(): Event<DockviewMaximizedGroupChanged> {
return this.component.onDidMaximizedGroupChange;
}
/**
* Add a popout group in a new Window
*/
addPopoutGroup(
item: IDockviewPanel | DockviewGroupPanel,
options?: {
position?: Box;
popoutUrl?: string;
onDidOpen?: (event: { id: string; window: Window }) => void;
onWillClose?: (event: { id: string; window: Window }) => void;
}
): Promise<boolean> {
return this.component.addPopoutGroup(item, options);
}
updateOptions(options: Partial<DockviewComponentOptions>) {
this.component.updateOptions(options);
}
/**
* Release resources and teardown component. Do not call when using framework versions of dockview.
*/
dispose(): void {
this.component.dispose();
}
} }

View File

@ -0,0 +1,145 @@
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;
/**
* Whether to skip setting the group as active after moving
*/
skipSetActive?: boolean;
}
export interface DockviewGroupPanelApi extends GridviewPanelApi {
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 location: DockviewGroupLocation;
}
const NOT_INITIALIZED_MESSAGE =
'dockview: DockviewGroupPanelApiImpl not initialized';
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
private _group: DockviewGroupPanel | undefined;
readonly _onDidLocationChange =
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
this._onDidLocationChange.event;
readonly _onDidActivePanelChange = new Emitter<DockviewGroupChangeEvent>();
readonly onDidActivePanelChange = this._onDidActivePanelChange.event;
get location(): DockviewGroupLocation {
if (!this._group) {
throw new Error(NOT_INITIALIZED_MESSAGE);
}
return this._group.model.location;
}
constructor(id: string, private readonly accessor: DockviewComponent) {
super(id, '__dockviewgroup__');
this.addDisposables(
this._onDidLocationChange,
this._onDidActivePanelChange
);
}
close(): void {
if (!this._group) {
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);
}
const group =
options.group ??
this.accessor.addGroup({
direction: positionToDirection(options.position ?? 'right'),
skipSetActive: options.skipSetActive ?? false,
});
this.accessor.moveGroupOrPanel({
from: { groupId: this._group.id },
to: {
group,
position: options.group
? options.position ?? 'center'
: 'center',
index: options.index,
},
skipSetActive: options.skipSetActive,
});
}
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 {
this._group = group;
}
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { Emitter, Event } from '../events'; import { DockviewEvent, Emitter, Event } from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { IPanel, Parameters } from '../panel/types'; import { IPanel, Parameters } from '../panel/types';
@ -24,9 +24,14 @@ export interface PanelApi {
readonly onDidFocusChange: Event<FocusEvent>; readonly onDidFocusChange: Event<FocusEvent>;
readonly onDidVisibilityChange: Event<VisibilityEvent>; readonly onDidVisibilityChange: Event<VisibilityEvent>;
readonly onDidActiveChange: Event<ActiveEvent>; readonly onDidActiveChange: Event<ActiveEvent>;
setVisible(isVisible: boolean): void; readonly onDidParametersChange: Event<Parameters>;
setActive(): void; setActive(): void;
setVisible(isVisible: boolean): void;
updateParameters(parameters: Parameters): 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 * 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 * The panel height in pixels
*/ */
readonly height: number; readonly height: number;
readonly onWillFocus: Event<WillFocusEvent>;
getParameters<T extends Parameters = Parameters>(): T;
}
export class WillFocusEvent extends DockviewEvent {
constructor() {
super();
}
} }
/** /**
@ -62,79 +77,62 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
private _isVisible = true; private _isVisible = true;
private _width = 0; private _width = 0;
private _height = 0; private _height = 0;
private _parameters: Parameters = {};
private readonly panelUpdatesDisposable = new MutableDisposable(); private readonly panelUpdatesDisposable = new MutableDisposable();
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>({ readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>();
replay: true,
});
readonly onDidDimensionsChange = this._onDidDimensionChange.event; readonly onDidDimensionsChange = this._onDidDimensionChange.event;
//
readonly _onDidChangeFocus = new Emitter<FocusEvent>({ readonly _onDidChangeFocus = new Emitter<FocusEvent>();
replay: true,
});
readonly onDidFocusChange: Event<FocusEvent> = this._onDidChangeFocus.event; readonly onDidFocusChange: Event<FocusEvent> = this._onDidChangeFocus.event;
// //
readonly _onFocusEvent = new Emitter<void>(); readonly _onWillFocus = new Emitter<WillFocusEvent>();
readonly onFocusEvent: Event<void> = this._onFocusEvent.event; readonly onWillFocus: Event<WillFocusEvent> = this._onWillFocus.event;
// //
readonly _onDidVisibilityChange = new Emitter<VisibilityEvent>({ readonly _onDidVisibilityChange = new Emitter<VisibilityEvent>();
replay: true,
});
readonly onDidVisibilityChange: Event<VisibilityEvent> = readonly onDidVisibilityChange: Event<VisibilityEvent> =
this._onDidVisibilityChange.event; this._onDidVisibilityChange.event;
//
readonly _onVisibilityChange = new Emitter<VisibilityEvent>(); readonly _onWillVisibilityChange = new Emitter<VisibilityEvent>();
readonly onVisibilityChange: Event<VisibilityEvent> = readonly onWillVisibilityChange: Event<VisibilityEvent> =
this._onVisibilityChange.event; this._onWillVisibilityChange.event;
//
readonly _onDidActiveChange = new Emitter<ActiveEvent>({ readonly _onDidActiveChange = new Emitter<ActiveEvent>();
replay: true,
});
readonly onDidActiveChange: Event<ActiveEvent> = readonly onDidActiveChange: Event<ActiveEvent> =
this._onDidActiveChange.event; this._onDidActiveChange.event;
//
readonly _onActiveChange = new Emitter<void>(); readonly _onActiveChange = new Emitter<void>();
readonly onActiveChange: Event<void> = this._onActiveChange.event; 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; return this._isFocused;
} }
get isActive() { get isActive(): boolean {
return this._isActive; return this._isActive;
} }
get isVisible() {
get isVisible(): boolean {
return this._isVisible; return this._isVisible;
} }
get width() { get width(): number {
return this._width; return this._width;
} }
get height() { get height(): number {
return this._height; return this._height;
} }
constructor(readonly id: string) { constructor(readonly id: string, readonly component: string) {
super(); super();
this.addDisposables( this.addDisposables(
this.panelUpdatesDisposable,
this._onDidDimensionChange,
this._onDidChangeFocus,
this._onDidVisibilityChange,
this._onDidActiveChange,
this._onFocusEvent,
this._onActiveChange,
this._onVisibilityChange,
this._onUpdateParameters,
this.onDidFocusChange((event) => { this.onDidFocusChange((event) => {
this._isFocused = event.isFocused; this._isFocused = event.isFocused;
}), }),
@ -147,24 +145,37 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
this.onDidDimensionsChange((event) => { this.onDidDimensionsChange((event) => {
this._width = event.width; this._width = event.width;
this._height = event.height; this._height = event.height;
}) }),
this.panelUpdatesDisposable,
this._onDidDimensionChange,
this._onDidChangeFocus,
this._onDidVisibilityChange,
this._onDidActiveChange,
this._onWillFocus,
this._onActiveChange,
this._onWillFocus,
this._onWillVisibilityChange,
this._onDidParametersChange
); );
} }
getParameters<T extends Parameters = Parameters>(): T {
return this._parameters as T;
}
public initialize(panel: IPanel): void { public initialize(panel: IPanel): void {
this.panelUpdatesDisposable.value = this._onUpdateParameters.event( this.panelUpdatesDisposable.value = this._onDidParametersChange.event(
(parameters) => { (parameters) => {
this._parameters = parameters;
panel.update({ panel.update({
params: { params: parameters,
params: parameters,
},
}); });
} }
); );
} }
setVisible(isVisible: boolean) { setVisible(isVisible: boolean): void {
this._onVisibilityChange.fire({ isVisible }); this._onWillVisibilityChange.fire({ isVisible });
} }
setActive(): void { setActive(): void {
@ -172,10 +183,6 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
} }
updateParameters(parameters: Parameters): void { updateParameters(parameters: Parameters): void {
this._onUpdateParameters.fire(parameters); this._onDidParametersChange.fire(parameters);
}
dispose() {
super.dispose();
} }
} }

View File

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

View File

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

View File

@ -61,3 +61,13 @@ export function firstIndex<T>(
return -1; return -1;
} }
export function remove<T>(array: T[], value: T): boolean {
const index = array.findIndex((t) => t === value);
if (index > -1) {
array.splice(index, 1);
return true;
}
return false;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,27 @@
.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);
background-color: var(--dv-drag-over-background-color);
opacity: 1;
/* GPU optimizations */
will-change: transform, opacity;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
contain: layout paint;
transition: opacity var(--dv-transition-duration) ease-in,
transform var(--dv-transition-duration) ease-out;
}
}

View File

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

View File

@ -1,38 +1,53 @@
.drop-target { .dv-drop-target {
position: relative; position: relative;
--dv-transition-duration: 70ms;
> .drop-target-dropzone { > .dv-drop-target-dropzone {
position: absolute; position: absolute;
left: 0px; left: 0px;
top: 0px; top: 0px;
height: 100%; height: 100%;
width: 100%; width: 100%;
z-index: 10000; z-index: 1000;
pointer-events: none;
> .drop-target-selection { > .dv-drop-target-selection {
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
width: 100%; width: 100%;
border: var(--dv-drag-over-border);
background-color: var(--dv-drag-over-background-color); 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 .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; will-change: transform;
pointer-events: none; pointer-events: none;
&.small-top { &.dv-drop-target-top {
border-top: 1px solid var(--dv-drag-over-border-color); &.dv-drop-target-small-vertical {
border-top: 1px solid var(--dv-drag-over-border-color);
}
} }
&.small-bottom { &.dv-drop-target-bottom {
border-bottom: 1px solid var(--dv-drag-over-border-color); &.dv-drop-target-small-vertical {
border-bottom: 1px solid var(--dv-drag-over-border-color);
}
} }
&.small-left { &.dv-drop-target-left {
border-left: 1px solid var(--dv-drag-over-border-color); &.dv-drop-target-small-horizontal {
border-left: 1px solid var(--dv-drag-over-border-color);
}
} }
&.small-right { &.dv-drop-target-right {
border-right: 1px solid var(--dv-drag-over-border-color); &.dv-drop-target-small-horizontal {
border-right: 1px solid var(--dv-drag-over-border-color);
}
} }
} }
} }

View File

@ -1,12 +1,97 @@
import { toggleClass } from '../dom'; import { toggleClass } from '../dom';
import { Emitter, Event } from '../events'; import { DockviewEvent, Emitter, Event } from '../events';
import { CompositeDisposable } from '../lifecycle'; import { CompositeDisposable } from '../lifecycle';
import { DragAndDropObserver } from './dnd'; import { DragAndDropObserver } from './dnd';
import { clamp } from '../math'; import { clamp } from '../math';
import { Direction } from '../gridview/baseComponentGridview'; import { Direction } from '../gridview/baseComponentGridview';
function numberOrFallback(maybeNumber: any, fallback: number): number { interface DropTargetRect {
return typeof maybeNumber === 'number' ? maybeNumber : fallback; top: number;
left: number;
width: number;
height: number;
}
function setGPUOptimizedBounds(element: HTMLElement, bounds: DropTargetRect): void {
const { top, left, width, height } = bounds;
const topPx = `${Math.round(top)}px`;
const leftPx = `${Math.round(left)}px`;
const widthPx = `${Math.round(width)}px`;
const heightPx = `${Math.round(height)}px`;
// Use traditional positioning but maintain GPU layer
element.style.top = topPx;
element.style.left = leftPx;
element.style.width = widthPx;
element.style.height = heightPx;
element.style.visibility = 'visible';
// Ensure GPU layer is maintained
if (!element.style.transform || element.style.transform === '') {
element.style.transform = 'translate3d(0, 0, 0)';
}
}
function setGPUOptimizedBoundsFromStrings(element: HTMLElement, bounds: {
top: string;
left: string;
width: string;
height: string;
}): void {
const { top, left, width, height } = bounds;
// Use traditional positioning but maintain GPU layer
element.style.top = top;
element.style.left = left;
element.style.width = width;
element.style.height = height;
element.style.visibility = 'visible';
// Ensure GPU layer is maintained
if (!element.style.transform || element.style.transform === '') {
element.style.transform = 'translate3d(0, 0, 0)';
}
}
function checkBoundsChanged(element: HTMLElement, bounds: DropTargetRect): boolean {
const { top, left, width, height } = bounds;
const topPx = `${Math.round(top)}px`;
const leftPx = `${Math.round(left)}px`;
const widthPx = `${Math.round(width)}px`;
const heightPx = `${Math.round(height)}px`;
// Check if position or size changed (back to traditional method)
return element.style.top !== topPx ||
element.style.left !== leftPx ||
element.style.width !== widthPx ||
element.style.height !== heightPx;
}
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 { export function directionToPosition(direction: Direction): Position {
@ -43,140 +128,280 @@ 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 Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
export type CanDisplayOverlay = export type CanDisplayOverlay = (
| boolean dragEvent: DragEvent,
| ((dragEvent: DragEvent, state: Position) => boolean); 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 { export class Droptarget extends CompositeDisposable {
private targetElement: HTMLElement | undefined; private targetElement: HTMLElement | undefined;
private overlayElement: HTMLElement | undefined; private overlayElement: HTMLElement | undefined;
private _state: Position | undefined; private _state: Position | undefined;
private _acceptedTargetZonesSet: Set<Position>;
private readonly _onDrop = new Emitter<DroptargetEvent>(); private readonly _onDrop = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event; 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 { get state(): Position | undefined {
return this._state; return this._state;
} }
constructor( constructor(
private readonly element: HTMLElement, private readonly element: HTMLElement,
private readonly options: { private readonly options: DroptargetOptions
canDisplayOverlay: CanDisplayOverlay;
acceptedTargetZones: Position[];
overlayModel?: {
size?: { value: number; type: 'pixels' | 'percentage' };
activationSize?: {
value: number;
type: 'pixels' | 'percentage';
};
};
}
) { ) {
super(); super();
this._disabled = false;
// use a set to take advantage of #<set>.has // use a set to take advantage of #<set>.has
const acceptedTargetZonesSet = new Set( this._acceptedTargetZonesSet = new Set(
this.options.acceptedTargetZones this.options.acceptedTargetZones
); );
this.addDisposables( this.dnd = new DragAndDropObserver(this.element, {
this._onDrop, onDragEnter: () => {
new DragAndDropObserver(this.element, { this.options.getOverrideTarget?.()?.getElements();
onDragEnter: () => undefined, },
onDragOver: (e) => { onDragOver: (e) => {
const width = this.element.clientWidth; Droptarget.ACTUAL_TARGET = this;
const height = this.element.clientHeight;
if (width === 0 || height === 0) { const overrideTarget = this.options.getOverrideTarget?.();
return; // avoid div!0
}
const rect = ( if (this._acceptedTargetZonesSet.size === 0) {
e.currentTarget as HTMLElement if (overrideTarget) {
).getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const quadrant = this.calculateQuadrant(
acceptedTargetZonesSet,
x,
y,
width,
height
);
if (quadrant === null) {
// no drop target should be displayed
this.removeDropTarget();
return; return;
} }
if (typeof this.options.canDisplayOverlay === 'boolean') {
if (!this.options.canDisplayOverlay) {
return;
}
} else if (!this.options.canDisplayOverlay(e, quadrant)) {
return;
}
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);
}
if (this.options.acceptedTargetZones.length === 0) {
return;
}
if (!this.targetElement || !this.overlayElement) {
return;
}
this.toggleClasses(quadrant, width, height);
this.setState(quadrant);
},
onDragLeave: () => {
this.removeDropTarget(); this.removeDropTarget();
}, return;
onDragEnd: () => { }
const target =
this.options.getOverlayOutline?.() ?? this.element;
const width = target.offsetWidth;
const height = target.offsetHeight;
if (width === 0 || height === 0) {
return; // avoid div!0
}
const rect = (
e.currentTarget as HTMLElement
).getBoundingClientRect();
const x = (e.clientX ?? 0) - rect.left;
const y = (e.clientY ?? 0) - rect.top;
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(); this.removeDropTarget();
}, return;
onDrop: (e) => { }
e.preventDefault();
const state = this._state;
if (!this.options.canDisplayOverlay(e, quadrant)) {
if (overrideTarget) {
return;
}
this.removeDropTarget(); this.removeDropTarget();
return;
}
if (state) { 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();
return;
}
this.markAsUsed(e);
if (overrideTarget) {
//
} 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);
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 // only stop the propagation of the event if we are dealing with it
// which is only when the target has state // which is only when the target has state
e.stopPropagation(); 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);
} }
public dispose(): void { setTargetZones(acceptedTargetZones: Position[]): void {
this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
}
setOverlayModel(model: DroptargetOverlayModel): void {
this.options.overlayModel = model;
}
dispose(): void {
this.removeDropTarget(); this.removeDropTarget();
super.dispose();
}
/**
* Add a property to the event object for other potential listeners to check
*/
private markAsUsed(event: DragEvent): void {
(event as any)[Droptarget.USED_EVENT_ID] = true;
}
/**
* 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];
return typeof value === 'boolean' && value;
} }
private toggleClasses( private toggleClasses(
@ -184,12 +409,14 @@ export class Droptarget extends CompositeDisposable {
width: number, width: number,
height: number height: number
): void { ): void {
if (!this.overlayElement) { const target = this.options.getOverrideTarget?.();
if (!target && !this.overlayElement) {
return; return;
} }
const isSmallX = width < 100; const isSmallX = width < SMALL_WIDTH_BOUNDARY;
const isSmallY = height < 100; const isSmallY = height < SMALL_HEIGHT_BOUNDARY;
const isLeft = quadrant === 'left'; const isLeft = quadrant === 'left';
const isRight = quadrant === 'right'; const isRight = quadrant === 'right';
@ -201,68 +428,159 @@ export class Droptarget extends CompositeDisposable {
const topClass = !isSmallY && isTop; const topClass = !isSmallY && isTop;
const bottomClass = !isSmallY && isBottom; const bottomClass = !isSmallY && isBottom;
let size = 0.5; let size = 1;
if (this.options.overlayModel?.size?.type === 'percentage') { const sizeOptions = this.options.overlayModel?.size ?? DEFAULT_SIZE;
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
}
if (this.options.overlayModel?.size?.type === 'pixels') { if (sizeOptions.type === 'percentage') {
size = clamp(sizeOptions.value, 0, 100) / 100;
} else {
if (rightClass || leftClass) { if (rightClass || leftClass) {
size = size = clamp(0, sizeOptions.value, width) / width;
clamp(0, this.options.overlayModel.size.value, width) /
width;
} }
if (topClass || bottomClass) { if (topClass || bottomClass) {
size = size = clamp(0, sizeOptions.value, height) / height;
clamp(0, this.options.overlayModel.size.value, height) /
height;
} }
} }
const translate = (1 - size) / 2; if (target) {
const scale = size; 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;
}
// Use GPU-optimized bounds checking and setting
if (!checkBoundsChanged(overlay, box)) {
return;
}
setGPUOptimizedBounds(overlay, box);
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) { if (rightClass) {
transform = `translateX(${100 * translate}%) scaleX(${scale})`; box.left = `${100 * (1 - size)}%`;
box.width = `${100 * size}%`;
} else if (leftClass) { } else if (leftClass) {
transform = `translateX(-${100 * translate}%) scaleX(${scale})`; box.width = `${100 * size}%`;
} else if (topClass) { } else if (topClass) {
transform = `translateY(-${100 * translate}%) scaleY(${scale})`; box.height = `${100 * size}%`;
} else if (bottomClass) { } else if (bottomClass) {
transform = `translateY(${100 * translate}%) scaleY(${scale})`; box.top = `${100 * (1 - size)}%`;
} else { box.height = `${100 * size}%`;
transform = '';
} }
this.overlayElement.style.transform = transform; setGPUOptimizedBoundsFromStrings(this.overlayElement, box);
toggleClass(this.overlayElement, 'small-right', isSmallX && isRight); toggleClass(
toggleClass(this.overlayElement, 'small-left', isSmallX && isLeft); this.overlayElement,
toggleClass(this.overlayElement, 'small-top', isSmallY && isTop); 'dv-drop-target-small-vertical',
toggleClass(this.overlayElement, 'small-bottom', isSmallY && isBottom); isSmallY
} );
toggleClass(
private setState(quadrant: Position): void { this.overlayElement,
switch (quadrant) { 'dv-drop-target-small-horizontal',
case 'top': isSmallX
this._state = 'top'; );
break; toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
case 'left': toggleClass(this.overlayElement, 'dv-drop-target-right', isRight);
this._state = 'left'; toggleClass(this.overlayElement, 'dv-drop-target-top', isTop);
break; toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom);
case 'bottom': toggleClass(
this._state = 'bottom'; this.overlayElement,
break; 'dv-drop-target-center',
case 'right': quadrant === 'center'
this._state = 'right'; );
break;
case 'center':
this._state = 'center';
break;
}
} }
private calculateQuadrant( private calculateQuadrant(
@ -272,14 +590,11 @@ export class Droptarget extends CompositeDisposable {
width: number, width: number,
height: number height: number
): Position | null { ): Position | null {
const isPercentage = const activationSizeOptions =
this.options.overlayModel?.activationSize === undefined || this.options.overlayModel?.activationSize ??
this.options.overlayModel?.activationSize?.type === 'percentage'; DEFAULT_ACTIVATION_SIZE;
const value = numberOrFallback( const isPercentage = activationSizeOptions.type === 'percentage';
this.options?.overlayModel?.activationSize?.value,
20
);
if (isPercentage) { if (isPercentage) {
return calculateQuadrantAsPercentage( return calculateQuadrantAsPercentage(
@ -288,7 +603,7 @@ export class Droptarget extends CompositeDisposable {
y, y,
width, width,
height, height,
value activationSizeOptions.value
); );
} }
@ -298,17 +613,19 @@ export class Droptarget extends CompositeDisposable {
y, y,
width, width,
height, height,
value activationSizeOptions.value
); );
} }
private removeDropTarget(): void { private removeDropTarget(): void {
if (this.targetElement) { if (this.targetElement) {
this._state = undefined; this._state = undefined;
this.element.removeChild(this.targetElement); this.targetElement.parentElement?.classList.remove(
'dv-drop-target'
);
this.targetElement.remove();
this.targetElement = undefined; this.targetElement = undefined;
this.overlayElement = undefined; this.overlayElement = undefined;
this.element.classList.remove('drop-target');
} }
} }
} }

View File

@ -2,13 +2,17 @@ import { addClasses, removeClasses } from '../dom';
export function addGhostImage( export function addGhostImage(
dataTransfer: DataTransfer, dataTransfer: DataTransfer,
ghostElement: HTMLElement ghostElement: HTMLElement,
options?: { x?: number; y?: number }
): void { ): void {
// class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues // class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
addClasses(ghostElement, 'dv-dragged'); 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); document.body.appendChild(ghostElement);
dataTransfer.setDragImage(ghostElement, 0, 0); dataTransfer.setDragImage(ghostElement, options?.x ?? 0, options?.y ?? 0);
setTimeout(() => { setTimeout(() => {
removeClasses(ghostElement, 'dv-dragged'); removeClasses(ghostElement, 'dv-dragged');

View File

@ -1,4 +1,7 @@
import { DockviewComponent } from '../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel'; import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { quasiPreventDefault } from '../dom';
import { addDisposableListener } from '../events';
import { IDisposable } from '../lifecycle'; import { IDisposable } from '../lifecycle';
import { DragHandler } from './abstractDragHandler'; import { DragHandler } from './abstractDragHandler';
import { LocalSelectionTransfer, PanelTransfer } from './dataTransfer'; import { LocalSelectionTransfer, PanelTransfer } from './dataTransfer';
@ -10,15 +13,42 @@ export class GroupDragHandler extends DragHandler {
constructor( constructor(
element: HTMLElement, element: HTMLElement,
private readonly accessorId: string, private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanel private readonly group: DockviewGroupPanel
) { ) {
super(element); super(element);
this.addDisposables(
addDisposableListener(
element,
'pointerdown',
(e) => {
if (e.shiftKey) {
/**
* You cannot call e.preventDefault() because that will prevent drag events from firing
* but we also need to stop any group overlay drag events from occuring
* Use a custom event marker that can be checked by the overlay drag events
*/
quasiPreventDefault(e);
}
},
true
)
);
} }
getData(dataTransfer: DataTransfer | null): IDisposable { override isCancelled(_event: DragEvent): boolean {
if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
return true;
}
return false;
}
getData(dragEvent: DragEvent): IDisposable {
const dataTransfer = dragEvent.dataTransfer;
this.panelTransfer.setData( this.panelTransfer.setData(
[new PanelTransfer(this.accessorId, this.group.id, null)], [new PanelTransfer(this.accessor.id, this.group.id, null)],
PanelTransfer.prototype PanelTransfer.prototype
); );
@ -42,9 +72,11 @@ export class GroupDragHandler extends DragHandler {
ghostElement.style.lineHeight = '20px'; ghostElement.style.lineHeight = '20px';
ghostElement.style.borderRadius = '12px'; ghostElement.style.borderRadius = '12px';
ghostElement.style.position = 'absolute'; ghostElement.style.position = 'absolute';
ghostElement.style.pointerEvents = 'none';
ghostElement.style.top = '-9999px';
ghostElement.textContent = `Multiple Panels (${this.group.size})`; ghostElement.textContent = `Multiple Panels (${this.group.size})`;
addGhostImage(dataTransfer, ghostElement); addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
} }
return { return {
@ -53,8 +85,4 @@ export class GroupDragHandler extends DragHandler {
}, },
}; };
} }
public dispose(): void {
//
}
} }

View File

@ -6,8 +6,13 @@ import {
import { Emitter, Event } from '../../../events'; import { Emitter, Event } from '../../../events';
import { trackFocus } from '../../../dom'; import { trackFocus } from '../../../dom';
import { IDockviewPanel } from '../../dockviewPanel'; 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 { export interface IContentContainer extends IDisposable {
readonly dropTarget: Droptarget;
onDidFocus: Event<void>; onDidFocus: Event<void>;
onDidBlur: Event<void>; onDidBlur: Event<void>;
element: HTMLElement; element: HTMLElement;
@ -16,15 +21,16 @@ export interface IContentContainer extends IDisposable {
closePanel: () => void; closePanel: () => void;
show(): void; show(): void;
hide(): void; hide(): void;
renderPanel(panel: IDockviewPanel, options: { asActive: boolean }): void;
} }
export class ContentContainer export class ContentContainer
extends CompositeDisposable extends CompositeDisposable
implements IContentContainer implements IContentContainer
{ {
private _element: HTMLElement; private readonly _element: HTMLElement;
private panel: IDockviewPanel | undefined; private panel: IDockviewPanel | undefined;
private disposable = new MutableDisposable(); private readonly disposable = new MutableDisposable();
private readonly _onDidFocus = new Emitter<void>(); private readonly _onDidFocus = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event; readonly onDidFocus: Event<void> = this._onDidFocus.event;
@ -36,19 +42,57 @@ export class ContentContainer
return this._element; return this._element;
} }
constructor() { readonly dropTarget: Droptarget;
constructor(
private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanelModel
) {
super(); super();
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'content-container'; this._element.className = 'dv-content-container';
this._element.tabIndex = -1; this._element.tabIndex = -1;
this.addDisposables(this._onDidFocus, this._onDidBlur); this.addDisposables(this._onDidFocus, this._onDidBlur);
// for hosted containers const target = group.dropTargetContainer;
// 1) register a drop target on the host
// 2) register window dragStart events to disable pointer events this.dropTarget = new Droptarget(this.element, {
// 3) register dragEnd events getOverlayOutline: () => {
// 4) register mouseMove events (if no buttons are present we take this as a dragEnd event) 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 { show(): void {
@ -59,46 +103,77 @@ export class ContentContainer
this.element.style.display = 'none'; this.element.style.display = 'none';
} }
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;
let container: HTMLElement;
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}'`
);
}
if (doRender) {
const focusTracker = trackFocus(container);
const disposable = new CompositeDisposable();
disposable.addDisposables(
focusTracker,
focusTracker.onDidFocus(() => this._onDidFocus.fire()),
focusTracker.onDidBlur(() => this._onDidBlur.fire())
);
this.disposable.value = disposable;
}
}
public openPanel(panel: IDockviewPanel): void { public openPanel(panel: IDockviewPanel): void {
if (this.panel === panel) { if (this.panel === panel) {
return; return;
} }
if (this.panel) {
if (this.panel.view?.content) {
this._element.removeChild(this.panel.view.content.element);
}
this.panel = undefined;
}
this.panel = panel;
const disposable = new CompositeDisposable(); this.renderPanel(panel);
if (this.panel.view) {
const _onDidFocus = this.panel.view.content.onDidFocus;
const _onDidBlur = this.panel.view.content.onDidBlur;
const { onDidFocus, onDidBlur } = trackFocus(this._element);
disposable.addDisposables(
onDidFocus(() => this._onDidFocus.fire()),
onDidBlur(() => this._onDidBlur.fire())
);
if (_onDidFocus) {
disposable.addDisposables(
_onDidFocus(() => this._onDidFocus.fire())
);
}
if (_onDidBlur) {
disposable.addDisposables(
_onDidBlur(() => this._onDidBlur.fire())
);
}
this._element.appendChild(this.panel.view.content.element);
}
this.disposable.value = disposable;
} }
public layout(_width: number, _height: number): void { public layout(_width: number, _height: number): void {
@ -106,10 +181,14 @@ export class ContentContainer
} }
public closePanel(): void { public closePanel(): void {
if (this.panel?.view?.content?.element) { if (this.panel) {
this._element.removeChild(this.panel.view.content.element); if (this.panel.api.renderer === 'onlyWhenVisible') {
this.panel = undefined; this.panel.view.content.element.parentElement?.removeChild(
this.panel.view.content.element
);
}
} }
this.panel = undefined;
} }
public dispose(): void { public dispose(): void {

View File

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

View File

@ -6,70 +6,78 @@
); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */ ); /* forces tab to be drawn on a separate layer (see https://github.com/microsoft/vscode/issues/18733) */
} }
.tab { .dv-tab {
flex-shrink: 0; 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 { &.dv-tab-dragging {
.tab-action { .dv-default-tab-action {
background-color: var(--dv-activegroup-visiblepanel-tab-color); background-color: var(--dv-activegroup-visiblepanel-tab-color);
} }
} }
&.active-tab > .default-tab { &.dv-active-tab {
.tab-action { .dv-default-tab {
visibility: visible; .dv-default-tab-action {
}
}
&.inactive-tab > .default-tab {
.tab-action {
visibility: hidden;
}
&:hover {
.tab-action {
visibility: visible; visibility: visible;
} }
} }
} }
.default-tab { &.dv-inactive-tab {
position: relative; .dv-default-tab {
height: 100%; .dv-default-tab-action {
display: flex; visibility: hidden;
min-width: 80px; }
align-items: center; &:hover {
padding: 0px 8px; .dv-default-tab-action {
white-space: nowrap; visibility: visible;
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-default-tab {
position: relative;
height: 100%;
display: flex;
align-items: center;
white-space: nowrap;
text-overflow: ellipsis;
.dv-default-tab-content {
flex-grow: 1;
margin-right: 4px;
}
.dv-default-tab-action {
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
&:hover {
border-radius: 2px;
background-color: var(--dv-icon-hover-background-color);
}
}
}
} }

View File

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

View File

@ -6,97 +6,89 @@ import {
PanelTransfer, PanelTransfer,
} from '../../../dnd/dataTransfer'; } from '../../../dnd/dataTransfer';
import { toggleClass } from '../../../dom'; import { toggleClass } from '../../../dom';
import { IDockviewComponent } from '../../dockviewComponent'; import { DockviewComponent } from '../../dockviewComponent';
import { DockviewDropTargets, ITabRenderer } from '../../types'; import { ITabRenderer } from '../../types';
import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget'; import {
DroptargetEvent,
Droptarget,
WillShowOverlayEvent,
} from '../../../dnd/droptarget';
import { DragHandler } from '../../../dnd/abstractDragHandler'; import { DragHandler } from '../../../dnd/abstractDragHandler';
import { IDockviewPanel } from '../../dockviewPanel';
import { addGhostImage } from '../../../dnd/ghost';
export interface ITab { class TabDragHandler extends DragHandler {
readonly panelId: string; private readonly panelTransfer =
readonly element: HTMLElement; LocalSelectionTransfer.getInstance<PanelTransfer>();
setContent: (element: ITabRenderer) => void;
onChanged: Event<MouseEvent>; constructor(
onDrop: Event<DroptargetEvent>; element: HTMLElement,
setActive(isActive: boolean): void; 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 _element: HTMLElement;
private readonly droptarget: Droptarget; private readonly dropTarget: Droptarget;
private content?: ITabRenderer; private content: ITabRenderer | undefined = undefined;
private readonly _onChanged = new Emitter<MouseEvent>(); private readonly _onPointDown = new Emitter<MouseEvent>();
readonly onChanged: Event<MouseEvent> = this._onChanged.event; readonly onPointerDown: Event<MouseEvent> = this._onPointDown.event;
private readonly _onDropped = new Emitter<DroptargetEvent>(); private readonly _onDropped = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event; 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 { public get element(): HTMLElement {
return this._element; return this._element;
} }
constructor( constructor(
public readonly panelId: string, public readonly panel: IDockviewPanel,
private readonly accessor: IDockviewComponent, private readonly accessor: DockviewComponent,
private readonly group: DockviewGroupPanel private readonly group: DockviewGroupPanel
) { ) {
super(); super();
this.addDisposables(this._onChanged, this._onDropped);
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'tab'; this._element.className = 'dv-tab';
this._element.tabIndex = 0; this._element.tabIndex = 0;
this._element.draggable = true; this._element.draggable = !this.accessor.options.disableDnd;
toggleClass(this.element, 'inactive-tab', true); toggleClass(this.element, 'dv-inactive-tab', true);
this.addDisposables( const dragHandler = new TabDragHandler(
new (class Handler extends DragHandler { this._element,
private readonly panelTransfer = this.accessor,
LocalSelectionTransfer.getInstance<PanelTransfer>(); this.group,
this.panel
getData(): IDisposable {
this.panelTransfer.setData(
[new PanelTransfer(accessor.id, group.id, panelId)],
PanelTransfer.prototype
);
return {
dispose: () => {
this.panelTransfer.clearData(
PanelTransfer.prototype
);
},
};
}
public dispose(): void {
//
}
})(this._element)
); );
this.addDisposables( this.dropTarget = new Droptarget(this._element, {
addDisposableListener(this._element, 'mousedown', (event) => { acceptedTargetZones: ['left', 'right'],
if (event.defaultPrevented) { overlayModel: { activationSize: { value: 50, type: 'percentage' } },
return;
}
/**
* TODO: alternative to stopPropagation
*
* I need to stop the event propagation here since otherwise it'll be intercepted by event handlers
* on the tabs-container. I cannot use event.preventDefault() since I need the on DragStart event to occur
*/
event.stopPropagation();
this._onChanged.fire(event);
})
);
this.droptarget = new Droptarget(this._element, {
acceptedTargetZones: ['center'],
canDisplayOverlay: (event, position) => { canDisplayOverlay: (event, position) => {
if (this.group.locked) { if (this.group.locked) {
return false; return false;
@ -105,35 +97,58 @@ export class Tab extends CompositeDisposable implements ITab {
const data = getPanelData(); const data = getPanelData();
if (data && this.accessor.id === data.viewId) { if (data && this.accessor.id === data.viewId) {
if ( return true;
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 this.group.model.canDisplayOverlay( return this.group.model.canDisplayOverlay(
event, event,
position, position,
DockviewDropTargets.Tab 'tab'
); );
}, },
getOverrideTarget: () => group.model.dropTargetContainer?.model,
}); });
this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
this.addDisposables( 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._onDropped.fire(event);
}) }),
this.dropTarget
); );
} }
public setActive(isActive: boolean): void { public setActive(isActive: boolean): void {
toggleClass(this.element, 'active-tab', isActive); toggleClass(this.element, 'dv-active-tab', isActive);
toggleClass(this.element, 'inactive-tab', !isActive); toggleClass(this.element, 'dv-inactive-tab', !isActive);
} }
public setContent(part: ITabRenderer): void { public setContent(part: ITabRenderer): void {
@ -144,8 +159,11 @@ export class Tab extends CompositeDisposable implements ITab {
this._element.appendChild(this.content.element); this._element.appendChild(this.content.element);
} }
public updateDragAndDropState(): void {
this._element.draggable = !this.accessor.options.disableDnd;
}
public dispose(): void { public dispose(): void {
super.dispose(); super.dispose();
this.droptarget.dispose();
} }
} }

View File

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

View File

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

View File

@ -0,0 +1,83 @@
.dv-tabs-container {
display: flex;
height: 100%;
overflow: auto;
scrollbar-width: thin; // firefox
/* GPU optimizations for smooth scrolling */
will-change: scroll-position;
transform: translate3d(0, 0, 0);
&.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);
}
}

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