Skip to content

Commit

Permalink
Merge pull request #289 from universal-ember/absorb-velcro
Browse files Browse the repository at this point in the history
Injest all of ember-velcro as a new optional import: `ember-primitives/floating-ui`
  • Loading branch information
NullVoxPopuli authored Apr 17, 2024
2 parents 4304acc + 5fd930b commit 463cca8
Show file tree
Hide file tree
Showing 16 changed files with 711 additions and 34 deletions.
7 changes: 2 additions & 5 deletions docs-app/app/markdown/import-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import * as eModifier from 'ember-modifier';
// ember-primitives (this library! yay!)
import * as emberPrimitives from 'ember-primitives';
import * as colorScheme from 'ember-primitives/color-scheme';
import * as floatingUI from 'ember-primitives/floating-ui';
// ember-resources
import * as emberResources from 'ember-resources';
// ember-velcro
import * as emberVelcro from 'ember-velcro';
import * as velcro from 'ember-velcro/modifiers/velcro';
// other
import * as loremIpsum from 'lorem-ipsum';
import * as remoteData from 'reactiveweb/remote-data';
Expand All @@ -26,8 +24,7 @@ import type { Options } from './compiler';
export const defaultOptions: Options = {
format: 'glimdown',
importMap: {
'ember-velcro': emberVelcro,
'ember-velcro/modifiers/velcro': velcro,
'ember-primitives/floating-ui': floatingUI,
'ember-primitives': emberPrimitives,
'ember-primitives/color-scheme': colorScheme,
'ember-headless-form': emberHeadlessForm,
Expand Down
1 change: 0 additions & 1 deletion docs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"ember-primitives": "workspace:*",
"ember-repl": "3.0.0-beta.8",
"ember-route-template": "^1.0.3",
"ember-velcro": "^2.1.3",
"hash": "^0.2.1",
"highlight.js": "^11.8.0",
"highlightjs-glimmer": "^2.2.1",
Expand Down
3 changes: 1 addition & 2 deletions ember-primitives/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@
"@babel/runtime": "^7.23.9",
"@embroider/addon-shim": "^1.8.7",
"@embroider/macros": "1.15.1",
"@floating-ui/dom": "^1.5.1",
"@floating-ui/dom": "^1.5.3",
"decorator-transforms": "^1.1.0",
"ember-element-helper": "^0.8.4",
"ember-velcro": "^2.1.3",
"reactiveweb": "^1.2.0",
"tabster": "^7.0.1",
"tracked-built-ins": "^3.2.0",
Expand Down
25 changes: 13 additions & 12 deletions ember-primitives/src/components/popover.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { arrow } from '@floating-ui/dom';
import { element } from 'ember-element-helper';
import { modifier } from 'ember-modifier';
import { cell } from 'ember-resources';
import { Velcro } from 'ember-velcro';

import { FloatingUI } from '../floating-ui.ts';
import { Portal } from './portal.gts';
import { TARGETS } from './portal-targets.gts';

import type { Signature as FloatingUiComponentSignature } from '../floating-ui/component.ts';
import type { Signature as HookSignature } from '../floating-ui/modifier.ts';
import type { TOC } from '@ember/component/template-only';
import type { Middleware, MiddlewareData } from '@floating-ui/dom';
import type { ElementContext, Middleware, MiddlewareData } from '@floating-ui/dom';
import type { ModifierLike, WithBoundArgs } from '@glint/template';
import type { Signature as HookSignature } from 'ember-velcro/modifiers/velcro';

export interface Signature {
Args: {
Expand Down Expand Up @@ -77,7 +78,7 @@ export interface Signature {
{
hook: ModifierLike<HookSignature>;
Content: WithBoundArgs<typeof Content, 'loop'>;
data: MiddlewareData;
data: FloatingUiComponentSignature['Blocks']['default'][0]['data'];
arrow: WithBoundArgs<ModifierLike<AttachArrowSignature>, 'arrowElement' | 'data'>;
},
];
Expand Down Expand Up @@ -206,31 +207,31 @@ function maybeAddArrow(middleware: Middleware[] | undefined, element: Element |

function flipOptions(options: HookSignature['Args']['Named']['flipOptions']) {
return {
elementContext: 'reference',
elementContext: 'reference' as ElementContext,
...options,
};
}

export const Popover: TOC<Signature> = <template>
{{#let (ArrowElement) as |arrowElement|}}
<Velcro
<FloatingUI
@placement={{@placement}}
@strategy={{@strategy}}
@middleware={{maybeAddArrow @middleware arrowElement.current}}
@flipOptions={{flipOptions @flipOptions}}
@shiftOptions={{@shiftOptions}}
@offsetOptions={{@offsetOptions}}
as |velcro|
as |fui|
>
{{yield
(hash
hook=velcro.hook
Content=(component Content loop=velcro.loop inline=@inline)
data=velcro.data
arrow=(modifier attachArrow arrowElement=arrowElement data=velcro.data)
hook=fui.hook
Content=(component Content loop=fui.loop inline=@inline)
data=fui.data
arrow=(modifier attachArrow arrowElement=arrowElement data=fui.data)
)
}}
</Velcro>
</FloatingUI>
{{/let}}
</template>;

Expand Down
2 changes: 2 additions & 0 deletions ember-primitives/src/floating-ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as FloatingUI } from './floating-ui/component.gts';
export { default as floatingUI } from './floating-ui/modifier.ts';
80 changes: 80 additions & 0 deletions ember-primitives/src/floating-ui/component.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { hash } from '@ember/helper';

import { modifier } from 'ember-modifier';

import VelcroModifier from './modifier.ts';

import type { Signature as ModifierSignature } from './modifier.ts';
import type { MiddlewareState } from '@floating-ui/dom';
import type { WithBoundArgs, WithBoundPositionals } from '@glint/template';
import type { ModifierLike } from '@glint/template';

type ModifierArgs = ModifierSignature['Args']['Named'];

export interface Signature {
Args: {
middleware?: ModifierArgs['middleware'];
placement?: ModifierArgs['placement'];
strategy?: ModifierArgs['strategy'];
flipOptions?: ModifierArgs['flipOptions'];
hideOptions?: ModifierArgs['hideOptions'];
shiftOptions?: ModifierArgs['shiftOptions'];
offsetOptions?: ModifierArgs['offsetOptions'];
};
Blocks: {
default: [
velcro: {
hook: ModifierLike<HookSignature>;
setHook: (element: HTMLElement | SVGElement) => void;
loop?: WithBoundArgs<WithBoundPositionals<typeof VelcroModifier, 1>, keyof ModifierArgs>;
data?: MiddlewareState;
},
];
};
}

interface HookSignature {
Element: HTMLElement | SVGElement;
}

export default class Velcro extends Component<Signature> {
@tracked hook?: HTMLElement | SVGElement = undefined;

// set by VelcroModifier
@tracked velcroData?: MiddlewareState = undefined;

setVelcroData: ModifierArgs['setVelcroData'] = (data) => (this.velcroData = data);

setHook = (element: HTMLElement | SVGElement) => {
this.hook = element;
};

velcroHook = modifier<HookSignature>((element: HTMLElement | SVGElement) => {
this.setHook(element);
});

<template>
{{#let
(modifier
VelcroModifier
flipOptions=@flipOptions
hideOptions=@hideOptions
middleware=@middleware
offsetOptions=@offsetOptions
placement=@placement
shiftOptions=@shiftOptions
strategy=@strategy
setVelcroData=this.setVelcroData
)
as |loop|
}}
{{#let (if this.hook (modifier loop this.hook)) as |loopWithHook|}}
{{yield
(hash hook=this.velcroHook setHook=this.setHook loop=loopWithHook data=this.velcroData)
}}
{{/let}}
{{/let}}
</template>
}
13 changes: 13 additions & 0 deletions ember-primitives/src/floating-ui/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Middleware } from '@floating-ui/dom';

export function velcroData(): Middleware {
return {
name: 'metadata',
fn: (data) => {
// https://floating-ui.com/docs/middleware#always-return-an-object
return {
data,
};
},
};
}
111 changes: 111 additions & 0 deletions ember-primitives/src/floating-ui/modifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { assert } from '@ember/debug';
import { registerDestructor } from '@ember/destroyable';

import { autoUpdate, computePosition, flip, hide, offset, shift } from '@floating-ui/dom';
import Modifier from 'ember-modifier';

import { velcroData } from './middleware.ts';

import type {
FlipOptions,
HideOptions,
Middleware,
OffsetOptions,
Placement,
ShiftOptions,
Strategy,
} from '@floating-ui/dom';

export interface Signature {
Element: HTMLElement;
Args: {
Positional: [referenceElement: string | HTMLElement | SVGElement];
Named: {
strategy?: Strategy;
offsetOptions?: OffsetOptions;
placement?: Placement;
flipOptions?: FlipOptions;
shiftOptions?: ShiftOptions;
hideOptions?: HideOptions;
middleware?: Middleware[];
setVelcroData?: Middleware['fn'];
};
};
}

export default class VelcroModifier extends Modifier<Signature> {
modify(
floatingElement: Signature['Element'],
[_referenceElement]: Signature['Args']['Positional'],
{
strategy = 'fixed',
offsetOptions = 0,
placement = 'bottom',
flipOptions,
shiftOptions,
middleware = [],
setVelcroData,
}: Signature['Args']['Named']
) {
const referenceElement: null | HTMLElement | SVGElement =
typeof _referenceElement === 'string'
? document.querySelector(_referenceElement)
: _referenceElement;

assert(
'no reference element defined',
referenceElement instanceof HTMLElement || referenceElement instanceof SVGElement
);

assert(
'no floating element defined',
floatingElement instanceof HTMLElement || _referenceElement instanceof SVGElement
);

assert(
'reference and floating elements cannot be the same element',
floatingElement !== _referenceElement
);

assert('@middleware must be an array of one or more objects', Array.isArray(middleware));

Object.assign(floatingElement.style, {
position: strategy,
top: '0',
left: '0',
});

let update = async () => {
let { middlewareData, x, y } = await computePosition(referenceElement, floatingElement, {
middleware: [
offset(offsetOptions),
flip(flipOptions),
shift(shiftOptions),
...middleware,
hide({ strategy: 'referenceHidden' }),
hide({ strategy: 'escaped' }),
velcroData(),
],
placement,
strategy,
});

let referenceHidden = middlewareData.hide?.referenceHidden;

Object.assign(floatingElement.style, {
top: `${y}px`,
left: `${x}px`,
margin: 0,
visibility: referenceHidden ? 'hidden' : 'visible',
});

setVelcroData?.(middlewareData['metadata']);
};

update();

let cleanup = autoUpdate(referenceElement, floatingElement, update);

registerDestructor(this, cleanup);
}
}
Loading

0 comments on commit 463cca8

Please sign in to comment.