Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set min height and width in px #33

Open
Nico-Mayer opened this issue Aug 1, 2022 · 23 comments · May be fixed by #58
Open

Set min height and width in px #33

Nico-Mayer opened this issue Aug 1, 2022 · 23 comments · May be fixed by #58
Labels
enhancement New feature or request

Comments

@Nico-Mayer
Copy link

Hey, I think it would be cool to set the min height and width in px, could be useful in some cases where u want to use a custom element as the splitter :)

@Tal500
Copy link
Collaborator

Tal500 commented Aug 1, 2022

Hi @Nico-Mayer!

It will be very cool, but you need to implement correctly and carefully, and there are things that needed to be done before in my opinion.
However, I'd be glad to see any PR that do this.

The coding should not be too hard, but need to make sure to handle all the cases in the code very carefully.

@Tal500
Copy link
Collaborator

Tal500 commented Aug 1, 2022

BTW now I think that there should be two ways to specify the min/max/snap - by pixels or/and by percentages, simultaneously!

I suggest to have them accept some types:

export enum MultiSizePolicy {
  MAX_OF,
  MIN_OF
}

export interface MultiSize {
  policy?: MultiSizePolicy,
  px: number | undefined,
  precentages: number | undefined
}

export type SizeSpecifier = number | string | MultiSize;

The values of min, max, snap should be of type SizeSpecifier. This is how they should be treated:

  1. If it is a number, treat it as percentages.
  2. If it is a string:
    i. If it is in the format ${number}%, treat it as percentages.
    ii. If it is in the format ${number}px, treat it as pixels.
    iii. Otherwise, print console error and ignore the size specifier.
  3. If it is a MultiSize object:
    i. If px and precentages are both undefined, ignore the size specifier.
    ii. If one of px and precentages is undefined and the other one is number, then treat this according to what that is a number.
    iii. If both of px and precentages are numbers, then the policy must be defined (otherwise print console error and ignore), and:
    a. If it is MAX_OF, the size specifier will be the maximum of px and precentages with respect to the container.
    b. If it is MAX_OF, the size specifier will be the minimum of px and precentages with respect to the container.

The functions IPane.min(), IPane.max() and IPane.snap() should calculate the actual percentages of the size specifier, with respect to the container (in the case of pixels typed size), according to the rules here.

Unresolved Case: SSR on case 3.iii above

How can you tell if the maximum and the minimum of pixels and the percentages? This used in the pane styling on SSR by setting the HTML attribute style="min/max-width/height: ...".
There are the CSS functions min and max which we can use, but I saw in W3C their poor support on legacy browsers:
תמונה

I guess we can decide that if the user choose to use this advanced feature, then on SSR the min and max size will be supported only on modern browsers. If the user really wants to support legacy browsers on SSR, then the user can say choose to use other cases of this logic by using the JS code expression (typeof window !== 'undefined').

What do you think @orefalo ?

@Tal500
Copy link
Collaborator

Tal500 commented Aug 1, 2022

More about it:

To support the pixels, there should be a function reportResize() or something that the user should invoke whenever the size of the container gets changed (and should be invoked automatically whenever it's possible to know something gets changed, like the window size).

There is not a proper 'size recalculation' in this library, the one we have basically forgets about the size the end-user changed by moving the panes.

This is why this feature is possible, but hard to achieve properly.

@orefalo
Copy link
Owner

orefalo commented Aug 1, 2022

Hi @Nico-Mayer, could you provide more details about the need to support pixel sizing rather than % sizing?

In my apps, I usually need pixel sizing for static panes of the apps - this can be easily achieved with CSS. However, so far, I couldn't find a valid use case to support pixel sizing.

@Tal500
Copy link
Collaborator

Tal500 commented Aug 1, 2022

Hi @Nico-Mayer, could you provide more details about the need to support pixel sizing rather than % sizing?

In my apps, I usually need pixel sizing for static panes of the apps - this can be easily achieved with CSS. However, so far, I couldn't find a valid use case to support pixel sizing.

The need is for responsivness.
Think for example you have a vertical navigation menu on the side, and you want the user to be able to shrink or grow it horizontally.
You'd like to have minimal and max sizes in pixels for the navigation pane, since you want the content to be visible nicely for both narrow and wide screens.

You can replace the navigation example with some "toolbox" like you have in GIMP on the side.

@orefalo
Copy link
Owner

orefalo commented Aug 2, 2022

Here is my thinking.

FOR: I recall trying to implement a similar functionality not so long ago with @Tal500, we gave up.
When you think about HTML sizes, they can be expressed in a multitude of units, px and % being just a subset of them. So it would make complete sense to mimic the functionality.

AGAINST:
Frameworks such as Tailwind have predefined classes for panel toggles. Using splitpane for these use cases is overkill and complexities the code base. Keep it simple and stupid.

Summary:

I see the functionality as a nice to have, not a must-have. Yet, I have no objection to accept a PR as long as it supports all existing use cases.

@orefalo
Copy link
Owner

orefalo commented Dec 30, 2022

So I've been thinking about this feature and how to best implement it.
I played with HTML to see how it handles similar constraints.
I believe the type should be

export type SizeSpecifier = number | string | MultiSize | "auto";

Note the "auto" addition. Which would translate to:

If it is a string:
i. If it is in the format ${number}%, treat it as percentages.
ii. If it is in the format ${number}px, treat it as pixels.
iii. if it is "auto", take the remaining space
vi. Otherwise, print console error and ignore the size specifier.

@orefalo
Copy link
Owner

orefalo commented Dec 30, 2022

Also, reading the great specs above, I don't see a valid (real life) use case for the MultiSizePolicy policy.
I understand what it does... but can think of a valid use case... maybe snapping?

@Tal500
Copy link
Collaborator

Tal500 commented Dec 30, 2022

After some thinking, this feature is possible to implement, just need to be very careful and implement it slowly, I believe after the a11y feature (we might release both of these features in a fancy "v0.8" version)
Strings are friendly to begin with, but I believe that for more experienced users, handling the dimensions as objects is better.

I believe that many flavors should be supported. For handling them as objects, I believe we shell introduce a special structure:

interface DimensionVector {
  prec?: number;
  px?: number;
}

This means we allow the dimension to be either on prec, px, or both.
How both? By treating this object as a "vector" - we sum the prec with px, and the value for the CSS will be outputted as something like, assuming both of them are defined and not 0: width: calc({prec}% + {px}px).

Of course, for handling this logic, the user advice to use library-exported helper functions, which we can picture as:

export const dimensions: ({prec?: number, px?: number}) => DimensionVector;
export const prec: (amount: number) => DimensionVector;
export const px: (amount: number) => DimensionVector;
export const sum: (...dims: DimensionVector[]) => DimensionVector;

So the user can initialize the sizes something like(a wild example):

<script>
  import { dimensions, prec, px, sum } from 'svelte-splitpanes';
</script>
<Splitpanes>
  <Pane size={dimensions({prec: 10, px: 3})}></Pane>
  <Pane size={prec(20)}></Pane>
  <Pane size={px(15)}></Pane>
  <Pane size={sum(prec(11), px(4))}></Pane>
</Splitpanes>

If it's not looking good for beginners in your opinion, we can support a simple string expressions of something like '1px + 5%' from the user, in addition.

@Tal500
Copy link
Collaborator

Tal500 commented Dec 30, 2022

I should also mention that the user should call a function like report_container_resize(), every time the container (might) be changed, so Splitpanes will convert PX to the updated prec amount.

In modern browsers, there are some 'Observers' object we can use to track container size changes, but on older one, we can reply only on window resize event. The window resize event doesn't capture in-page size changing from dynamic content changes inside the page itself, so we must rely on the end-user to call this report_container_resize() function every time the size could have been changed.

Observation I made just now: Imagine their is a sidebar in the page and the user is in modern browser, that have a smooth CSS transition when it's getting opened. When the client opens the sidebar, the splitpane will automatically stretch/shrink PX unit to %, but the updated will be for every JS frame, not every CSS frame. This might will be looked like a "lag" on size handling in Splitpane for the client, during the sidebar transition CSS animation.

A possible solution to the observation: Have the user must call report_container_resize() for every possible change, and not rely on some fancy modern 'Observers'. This way the user can call report_container_resize() only after the end of the transition.

@orefalo
Copy link
Owner

orefalo commented Dec 30, 2022

all good with report_container_resize(), trying to come up with the pseudo code

@hardyjosh
Copy link

hardyjosh commented Jan 3, 2023

Adding my support for this feature. For me the use case is, I have a small header for each panel. It would be great if the minimum size for the panels were the same as the header size, so maximising one panel would mean only the headers of the others show.

It would also be great if the headers were also the splitter hitbox, but that's a separate request.

image

Edit: by the way, thank you for putting together this package, such a great addition to the Svelte ecosystem.

@orefalo
Copy link
Owner

orefalo commented Jan 10, 2023

@Tal500 I do not understand the need for vectors such as '1px + 5%' or sum(prec(11), px(4)). What is the use case? this makes the logic more complex, see edge cases below.

I am thinking HTML/CSS, and it doesn't have this capability. Let's try simple.. then we can make it feature perfect.

<Splitpanes>
  <Pane size="10px">header</Pane>
  <Pane size="20%"></Pane>
  <Pane size="auto">context</Pane>
  <Pane size="15px">footer</Pane>
</Splitpanes>

Besides, sticking to html/css conventions makes the learning curve easy.

Edge cases I can think of:

  • sum of sizes goes beyond SplitPanes max px size
<Splitpanes>
  <Pane size="10px">header</Pane>
  <Pane size="100px"></Pane>
  <Pane size="10000px">context</Pane>
  <Pane size="15px">footer</Pane>
</Splitpanes>
  • Sum of sizes goes beyond SplitPanes max px size
<Splitpanes>
  <Pane size="25%">header</Pane>
  <Pane size="100%">EXCEPTION</Pane>
  <Pane size="25%">context</Pane>
  <Pane size="25%">footer</Pane>
</Splitpanes>
  • % constraints can't be honored (assume spitpane is 100px)
<Splitpanes>
  <Pane size="47%">EXCEPTION</Pane>
  <Pane size="50%"></Pane>
  <Pane size="auto">context</Pane>
  <Pane size="10px">footer</Pane>
</Splitpanes>

Actually, the more I think about exceptions, the more I realize... aren't we reimplementing HTML?
The desired pixel constraint behavior can easily be achieved using HTML composition.

@orefalo
Copy link
Owner

orefalo commented Jan 10, 2023

I checked how Hoppscotch does it https://hoppscotch.io/. Hoppscotch is a REST client based on vue-splipanes.
the header and footer are fixed size

html - https://github.com/hoppscotch/hoppscotch/blob/51e40581b01b7f1a5b8fe3a91fb8e0a3e8382459/packages/hoppscotch-common/src/layouts/default.vue

 <div class="flex w-screen h-screen">
    <Splitpanes class="no-splitter" :dbl-click-splitter="false" horizontal>
      <Pane v-if="!zenMode" style="height: auto">
        <AppHeader />
      </Pane>
      <Pane :class="spacerClass" class="flex flex-1 !overflow-auto md:mb-0">
        <Splitpanes
          class="no-splitter"
          :dbl-click-splitter="false"
          :horizontal="!mdAndLarger"
        >
          <Pane
            style="width: auto; height: auto"
            class="!overflow-auto hidden md:flex md:flex-col"
          >
            <AppSidenav />
          </Pane>
          <Pane class="flex flex-1 !overflow-auto">
            <Splitpanes
              class="no-splitter"
              :dbl-click-splitter="false"
              horizontal
            >
              <Pane class="flex flex-1 !overflow-auto">
                <main class="flex flex-1 w-full" role="main">
                  <RouterView v-slot="{ Component }" class="flex flex-1">
                    <Transition name="fade" mode="out-in" appear>
                      <component :is="Component" />
                    </Transition>
                  </RouterView>
                </main>
              </Pane>
            </Splitpanes>
          </Pane>
        </Splitpanes>
      </Pane>
      <Pane v-if="mdAndLarger" style="height: auto">
        <AppFooter />
      </Pane>
      <Pane
        v-else
        style="height: auto"
        class="!overflow-auto flex flex-col fixed inset-x-0 bottom-0 z-10"
      >
        <AppSidenav />
      </Pane>
    </Splitpanes>
</div>

https://github.com/hoppscotch/hoppscotch/blob/acafc072db28db247321d9c624734d3620d2c145/packages/hoppscotch-common/assets/scss/styles.scss

.no-splitter .splitpanes__splitter {
  @apply relative;
  @apply bg-primaryLight;
}
.no-splitter.splitpanes--vertical > .splitpanes__splitter {
  @apply w-0.5;
  @apply pointer-events-none;
}

.no-splitter.splitpanes--horizontal > .splitpanes__splitter {
  @apply h-0.5;
  @apply pointer-events-none;
}

I must point that I use the same technique - https://orefalo.github.io/svelte-splitpanes/examples/styling/splitters-modern
and yes.. the constraints are not pixel based, but rather content based.. but again, why do you need pixels in the first place?

@Tal500
Copy link
Collaborator

Tal500 commented Jan 10, 2023

I am thinking HTML/CSS, and it doesn't have this capability. Let's try simple.. then we can make it feature perfect.

I can't see everything you sent for now, but in the meantime for understanding why I'm thinking this way, and why it's possible in HTML, see the CSS calc() function.

It is supported even by IE11, and way beyond in even earlier versions, by my last check on caniuseit.

@orefalo
Copy link
Owner

orefalo commented Jan 10, 2023

But you are not getting my functional concern...
When do you need to specify a constraint such as '10px + 5%' ?

say, you have an toolbox with icon 10px by 10px.. the constraint would be '10px', fine i can see that (even if the above solution is much better)

say, you want a splitter a 5% of the screen, constraint would be '5%', fine too.

please find me a use case for '10px + 5%'... it makes no sense to me.

@Tal500
Copy link
Collaborator

Tal500 commented Jan 10, 2023

But you are not getting my functional concern... When do you need to specify a constraint such as '10px + 5%' ?

say, you have an toolbox with icon 10px by 10px.. the constraint would be '10px', fine i can see that (even if the above solution is much better)

say, you want a splitter a 5% of the screen, constraint would be '5%', fine too.

please find me a use case for '10px + 5%'... it makes no sense to me.

I didn't suggest it to be a use case, I just want a uniform self transparent API, since I'm very sure that internally this is how this library should store the size. With this internal implementation detail that is taken as an axiom, I can see two reasons now:

  1. The user will understand how the library is working(kinda) and the documentation might be easier to grasp.
  2. Why designers that choose to use pixels, wouldn't take advantage of %? I think that this can be a novel design idea, thinking on the px on the base size, and thinking in the % as the additional fluid style. I think it has the same magic of CSS flex boxes, that have the potential to be automatically fit to any device. I do agree that this option is a more advanced one, but my intuition says to not try to fight nature and restrict this option from the (advanced?) user.

@Tal500
Copy link
Collaborator

Tal500 commented Jan 11, 2023

@orefalo, in addition, for a user who would like a two way binding for the pane size(e.g. programmatic resizing), how do you suggest he should bind to the size value? By strings conversion it seems to be a waste. How can you keep the API clean in this use case?

@Tal500
Copy link
Collaborator

Tal500 commented Jan 11, 2023

@orefalo, in addition, for a user who would like a two way binding for the pane size(e.g. programmatic resizing), how do you suggest he should bind to the size value? By strings conversion it seems to be a waste. How can you keep the API clean in this use case?

@orefalo, the last comment of mine about binding made me realize, maybe instead of some weird object as size, we need to have the additional postfixes of Prec and Px as variables?

This way, we can make the size variable(and also the additional min/max/snap size vars) split up to:

  • size - a string of the type of ${number}px|${number}%, for the user convenience.
  • sizePx - the PX coordinate of the size, as number.
  • sizePrec - the same as the previous just for %.

This way, the user can either use the string version, or specify numbers in sizePx, in sizePrec, or sizePx+sizePrec.

The separation makes things more intuitive, and help the possible two-way binding to be simpler.

@orefalo
Copy link
Owner

orefalo commented Jan 11, 2023

Your last comment is getting closer to what could be a proper implementation. yet I believe you worry too much about optimizations, at the cost of spec simplicity.

So let's step back for a second. What are the use cases for a splitter?

  1. "As a developer, I want to define my draggable splitter by pane % size, including constraints"
  2. "As a developer, I want to define my draggable splitter by pane pixel size, including constraints"
  3. "As a developer, I want to define my static splitter pane by pixel dimension"
  4. "As a developer, I want to define my static splitter pane by % "
    ... let's stop here... Note that there is no use case for pixel AND % size, it makes no sense to me (unless someone can prove me wrong)

3 & 4 -> can be done in pure css. The rational here is that splitpane with not replace the richness of a div+css. it might be more convenient, but it's a trap.
1 & 2 -> can be solved by the size* structure you proposed above. I would personally stick to sizing standards developers already know: html! IMHO, there should be just one size attribute, how this size attribute is mapped to internal structures is an implementation concern. I understand purist may see a few cpu cycles wasted for the string to number conversion, but it comes as a balance to a clean/simple/known/understood tag attribute specification.

We already define the structure earlier in this thread - note the "auto", which we will likely need based on my assessment.

export type SizeSpecifier = number | string | "auto";

if it's a number -> percentage
If it is a string:
   If it is in the format ${number}%, treat it as percentages.
   If it is in the format ${number}px, treat it as pixels.
   if it is "auto", take the remaining space

Are we getting any closer to each other? ;-)

PS: I am very concerned about using calc and would rather stay away from it for compatibility reasons (unless there is a trick/optimization I don't see).

@Tal500
Copy link
Collaborator

Tal500 commented Jan 11, 2023

Your last comment is getting closer to what could be a proper implementation. yet I believe you worry too much about optimizations, at the cost of spec simplicity.

The optimization is only the side effect, the real design issue is the limitation to strings, but there are more I'll try to say soon.

So let's step back for a second. What are the use cases for a splitter?

1. "As a developer, I want to define my draggable splitter by pane % size, including constraints"

2. "As a developer, I want to define my draggable splitter by pane pixel size, including constraints"

3. "As a developer, I want to define my **static** splitter pane by pixel dimension"

4. "As a developer, I want to define my **static** splitter pane by % "
   ... let's stop here... Note that there is no use case for pixel AND % size, it makes no sense to me (unless someone can prove me wrong)

3 & 4 -> can be done in pure css. The rational here is that splitpane with not replace the richness of a div+css. it might be more convenient, but it's a trap. 1 & 2 -> can be solved by the size* structure you proposed above. I would personally stick to sizing standards developers already know: html! IMHO, there should be just one size attribute, how this size attribute is mapped to internal structures is an implementation concern. I understand purist may see a few cpu cycles wasted for the string to number conversion, but it comes as a balance to a clean/simple/known/understood tag attribute specification.

First, I do agree that 3&4 should be out of the scope of this library.

About the design: I am not a great designer, but I am convinced that the optional px+% option for the advance user, will result in a great satisfaction for different screen sizes. Imagine a regular dynamic 2 panes with a splitter between them. The left pane is a side bar (list of files, or other toolbar) and the right one is the "main" one. The designer might want that the toolbar will have a base size of 20px(meaning, that the initial size will be not less then 20px on any device), and the fluid size(i.e. fraction spare size) will be 2%, so the rendered size of the left pane will be calc(20px + 2%). The choice for this design is clear for me - the designer wants the left pane(i.e. the "toolbar") to take a relatively small prec of the space, but not too much. He specifies 20px as the base size, because if it will be smaller, the content on the left pane will be unreadable. He specifies the extra 2% for the convenient of the end-user, that he may enjoy his possible huge screen, allowing the left pane to be wider than the 20px size. Of course, the end-user may always manually override the panes to be the size of he wishes, but it is reasonable to give him the automatically initial recommended sizing for his screen size. if the library wouldn't support this use case, the designer might give up on the dynamic sizing, and stick to static sizing via CSS calc(20px + 2%).

Additionally, I am planning to use the CSS calc() even if the sizes are only in %, because I'm worried about the (extra) issues that will have with the sizes of the splitters themselves - because we might need to substract the pixel sizes of the splitters(which are in PX) from the actual CSS pane sizes themselves, using the calc(). Today, this happens automatically by the browsers, since the total sizes of the container children are "overflow", but when introducing PX it might makes the automated handaling of this overflow even more unexpected/unprefereble by the browsers.

We already define the structure earlier in this thread - note the "auto", which we will likely need based on my assessment.

export type SizeSpecifier = number | string | "auto";

if it's a number -> percentage
If it is a string:
   If it is in the format ${number}%, treat it as percentages.
   If it is in the format ${number}px, treat it as pixels.
   if it is "auto", take the remaining space

Are we getting any closer to each other? ;-)

Yes, but you forgot one thing that I mentioned in the previous last comment of mine - two way binding (e.g. the "prog resize" example)!
In the two way binding, the user uses bind:size={sizeVar}, and it's automatically being supported, see the implementation of the "prog resize" example.
How do you expect the user to bind to a "textual" size value(i.e. string value)? If it's numberical, this was making a total sense to bind, but on the textual, it's harder to work with programitically.

PS: I am very concerned about using calc and would rather stay away from it for compatibility reasons (unless there is a trick/optimization I don't see).

See caniuseit of the CSS calc() . Please notice that although it's yellow on IE9-11, it's totally working on these legacy ones in the simple format of the kind calc(9% + 15px).

@Tal500 Tal500 linked a pull request Jan 23, 2023 that will close this issue
@wtachau
Copy link
Contributor

wtachau commented Jun 14, 2023

FYI for anyone else who comes across this— I was able to make it work in my use case with just a component like

<script lang="ts">
  import { Pane } from 'svelte-splitpanes';

  let screenHeight: number | undefined;
  let drawerHeaderHeight: number | undefined;

  $: minSize =
    drawerHeaderHeight != undefined && screenHeight != undefined
      ? (drawerHeaderHeight / screenHeight) * 100
      : 1;
</script>

<svelte:window bind:innerHeight={screenHeight} />
<Pane size={20} snapSize={10} maxSize={90} {minSize}>
  <div bind:clientHeight={drawerHeaderHeight} class="flex">
    <div>tab1</div>
    <div>tab2</div>
    <div>tab3</div>
  </div>
  <div>pane contents</div>
</Pane>

@ptrxyz
Copy link

ptrxyz commented Feb 22, 2024

Just as a quick workaround:

<Pane class="min-w-300"></Pane>

<style>
.min-w-300 { min-width: 300px }
</style>

This helped me for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants