11import iconClose from "@ktibow/iconset-material-symbols/close" ;
22import iconAdd from "@ktibow/iconset-material-symbols/add" ;
3- import type { Component , ComponentInstance } from "dreamland/core" ;
3+ import {
4+ createState ,
5+ type Component ,
6+ type ComponentInstance ,
7+ } from "dreamland/core" ;
48import { Icon } from "./ui/Icon" ;
59
610type TabCallbacks = {
@@ -13,86 +17,28 @@ type TabCallbacks = {
1317 "on:setactive" : ( id : Symbol ) => void ;
1418} ;
1519
16- export const Tab : Component <
17- {
18- active : boolean ;
19- icon : string ;
20- title : string ;
21-
22- funcs : TabCallbacks ;
23- } ,
24- {
25- dragroot : HTMLElement ;
26- dragoffset : number ;
27- } ,
28- {
29- id : Symbol ;
30-
31- dragpos : number ;
32-
33- width : number ;
34- pos : number ;
35- }
36- > = function ( cx ) {
37- this . dragpos = - 1 ;
38- this . dragoffset = - 1 ;
39- this . width = 0 ;
40- this . pos = 0 ;
41-
42- this . id = Symbol ( ) ;
43-
44- const calcDragPos = ( e : MouseEvent ) => {
45- const maxPos = this . funcs . getMaxDragPos ( ) - cx . root . offsetWidth ;
46-
47- const pos = e . clientX - this . dragoffset - this . funcs . getAbsoluteStart ( ) ;
48-
49- this . dragpos = Math . min ( Math . max ( this . funcs . getLayoutStart ( ) , pos ) , maxPos ) ;
50- this . funcs . relayout ( ) ;
51- } ;
52-
53- cx . mount = ( ) => {
54- const root = cx . root ;
55-
56- root . addEventListener ( "mousedown" , ( e : MouseEvent ) => {
57- this . active = true ;
58- this . funcs [ "on:setactive" ] ( this . id ) ;
59-
60- const rect = root . getBoundingClientRect ( ) ;
61- root . style . zIndex = "100" ;
62- this . dragroot . style . width = rect . width + "px" ;
63- this . dragroot . style . position = "absolute" ;
64- this . dragoffset = e . clientX - rect . left ;
65-
66- if ( this . dragoffset < 0 ) throw new Error ( "dragoffset must be positive" ) ;
67-
68- calcDragPos ( e ) ;
69- } ) ;
70- root . addEventListener ( "transitionend" , ( ) => {
71- root . style . transition = "" ;
72- root . style . zIndex = "0" ;
73- this . funcs [ "on:transitionend" ] ( ) ;
74- } ) ;
75-
76- window . addEventListener ( "mousemove" , ( e : MouseEvent ) => {
77- if ( this . dragoffset == - 1 ) return ;
78- calcDragPos ( e ) ;
79- } ) ;
80-
81- window . addEventListener ( "mouseup" , ( _e ) => {
82- if ( this . dragoffset == - 1 ) return ;
83-
84- this . dragroot . style . width = "" ;
85- this . dragroot . style . position = "unset" ;
86-
87- this . dragoffset = - 1 ;
88- this . dragpos = - 1 ;
89- this . funcs . relayout ( ) ;
90- } ) ;
91- } ;
92-
20+ export const Tab : Component < {
21+ active : boolean ;
22+ id : number ;
23+ icon : string ;
24+ title : string ;
25+ mousedown : ( e : MouseEvent ) => void ;
26+ transitionend : ( ) => void ;
27+ } > = function ( cx ) {
9328 return (
94- < div style = "z-index: 0;" >
95- < div this = { use ( this . dragroot ) . bind ( ) } style = "position: unset;" >
29+ < div
30+ style = "z-index: 0;"
31+ class = "tab"
32+ data-id = { this . id }
33+ on :mousedown = { ( e ) => this . mousedown ( e ) }
34+ on :transitionend = { ( ) => {
35+ console . log ( "tr end" ) ;
36+ cx . root . style . transition = "" ;
37+ cx . root . style . zIndex = "0" ;
38+ this . transitionend ( ) ;
39+ } }
40+ >
41+ < div class = "dragroot" style = "position: unset;" >
9642 < div class = { use ( this . active ) . map ( ( x ) => `main ${ x ? "active" : "" } ` ) } >
9743 < img src = { use ( this . icon ) } />
9844 < span > { use ( this . title ) } </ span >
@@ -192,6 +138,17 @@ Tab.css = `
192138 }
193139` ;
194140
141+ type TabData = Stateful < {
142+ id : number ;
143+ title : string ;
144+ active : boolean ;
145+
146+ dragoffset : number ;
147+ dragpos : number ;
148+
149+ width : number ;
150+ pos : number ;
151+ } > ;
195152export const Tabs : Component <
196153 { } ,
197154 {
@@ -200,10 +157,25 @@ export const Tabs: Component<
200157 rightEl : HTMLElement ;
201158 afterEl : HTMLElement ;
202159
203- tabs : ComponentInstance < typeof Tab > [ ] ;
160+ tabs : TabData [ ] ;
161+ currentlydragging : number ;
204162 } ,
205163 { }
206164> = function ( cx ) {
165+ this . currentlydragging = - 1 ;
166+ let id = 0 ;
167+ function mktab ( title ) {
168+ return createState ( {
169+ id : id ++ ,
170+ title,
171+ dragoffset : - 1 ,
172+ dragpos : - 1 ,
173+ width : 0 ,
174+ pos : 0 ,
175+ } ) ;
176+ }
177+ this . tabs = [ mktab ( "T 1" ) , mktab ( "T 2" ) ] ;
178+
207179 const TAB_PADDING = 6 ;
208180 const TAB_MAX_SIZE = 231 ;
209181 const TAB_TRANSITION = "250ms ease" ;
@@ -249,10 +221,7 @@ export const Tabs: Component<
249221 } ;
250222
251223 const reorderTabs = ( ) => {
252- this . tabs . sort ( ( aComponent , bComponent ) => {
253- let a = aComponent . $ . state ;
254- let b = bComponent . $ . state ;
255-
224+ this . tabs . sort ( ( a , b ) => {
256225 const aCenter = a . pos + a . width / 2 ;
257226
258227 const bLeft = b . pos ;
@@ -264,15 +233,19 @@ export const Tabs: Component<
264233 } ) ;
265234 } ;
266235
236+ const getTabFromIndex = ( index : number ) => {
237+ return cx . root . querySelector ( `.tab[data-id='${ index } ']` ) as HTMLElement ;
238+ } ;
239+
267240 const layoutTabs = ( transition : boolean ) => {
268241 const width = getTabWidth ( ) ;
269242
270243 reorderTabs ( ) ;
271244
272245 let dragpos = - 1 ;
273246 let currpos = getLayoutStart ( ) ;
274- for ( const component of this . tabs ) {
275- let tab = component . $ . state ;
247+ for ( const tab of this . tabs ) {
248+ let component = getTabFromIndex ( tab . id ) ;
276249 component . style . width = width + "px" ;
277250
278251 const tabPos = tab . dragpos != - 1 ? tab . dragpos : currpos ;
@@ -291,62 +264,82 @@ export const Tabs: Component<
291264 const afterpos = Math . max ( dragpos , currpos ) ;
292265 this . afterEl . style . transform = `translateX(${ afterpos } px)` ;
293266 } ;
294- const tabfuncs : TabCallbacks = {
295- relayout ( ) {
296- layoutTabs ( true ) ;
297- } ,
298- getMaxDragPos ( ) {
299- return getLayoutStart ( ) + getRootWidth ( ) ;
300- } ,
301- getAbsoluteStart ( ) {
302- return getAbsoluteStart ( ) ;
303- } ,
304- getLayoutStart ( ) {
305- return getLayoutStart ( ) ;
306- } ,
307-
308- "on:transitionend" : ( ) => {
309- transitioningTabs -- ;
310- if ( transitioningTabs == 0 ) {
311- this . tabs = this . tabs ;
312- }
313- } ,
314- "on:setactive" : ( id : Symbol ) => {
315- for ( const tab of this . tabs ) {
316- if ( tab . $ . state . id != id ) tab . $ . state . active = false ;
317- }
318- // TODO on:active
319- } ,
320- } ;
321-
322- this . tabs = [
323- (
324- < Tab
325- active = { true }
326- icon = "/vite.svg"
327- title = "ViteViteViteViteViteVite Vite Vite Vite"
328- funcs = { tabfuncs }
329- />
330- ) as ComponentInstance < typeof Tab > ,
331- (
332- < Tab
333- active = { false }
334- icon = "/vite.svg"
335- title = "ViteViteViteViteViteVite Vite Vite Vite"
336- funcs = { tabfuncs }
337- />
338- ) as ComponentInstance < typeof Tab > ,
339- ] ;
340267
341268 cx . mount = ( ) => {
342269 requestAnimationFrame ( ( ) => layoutTabs ( false ) ) ;
343270 window . addEventListener ( "resize" , ( ) => layoutTabs ( false ) ) ;
344271 } ;
345272
273+ const getMaxDragPos = ( ) => {
274+ return getLayoutStart ( ) + getRootWidth ( ) ;
275+ } ;
276+
277+ const calcDragPos = ( e : MouseEvent , tab : TabData ) => {
278+ const root = getTabFromIndex ( tab . id ) ;
279+ const maxPos = getMaxDragPos ( ) - root . offsetWidth ;
280+
281+ const pos = e . clientX - tab . dragoffset - getAbsoluteStart ( ) ;
282+
283+ tab . dragpos = Math . min ( Math . max ( getLayoutStart ( ) , pos ) , maxPos ) ;
284+ layoutTabs ( true ) ;
285+ } ;
286+
287+ window . addEventListener ( "mousemove" , ( e : MouseEvent ) => {
288+ if ( this . currentlydragging == - 1 ) return ;
289+ calcDragPos ( e , this . tabs . find ( ( tab ) => tab . id === this . currentlydragging ) ! ) ;
290+ } ) ;
291+
292+ window . addEventListener ( "mouseup" , ( e ) => {
293+ if ( this . currentlydragging == - 1 ) return ;
294+ const tab = this . tabs . find ( ( tab ) => tab . id === this . currentlydragging ) ;
295+ const root = getTabFromIndex ( tab . id ) ;
296+ const dragroot = root . querySelector ( ".dragroot" ) as HTMLElement ;
297+
298+ dragroot . style . width = "" ;
299+ dragroot . style . position = "unset" ;
300+ tab . dragoffset = - 1 ;
301+ tab . dragpos = - 1 ;
302+ layoutTabs ( true ) ;
303+ this . currentlydragging = - 1 ;
304+ } ) ;
305+
306+ const mosueDown = ( e : MouseEvent , tab : TabData ) => {
307+ tab . active = true ;
308+ this . currentlydragging = tab . id ;
309+
310+ const root = getTabFromIndex ( tab . id ) ;
311+ const rect = root . getBoundingClientRect ( ) ;
312+ root . style . zIndex = "100" ;
313+ const dragroot = root . querySelector ( ".dragroot" ) as HTMLElement ;
314+ dragroot . style . width = rect . width + "px" ;
315+ dragroot . style . position = "absolute" ;
316+ tab . dragoffset = e . clientX - rect . left ;
317+
318+ if ( tab . dragoffset < 0 ) throw new Error ( "dragoffset must be positive" ) ;
319+
320+ calcDragPos ( e , tab ) ;
321+ } ;
322+
323+ const transitionend = ( ) => {
324+ transitioningTabs -- ;
325+ if ( transitioningTabs == 0 ) {
326+ this . tabs = this . tabs ;
327+ }
328+ } ;
329+
346330 return (
347331 < div this = { use ( this . container ) . bind ( ) } >
348332 < div class = "extra left" this = { use ( this . leftEl ) . bind ( ) } > </ div >
349- { use ( this . tabs ) }
333+ { use ( this . tabs ) . mapEach ( ( tab ) => (
334+ < Tab
335+ id = { tab . id }
336+ title = { tab . title }
337+ icon = "/vite.svg"
338+ active = { use ( tab . id ) . map ( ( id ) => id === this . currentlydragging ) }
339+ mousedown = { ( e ) => mosueDown ( e , tab ) }
340+ transitionend = { transitionend }
341+ />
342+ ) ) }
350343 < div class = "extra after" this = { use ( this . afterEl ) . bind ( ) } > </ div >
351344 < div class = "extra right" this = { use ( this . rightEl ) . bind ( ) } > </ div >
352345 </ div >
0 commit comments