@ethlete/core 5.0.0-next.23 → 5.0.0-next.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +30 -4
- package/fesm2022/ethlete-core.mjs +42 -21
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/generators/tailwind-4-color-theme/generator.js +24 -5
- package/generators/tailwind-4-surface-theme/generator.js +30 -12
- package/package.json +1 -1
- package/types/ethlete-core.d.ts +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @ethlete/core
|
|
2
2
|
|
|
3
|
+
## 5.0.0-next.25
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#3017](https://github.com/ethlete-io/ethdk/pull/3017) [`83853d6`](https://github.com/ethlete-io/ethdk/commit/83853d6138bc7d0a481b5b982c2a7137b9cc65e6) Thanks [@github-actions](https://github.com/apps/github-actions)! - Overlay: anchored overlays with `autoCloseIfReferenceHidden` now close instantly when their anchor is removed, instead of jumping to the viewport corner and animating out from there.
|
|
8
|
+
|
|
9
|
+
## 5.0.0-next.24
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#3016](https://github.com/ethlete-io/ethdk/pull/3016) [`2f18e43`](https://github.com/ethlete-io/ethdk/commit/2f18e4344759dbbcd17ba0dbeca138f1f7043cdf) Thanks [@github-actions](https://github.com/apps/github-actions)! - Anchored overlays: the `shift` option now also accepts `{ crossAxis?: boolean }` in addition to a boolean. With `crossAxis: true`, an overlay that fits on neither side of its reference is shifted along the placement's cross axis to stay inside the viewport (it may then overlap the reference) instead of overflowing off-screen.
|
|
14
|
+
|
|
15
|
+
Menus enable this by default: a nested submenu near the viewport edge first flips to the other side and, when neither side fits, slides over its parent menu — matching native OS menu behavior — instead of being cut off by the viewport.
|
|
16
|
+
|
|
17
|
+
The `size` middleware (`autoResize`) now runs after `shift` instead of before it, so `--et-overlay-max-width` / `--et-overlay-max-height` are measured from the pane's shifted position. Previously a cross-axis-shifted pane had its max size capped to the unshifted leftover space, squeezing e.g. a submenu to a sliver instead of letting it keep its width while overlapping its parent.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#3016](https://github.com/ethlete-io/ethdk/pull/3016) [`2f18e43`](https://github.com/ethlete-io/ethdk/commit/2f18e4344759dbbcd17ba0dbeca138f1f7043cdf) Thanks [@github-actions](https://github.com/apps/github-actions)! - Animations: fix overlays getting stuck in the DOM in an invisible but click-blocking state when their leave transition never reported completion.
|
|
22
|
+
|
|
23
|
+
When several anchored overlays close at once (e.g. clicking an item inside a nested menu closes the whole menu tree), destroying an ancestor pane shifts the anchored position of a still-leaving descendant. The browser then cancels and restarts the running transition, so its end event arrives either flagged as cancelled or under a stale transition id — and the animated lifecycle waited forever for an event that could no longer arrive, leaving the pane orphaned at `opacity: 0` while still intercepting pointer events. The lifecycle now treats the transition as settled once no animation is running anymore, matching the fallback the interrupted-transition path already had, so the overlay is torn down reliably.
|
|
24
|
+
|
|
25
|
+
- [#3016](https://github.com/ethlete-io/ethdk/pull/3016) [`3b359e5`](https://github.com/ethlete-io/ethdk/commit/3b359e52c5efa1cd9b0e79216fc578525aafa8e8) Thanks [@github-actions](https://github.com/apps/github-actions)! - Tailwind surface & color theme generators: fix the dynamic `--color-*-surface-*` /
|
|
26
|
+
`--color-*-theme-*` variables not resolving per scope. They were emitted only inside
|
|
27
|
+
`@theme`, which lands on `:root`, so `rgb(var(--<runtime>-surface-background))` resolved
|
|
28
|
+
once against the root surface and inherited that concrete color into descendants. As a
|
|
29
|
+
result `bg-<prefix>-surface-*` / `bg-<prefix>-theme-*` utilities ignored nested
|
|
30
|
+
`.<runtime>-surface--*` / `.<runtime>-color--*` scopes (e.g. an elevated surface would
|
|
31
|
+
still paint the root background). The dynamic colors are now also re-declared on each
|
|
32
|
+
surface/color selector in the alias block, so the utilities resolve against the nearest
|
|
33
|
+
scope while still being generated from `@theme`.
|
|
34
|
+
|
|
3
35
|
## 5.0.0-next.23
|
|
4
36
|
|
|
5
37
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
|
-
# core
|
|
1
|
+
# @ethlete/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Angular framework primitives for the Ethlete SDK — signal-based DOM observation, the surface/color theming systems, the animation and overlay engines, and a toolbox of directives, pipes and utilities. Component-less; every other `@ethlete/*` UI library builds on it.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @ethlete/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { injectCurrentBreakpoint, signalHostElementDimensions } from '@ethlete/core';
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
/* … */
|
|
18
|
+
})
|
|
19
|
+
export class ExampleComponent {
|
|
20
|
+
breakpoint = injectCurrentBreakpoint();
|
|
21
|
+
dimensions = signalHostElementDimensions();
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Documentation
|
|
26
|
+
|
|
27
|
+
Full guides on the docs site:
|
|
28
|
+
|
|
29
|
+
- [Overview](https://ethlete-sdk-docs.web.app/core/)
|
|
30
|
+
- [Element signals](https://ethlete-sdk-docs.web.app/core/element-signals) & [signal utilities](https://ethlete-sdk-docs.web.app/core/signal-utils)
|
|
31
|
+
- [Theming](https://ethlete-sdk-docs.web.app/core/theming)
|
|
32
|
+
- [Animations](https://ethlete-sdk-docs.web.app/core/animations) & [overlay runtime](https://ethlete-sdk-docs.web.app/core/overlay-runtime)
|
|
33
|
+
- [Providers](https://ethlete-sdk-docs.web.app/core/providers), [SEO](https://ethlete-sdk-docs.web.app/core/seo) & [utilities](https://ethlete-sdk-docs.web.app/core/utilities)
|
|
@@ -3634,7 +3634,21 @@ class AnimatedLifecycleDirective {
|
|
|
3634
3634
|
this.removeClass(fromClass);
|
|
3635
3635
|
this.addClass(toClass);
|
|
3636
3636
|
}
|
|
3637
|
-
}), switchMap(() => this.animatable.animationEnd$), filter((
|
|
3637
|
+
}), switchMap(() => this.animatable.animationEnd$), filter(() => this.state$.value === expectedState), switchMap((e) => {
|
|
3638
|
+
if (!e.cancelled && e.transitionId === transitionId) {
|
|
3639
|
+
return of(true);
|
|
3640
|
+
}
|
|
3641
|
+
// The batch ended cancelled or under a different id — e.g. the browser cancelled and
|
|
3642
|
+
// restarted a running transition mid-flight (an anchored overlay whose ancestor pane got
|
|
3643
|
+
// destroyed) or a foreign transition on the same element finished. If nothing is
|
|
3644
|
+
// animating anymore the expected end event can never arrive, so treat the transition as
|
|
3645
|
+
// settled instead of waiting forever (mirrors the fallback in handleInterruptedTransition).
|
|
3646
|
+
return this.animatable.isAnimating$.pipe(take(1), tap((stillAnimating) => {
|
|
3647
|
+
if (!stillAnimating) {
|
|
3648
|
+
this.debugLog(`${transitionId}: completes via fallback (batch id "${e.transitionId ?? 'none'}", cancelled ${e.cancelled})`);
|
|
3649
|
+
}
|
|
3650
|
+
}), map((stillAnimating) => !stillAnimating));
|
|
3651
|
+
}), filter((settled) => settled && this.state$.value === expectedState), tap(onComplete), take(1), takeUntil(cancelSignal), takeUntilDestroyed(this.destroyRef))
|
|
3638
3652
|
.subscribe();
|
|
3639
3653
|
}
|
|
3640
3654
|
handleInterruptedTransition(config) {
|
|
@@ -4583,6 +4597,15 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
4583
4597
|
fallbackPlacements: strategy.fallbackPlacements ?? undefined,
|
|
4584
4598
|
fallbackAxisSideDirection: 'start',
|
|
4585
4599
|
}));
|
|
4600
|
+
if (strategy.shift !== false) {
|
|
4601
|
+
middleware.push(shift({
|
|
4602
|
+
crossAxis: typeof strategy.shift === 'object' ? (strategy.shift.crossAxis ?? false) : false,
|
|
4603
|
+
limiter: limitShift(),
|
|
4604
|
+
padding: strategy.viewportPadding ?? 8,
|
|
4605
|
+
}));
|
|
4606
|
+
}
|
|
4607
|
+
// size must run AFTER shift so the available space is measured from the shifted position —
|
|
4608
|
+
// otherwise a cross-axis-shifted pane gets its max size capped to the unshifted leftover space
|
|
4586
4609
|
if (strategy.autoResize) {
|
|
4587
4610
|
middleware.push(size({
|
|
4588
4611
|
padding: strategy.viewportPadding ?? 8,
|
|
@@ -4594,12 +4617,6 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
4594
4617
|
},
|
|
4595
4618
|
}));
|
|
4596
4619
|
}
|
|
4597
|
-
if (strategy.shift !== false) {
|
|
4598
|
-
middleware.push(shift({
|
|
4599
|
-
limiter: limitShift(),
|
|
4600
|
-
padding: strategy.viewportPadding ?? 8,
|
|
4601
|
-
}));
|
|
4602
|
-
}
|
|
4603
4620
|
if (arrowElement) {
|
|
4604
4621
|
middleware.push(arrow({
|
|
4605
4622
|
element: arrowElement,
|
|
@@ -4616,9 +4633,24 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
4616
4633
|
strategy: 'absolute',
|
|
4617
4634
|
middleware,
|
|
4618
4635
|
}).then(({ x, y, placement, middlewareData }) => {
|
|
4636
|
+
// A detached reference reports a zeroed rect, so floating-ui positions the pane at the
|
|
4637
|
+
// viewport's top-left. Tear the overlay down (without animating from that bogus position)
|
|
4638
|
+
// before applying the transform when auto-close is on, and otherwise leave the pane where it
|
|
4639
|
+
// last was rather than snapping it to the corner.
|
|
4640
|
+
if (middlewareData.hide?.referenceHidden) {
|
|
4641
|
+
if (strategy.autoCloseIfReferenceHidden) {
|
|
4642
|
+
overlayRef.close(undefined, 'reference-detached');
|
|
4643
|
+
return;
|
|
4644
|
+
}
|
|
4645
|
+
if (strategy.autoHide) {
|
|
4646
|
+
renderer.setStyle(paneElement, { visibility: 'hidden' });
|
|
4647
|
+
}
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4619
4650
|
renderer.setStyle(paneElement, {
|
|
4620
4651
|
transform: `translate3d(${x}px, ${y}px, 0)`,
|
|
4621
4652
|
width: mirrorWidthElement ? `${mirrorWidthElement.offsetWidth}px` : null,
|
|
4653
|
+
visibility: null,
|
|
4622
4654
|
});
|
|
4623
4655
|
// exposed so animations can compose their transforms with the anchored position
|
|
4624
4656
|
renderer.setCssProperties(paneElement, {
|
|
@@ -4629,19 +4661,6 @@ const createAnchoredPositionCleanup = (strategy, paneElement, overlayRef, render
|
|
|
4629
4661
|
if (arrowElement && middlewareData.arrow) {
|
|
4630
4662
|
renderer.setCssProperty(arrowElement, '--et-floating-arrow-translate', `translate3d(${middlewareData.arrow.x ?? 0}px, ${middlewareData.arrow.y ?? 0}px, 0)`);
|
|
4631
4663
|
}
|
|
4632
|
-
if (middlewareData.hide?.referenceHidden) {
|
|
4633
|
-
if (strategy.autoCloseIfReferenceHidden) {
|
|
4634
|
-
overlayRef.close(undefined, 'api');
|
|
4635
|
-
return;
|
|
4636
|
-
}
|
|
4637
|
-
renderer.setStyle(paneElement, {
|
|
4638
|
-
visibility: strategy.autoHide ? 'hidden' : null,
|
|
4639
|
-
});
|
|
4640
|
-
return;
|
|
4641
|
-
}
|
|
4642
|
-
renderer.setStyle(paneElement, {
|
|
4643
|
-
visibility: null,
|
|
4644
|
-
});
|
|
4645
4664
|
});
|
|
4646
4665
|
});
|
|
4647
4666
|
return () => {
|
|
@@ -4896,7 +4915,9 @@ const [provideOverlayRuntime, injectOverlayRuntime] = createRootProvider(() => {
|
|
|
4896
4915
|
}
|
|
4897
4916
|
const lifecycle = getAnimatedLifecycle(componentRef);
|
|
4898
4917
|
animationDebugLog(`runtime ${config.id}`, `close requested (source "${closeEvent.source}", lifecycle state "${lifecycle?.state$.value ?? 'none'}", delegate ${config.animationDelegate?.leave ? 'yes' : 'no'})`);
|
|
4899
|
-
|
|
4918
|
+
// The reference element is gone — there is nothing left to animate away from, so tear the
|
|
4919
|
+
// overlay down synchronously instead of playing a leave transition from a stale position.
|
|
4920
|
+
if (!lifecycle || closeEvent.source === 'reference-detached') {
|
|
4900
4921
|
destroyMountedOverlay(closeEvent);
|
|
4901
4922
|
return;
|
|
4902
4923
|
}
|