Active-CSS / active-css

The epic event-driven browser language for UI with functionality in one-liner CSS. Over 100 incredible CSS commands for DOM manipulation, ajax, reactive variables, single-page application routing, and lots more. Could CSS be the JavaScript framework of the future?
https://activecss.org
Other
42 stars 7 forks source link

Get if-visible and if-completely using left and right edges as well as top and bottom #199

Closed bob2517 closed 9 months ago

bob2517 commented 2 years ago

Currently it's only using top and bottom to detect partial and complete visibility. "Complete" is therefore a bit of a misnomer.

This is fine for some scenarios, but may not be fine for others.

We should have an additional option in both these conditionals to specify whether to go height and width dimension check, height-only dimension check or width-only dimension-check.

The default should be both height and width dimension check - which is the breaking change bit. The additional option will allow fixing existing config to use prior core version behaviour.

bob2517 commented 2 years ago

Changing the spec for this.

The 2.9.0 branch now has an additional scope parameter, which can reference the containing element. This makes it possible for elements to get accurately checked within hidden/auto overflowing containers.

When this issue gets done, which won't be in the 2.9.0 branch as I don't have a personal need for it and no one has asked for it yet, the parameter options will be:

vertical horizontal both

Defaulting to vertical, which is what it does now, and is arguably the most useful.

If anyone needs it sooner rather than later, let me know in this issue.

dragontheory commented 2 years ago

I can think of a game changer use case that may be applicable...

Never seen this before but maybe it can be done with this functionality... it would be huge in terms of layout automation.

INTELLIGENT responsive design (no need for media queries).

No need for arbitrary guestimated preset widths for WHEN DOM elements rearrange themselves for responsiveness. They will actually KNOW they are touching/overlapping and rearrange themselves automagically.

When a browser window or inner DOM elements are resized, DOM elements check horizontal or vertical edges or both if they are being touched/overlapped or hidden on the edges. If so, rearrange until they are not!

How they rearrange is the design part. But the trigger is the check.

WDYT?

bob2517 commented 2 years ago

I can think of a game changer use case that may be applicable...

Never seen this before but maybe it can be done with this functionality... it would be huge in terms of layout automation.

INTELLIGENT responsive design (no need for media queries).

No need for arbitrary guestimated preset widths for WHEN DOM elements rearrange themselves for responsiveness. They will actually KNOW they are touching/overlapping and rearrange themselves automagically.

When a browser window or inner DOM elements are resized, DOM elements check horizontal or vertical edges or both if they are being touched/overlapped or hidden on the edges. If so, rearrange until they are not!

How they rearrange is the design part. But the trigger is the check.

WDYT?

I think it's something that would be amazing! It would be a massive achievement for CSS to pull it off. It's definitely something for the CSS rendering engine guys.

I personally wouldn't want to go near it because I just don't have the CSS knowledge or the rendering engine expertise to get the complete picture, in order to work out how to code it. It would need to be setup in the rendering engine and not JavaScript, otherwise you'd be battling with existing CSS rules. I mean, it could be done, but it's just not worth the headache (and it would be a headache) working out how to do it - it's a core styling issue which is better off in the CSS rendering engine.

I'm sure it can be specified out by someone though - it would be a really cool future upgrade to CSS.

bob2517 commented 2 years ago

Could you do it with completely-visible and visible? You could probably use them to solve issues related to the problem, for sure.

So yeah - it might help!

bob2517 commented 2 years ago

I think they can go a long way to solving it, though if you had two elements next to each other that need to resize - you could get an infinite loop going on at a smaller device size when they both try and resize. Unless you had an extra check in there or something.

So there's potential complexity with it, but if only used in key places it could be enough maybe.

But then, you could just use media queries. I guess it depends on what is on the page...

Those are my first thoughts...

bob2517 commented 2 years ago

I actually think that you've thought about this more than me, and are further along in seeing the potential for this.

Maybe just play around with it and see what you can do?

There's a lot of potential in ACSS that I haven't come anywhere close to exploring. This looks like it's probably one of those areas. The only use-cases I've had for completely-visible is the example on the docs site and your ul list, so I'm sure there's more that can be done with it.

bob2517 commented 2 years ago

You are the CSS expert, rather than me, methinks - so my opinion is probably lacking vision a little bit.

bob2517 commented 2 years ago

Apologies if I can seem a bit harsh or blunt at times. It can take a little while sometimes to fully get what is trying to be communicated :)

bob2517 commented 2 years ago

So basically, what you're saying is this:

1) Put complete-visible (or not-completely-visible) observe events on certain elements. 2) Then when these trigger, the element resizes to try and get visible again.

I guess if you tied their width into a CSS variable (you can use CSS variables in ACSS), then you could readjust that way rather than changing the width/height manually.

Probably the missing aspect to this would be how to get how many pixels were changed in the resize, otherwise you'd need to resize a pixel at a time, trigger a new observe event, until it was no longer resized. An "offset" pixel figure would mean that you could just do a resize once, which would be more performant.

You might then need an similar observe event on the element that was resized, unless it had a percentage width or something like that.

But yeah, something like that is probably what you want.

bob2517 commented 2 years ago

But thinking about it a bit more, the observe event only runs once and then stops, so you would definitily need the offset figure to adjust the width - otherwise you wouldn't know by how much to adjust the width. It's not going to run more than once. Once the condition has matched that something isn't completely-visible, it won't run again.

So I think it's almost there - but without that offset figure to determine what the width change was, it's not going to work with observe as it stands right now. It could be modified to work, but I don't think it will work at the moment.

bob2517 commented 2 years ago

I'm not sure you'd want to manually adjust either.

I think you'd need something like an autoAdjustWidth event, which is tied to an intersectionobserver on a specific element, which adjusts width according to visibility. I dunno - needs more thought and a proper use case setting up.

bob2517 commented 2 years ago

Or like an observe that kept observing until a condition was met. The use of it would insist on a change being made in the event itself though, to make an attempt to match the condition, otherwise it would just run until it crashed the browser.

bob2517 commented 2 years ago

If an example was written in JavaScript showing the logic needed for one event, then that would be something to work from...

bob2517 commented 2 years ago

It would maybe need an element and the monitoring style property as parameters, something like that. Maybe also the element that it needs to adjust against. We're starting to get into CSS rendering engine territory here though, which is what I said at the beginning. It's a complex path to go down, unless a simple method is found of course.

bob2517 commented 2 years ago

Manual adjustments would be easier to set up, but then you've got media queries, and ACSS for that, using conditionals. I'm not sure how much easier things would get doing it manually with ACSS,

bob2517 commented 2 years ago

I think that's probably enough thought on that from me for now. I'm getting close to getting the core ready for the new release, so I'm putting my attention back on that now...

bob2517 commented 2 years ago

But to finish off (I'm like a dog to a bone on these things sometimes) - an observe event that monitored visibility changes and returned an offset pixel value to show before and after boundary position changes, could then be used to adjust other elements on a one-off basis.

That would be the manual handling that completely-visible and visible could potentially help with. But you would need the boundary width/height change that occurred in order to adjust other elements when the observe event kicked in. And that's the part that isn't there at the moment. It wouldn't be difficult to add in, but I'd need a use case set up to test with.

It would probably need a parameter adding somewhere to tell the observe event to monitor the boundary changes.

bob2517 commented 2 years ago

If you didn't get the boundary change, you could alternatively specify the element to compare against, in order to adjust both items. That's the other option.

Doing the adjustment automatically beyond that, would definitely be a CSS rendering engine solution, as that is better designed to monitor surrounding elements.

bob2517 commented 2 years ago

Additionally for that functionality, there is also the max-width, max-height, min-width and min-height conditionals in ACSS, which I use to adjust the code editor on the docs site. That doesn't use media queries - it depends on the width of the code editor itself to work out the styling. That was pretty cool at the time - I'd forgotten about that.

Like you could use it to detect the width of the ajax list. And if it falls above or below a certain width, you can add a class to the body and change the styling from that.

That might already do what you need. That can just be triggered from the resize event. Just use max-width with the selector and if it reaches that width, then it will trigger the event on the resize.

That's the other solution that might already help. I wrote that because a problem with media queries is that they only monitor the width of the device rather than specific elements. And I need to change the code editor CSS and behaviour according to how wide the code editor is when placed in the page, as it sometimes isn't full width.

dragontheory commented 2 years ago

Thank you for the input and thoughts. Agreed. CSS is 100 to 1K times faster than equivilent JS solutions and can take advantage of GPUs. If it can be done with CSS only, then that's certainly preferred.

Also agreed that this is very similar to media queries, since we are basically just trying to automate the trigger of media queries. Instead of static preset breakpoints (triggers), elements police and react to themselves if they get to close to one another and trigger. Everything else is the same.

Brainstorming: Since there are so many different types of layouts such as fixed width, percentage width, full-bleed, fluid, and every combination in between, thus not trigger on element widths, would the trigger be on whether content wraps or ellipsises? Maybe there could be three or four general levels of "sensitivity" to determine how much content wraps or ellipsises before triggering. Most sensitive being trigger the moment content wraps. Least sensitive being trigger after content wraps and begins to be hidden?

Not sure if that's viable... hmmm...

bob2517 commented 2 years ago

The sensitivity levels are a good idea.

Let's take it back a little bit and work out just one scenario.

For just one overlap, what would be the flow that would happen on the page? Have you got an idea of the syntax that you would use? I find that writing out the pseudo-code or the syntax that you would like to see can make it clearer what would need to happen. Getting that right is half the process I think.

dragontheory commented 2 years ago

Sorry for the dely. Been sick for the past few days.

Feeling well enough now.

I have to think ahead a little because of other features that are planned.

For example:

  1. Google and Microsoft web email apps have user options to set "display density levels" to "default", "comfortable", "compact". All it does is change padding and margins.
  2. Some Grid column widths will be resizable via a splitter. This one uses jQuery so I need to convert it to vanilla JS. Notice that double clicking one of the arrows on the splitter minimizes the div in the direction of the arrow to a set min-width. Sort of like how CodePen's splitters minimize when the splitters are double clicked. Pretty neat. https://codepen.io/dragontheory/pen/vYeymYK

So some column widths will be a fixed and others will be fluid and users will be able to change the padding and margins.

That's why I looked past considering widths and or padding/margins to trigger on the element level and on to the wrapping/ellipsising content level. Because ultimately it's about if content is accessible or not.

An example could be to watch content like <p> tags and "load bearing scaffolding" element edges. If the <p> wraps AND the element edges are touching, add a class to <body>?

Theoretical ACSS:

"loading bearing" scaffolding element edges:observe {
  if-touching {
    body {
      add-class: .sesitivy-level-01;
    }
  }
}

~ and ~

p:observe {
  if-wrap {
    body {
      add-class: .another combo class;
    }
  }
}

How do you determine "load bearing"? Check to see what CSS attributes/properties it has. If the element has or is a child of an element that has display: grid, for example.

I don't know, this may all be too much like you said.

bob2517 commented 2 years ago

Glad to see you well :)

Could you check if a p tag or a load-bearing scaffolding element is completely-visible along with an max-width conditional or something like that?

I'm just thinking in terms of can it be done already? But the question is would that fit your requirements or do you need more?

Yeah, the drag stuff I've had experience with. I did it for the extension, and it's also on the horizontal slider which moves up and down on the code editor on the docs site. I set the inner elements to 100% so it fills the space, then it automatically adjusts. I don't know if that would be any use or not?

bob2517 commented 2 years ago

One other point on the slider thing, is you basically don't actually drag anything. I just used mousedown, mousemove and mouseleave. When you "drag" left, you adjust the left box width to make it smaller, and you adjust the right box to make it bigger (by the drag position change amount). Then when you drag it right, you make the right box width smaller and the left box bigger. So you create the effect of dragging, but all you're doing it changing the width of two boxes and the bit in the middle just follows you around.

And if you're doing that, all you need to do is set the inner contents of the boxes to fill the space, and that's literally all you've got to do. You just change two widths (or heights, depending on the slider), and that's pretty much it.

When I did it for the code editor, I needed to put a div on top of where the mouse was going so that I didn't trigger a mouse leave event when I "dragged" the bar.

Just to show the concept - the code was this - the working code is the code editor itself and you can see the code for the whole thing, if you like, in the reusable components section of the docs. It's quite old code now, but it works fine. This moves a "slider" up and down though. but can be adjusted to go left and right depending on the boxes you need to adjust.

There's not a lot of code there, and it's a neat trick considering that. The CodePen interface isn't that difficult to reproduce. You can certainly do it in a lot less code with ACSS I expect.

(Note o.doc, o.e, etc. - theses are available when setting variables and in custom commands. They are listed here: https://activecss.org/manual/func.html - I should probably mention them more in the docs. winH is window.innerHeight assigned using ACSS var somewhere else in the code.)

/* this was in a shadow DOM component because you can have multiple code editors on a page, but can be global. */
    #horizontalBar:mousedown {
        #bottomCover {     /* this was just a div covering the area of dragging when the person started dragging. */
            display: block;
        }
        prevent-default: true;
        stop-propagation: true;
        var: mouseIsDown true,
            containerHeight o.doc.getElementById('editorWrap').clientHeight,
            topWrapHeight o.doc.getElementById('typingAreaOuterWrap').clientHeight,
            bottomWrapHeight o.doc.getElementById('resultOuterWrap').clientHeight,
            resizerTop parseInt(o.secSelObj.offsetTop, 10),
            original_mouse_y o.e.pageY;
    }
    #editorWrap:if-var-true(mouseIsDown):mousemove {
        acss-editor-adjust-panels: true;    /* a custom command - see below */
    }
    #editorWrap:mouseleave, *:mouseup {
        #bottomCover {
            display: none;
        }
        var: mouseIsDown false;
    }

-------------------------------------
@command acss-editor-adjust-panels {=
    /* Pull in component ACSS variables to be used in this command. The scope used is the exact same as where the command is used. */
    vars containerHeight, topWrapHeight, bottomWrapHeight, resizerTop, original_mouse_y, minimum_sizeY, winH;

    /***
        The rest is regular JavaScript.
        Note: The variables e and doc used down below are always available in a custom command. See @command in docs.
    */
    const topHeight = topWrapHeight + (e.pageY - original_mouse_y);
    const bottomHeight = bottomWrapHeight + -(e.pageY - original_mouse_y);

    if (topHeight > 80 && bottomHeight > 80 && winH > 300) {
        const topWrap = doc.getElementById('typingAreaOuterWrap');
        const bottomWrap = doc.getElementById('resultOuterWrap');
        const resizer = doc.getElementById('horizontalBar');

        /* When the mouse moves over the results iframe, the document focus is lost and the resizing stops - we need it to continue for quick mouse movements so there's a transparent div over the iframe for this that gets displayed on mousedown to keep mouse events in the document that gets removed on mouseup or if the mouse goes out of the component. */
        const bottomCover = doc.getElementById('bottomCover');

        const newTopHeightPerc = topHeight / containerHeight * 100;
        const newBottomHeightPerc = bottomHeight / containerHeight * 100;
        const newBottomTopPerc = newTopHeightPerc;

        topWrap.style.height = 'calc(' + newTopHeightPerc + '% - 10px)';
        bottomWrap.style.height = 'calc(' + newBottomHeightPerc + '% + 10px)';
        bottomWrap.style.top = 'calc(' + newBottomTopPerc + '% + 10px)';
        bottomCover.style.height = 'calc(' + newBottomHeightPerc + '% + 10px)';
        bottomCover.style.top = 'calc(' + newBottomTopPerc + '% + 10px)';
        resizer.style.top = 'calc(' + newBottomTopPerc + '% - 14px)';
    }
=}
bob2517 commented 2 years ago

The other reason I used this method was because drag events don't work on mobile. They use touch events.

There is a polyfill though, if you do want to go the touch route and want it working on desktop: https://github.com/bob2517/dragdroptouch

That's an amazing magic plugin.

I like my method though, it avoids all of that nonsense by simply using mouseup, mousedown and mousemove.

I do understand why touch events were built differently to drag events - times change and so does technology.

However, in my opinion there should have been a single universal touch/drag event for both desktop and mobile. A finger is basically like a mouse anyway, when it comes to drag and drop (I know there's pinching and all that stuff, but that could just be a different event). This plugin proves the point. Hopefully there will be a single event for drag 'n drop one day. Anyways... I'm flagging it up in case you didn't know that already and it can save you time being aware of the problem. I wasted a lot of time building something only to find that it didn't work on an ipad. So I used the plugin. Drag events don't won't on tablets.

bob2517 commented 2 years ago

None of that particularly answers your question, but it could simplify things for you if you are working on the slider.

For the other part, I'm still struggling to think of a use case that can't be handled with CSS using width and height percentages or with CSS ellipsis handling (like text-overflow: ellipsis;), so please forgive me if I'm missing the point. I probably haven't run into that sort of thing yet.

dragontheory commented 2 years ago

Happy Friday!

Sorry again for the delay. Taking care sick family. As soon as I get a chance, I will catch up what you have written.

Yeah, the drag stuff I've had experience with. I did it for the extension, and it's also on the horizontal slider which moves up and down on the code editor on the docs site. I set the inner elements to 100% so it fills the space, then it automatically adjusts. I don't know if that would be any use or not?

Yes. YES. And YES!

Majorly excited about the splitter functionality!

THE most requested feature by far in my years as a DEV.

Users LOVE how they can make the UI work for them. Change it. Make it their own. They never see it coming either. "Wow... I can do that?" Some see it as fancy eye candy but it has ALWAYS been one of the best features to include in a web application project. Thus why I try to include it as early as possible. All my mockups include it to introduce the idea early.

Most solutions I've seen require at least one extra container/parent element and JS setting/resizing both sides with percentages, pixels, or a mix of both, depending on if the DEV/framework really wants to account for all the edge cases. It can get REALLY complicated when there are TWO or more resizable columns.

I am hoping that it is possible to leverage CSS Grid column template behavior to account for variable column widths. I'm very curious to see if columns can be resized without ever declaring their widths, using CSS Grid and CSS variables.

Before, I used CSS Flex-box. I was almost able to resize columns without setting widths, but really didn't do what I needed it to do and required the dreaded extra parent container/wrapper. Chris Coiyer from Css-tricks.com has an extensive article on how it may be a working but ultimately bloated solution.

bob2517 commented 2 years ago

Been away doing non-programming things, hence the delay.

Yes, I think I still need the parent container. It does give flexibility, but the slider is a common-enough requirement - like you said - that it shouldn't be something you have to spend a great deal of time implementing.

The more I think about it, It's the sort of functionality that should be built into HTML elements. Like the textarea expansion corner that you can drag around. I suppose the CSS options could just complicate things now... maybe that's why it's not been implemented at an HTML level. Just a basic slider to adjust adjacent elements would probably suffice in most cases. It must have been considered by someone already.

In fact, now that I can picture it, I'm surprised that it isn't already built into HTML elements. Textareas can do it already. But it might be something that is being held up by implications with CSS complexity that is already present, or something like that. I don't know how easy it would be to implement a slider (natively) to work with grid or flex, but it would probably add complexity if a slider aspect was added to elements by default. I'm not the world's greatest experts with grid and flex. I can remember how they work and implement them for about 5 minutes, and then it all disappears out of my brain.

bob2517 commented 2 years ago

Yeah - I knew I'd seen it before - there is the "resize" HTML property. It's a pity it doesn't allow dragging from the edge though - it looks like it has to be the corner.

I've only used it on textareas though. It's a bit limiting having it in the corner.

dragontheory commented 2 years ago

I'm just thinking in terms of can it be done already?

Yes, I believe boolean conditionals can be set up with existing CSS, but not adding/removing the class to the <body> when true and removed when false.

BTW, that reminds me, have you seen the latest syntax updates coming to CSS for @else and @when? https://blog.logrocket.com/extending-css-when-else-chains-first-look

Also the next version of Safari will have :has() enabled by default. https://css-irl.info/has-has-landed-in-safari/

bob2517 commented 2 years ago

BTW, that reminds me, have you seen the latest syntax updates coming to CSS for @else and @when?

Thanks for the link on that. I'm not back on here yet to answer other bits in more depth, but I did just check that out. It's interesting - it's like it's mirroring the possibilities we were approaching when discussing observe. I'm sure there's a scenario that warrants it, for it to get into the CSS spec.

So I'll need to add support for @when into ACSS. Currently standard media queries work with events in ACSS - like you can have different events depending on the device size. So I'll need to add in support for @when and the @when version of @else too, which shouldn't be too big a deal. I might leave it a little while and see when it starts getting adopted.

Also the next version of Safari will have :has() enabled by default.

That's good! Did you know that ACSS has ":has" support? The core should support all the CSS level 4 selectors, as long as they work in the browser. I've not tested ":has", but it should work - it's just a CSS selector. There's basically a list of them in the core so that internally they don't get mistaken for custom ACSS conditionals. I leave it up to the browser to get them to work... https://activecss.org/manual/conditionals.html

Yes, I believe boolean conditionals can be set up with existing CSS, but not adding/removing the class to the when true and removed when false.

Ok - will check that out in proper context and contribute more usefully when I'm officially back. I've been dealing with home networking issues and other spring cleaning (winter cleaning?) today, but will get refocused on that conversation soon.

dragontheory commented 2 years ago

I set the inner elements to 100% so it fills the space then it automatically adjusts. I don't know if that would be any use or not?

Yes! Result is the same but I use CSS Grid fr units. The nature of the Grid allows for not setting widths on either side.

dragontheory commented 2 years ago

And if you're doing that, all you need to do is set the inner contents of the boxes to fill the space, and that's literally all you've got to do. You just change two widths (or heights, depending on the slider), and that's pretty much it.

Yes! Totally! With Grid, you don't even need to set the widths! It is also much more smooth because the widths are handled by CSS which is 100 to 1K faster than JS and can leverage GPUs.

dragontheory commented 2 years ago

There's not a lot of code there, and it's a neat trick considering that. The CodePen interface isn't that difficult to reproduce. You can certainly do it in a lot less code with ACSS I expect.

Agreed! Will be doing it in CodePen soon. Want to have it ready to integrate into my projects.

Things may get a little complicated though when there are more than one resizable columns.

My real world scenario I need to build is a total of five columns, some fixed, some resizable and not all are visible at once! lol

CSS: 1fr pushes out to take up the rest of the space of the wrapper element 1.5fr pushes out to take up the rest of the space plus a half more of the wrapper element auto shrink space to element's inner content width

Remember this?

page-wrapper {
  display: grid;
  grid-template-columns: auto auto 1.5fr 1fr 1fr;
  grid-template-row: 1fr;
}
page-profile, page-details {display: none;} /* hide by default */
body.open-profile page-profile {display: grid;} /* show profile column */
body.open-profile.open-details page-details {display: grid;} /* show profile and details columns */

<app-container>________________________________________________________________________________________
|  <page-nav>____   <page-filter>____   <page-content>_______   <page-profile>___   <page-details>___  |
| |  ----------  | |  -------------  | |  -----------------  | |  -------------  | |  -------------  | |
| |  ----------  | |  -------------  | |  -----------------  | |  -------------  | |  -------------  | |
| |  --300px---  | |  --350px------  | |  -----------------  | |  -------------  | |  -------------  | |
| |  --inner---- | |  --inner------  | |  -----------------  | |  -------------  | |  -------------  | |
| |  --content-  | |  --content----  | |  -----------------  | |  -------------  | |  -------------  | |
| |  --width---  | |  --width------  | |  ---------------- <-|-|-> ----------- <-|-|-> ------------  | | 
| |  ----------  | |  -------------  | |  ---------------- splitter ---------- splitter -----------  | |
| |  ----------  | |  -------------  | |  -----------------  | |  -------------  | |  -------------  | |
| |  ----------  | |  -------------  | |  -----------------  | |  -------------  | |  -------------  | |
| |____</page-nav> |____</page-filter> |_______</page-content> |___</page-profile> |___</page-details> |
|_______________________________________________________________________________________</app-container>
    Fixed width         Fixed width         Variable width        Variable width      Variable width

The variable width columns are setup to resize themselves. Everything should fill in and take up space naturally.

Just need to provide the mechanism to resize them.

The CSS resize property can be applied to any element (not just textareas) and works fairly well.

Bad news is, as you pointed out, the resize property resizer appears only in the lower right corner of the resizing element and is difficult to change/style.

The good news is, we've taken JS out of the business of setting and getting widths and heights, which is a huge performance hit in light of how fast CSS can do it naturally on it's own. Plus we've reduced the need for JS down to just changing the size of the resizer - which I am not sure can be done easily with JS. I am up for the challenge!

WDYT?

bob2517 commented 9 months ago

Closing pending release. Added if-visible-x and if-visible-y commands and got the scoped container working with it.