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

feat(programmatic): add programmatic feature #944

Merged
merged 25 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4361f34
feat(programmatic): add useProgrammatically composable wrapper
mlmoravek Mar 1, 2024
4fa8382
feat(programmatic): add useProgrammatically composable wrapper
mlmoravek Mar 1, 2024
3fc61f4
refactor(programmatic): cleanup useProgrammatic composable
mlmoravek Mar 2, 2024
f9b0261
refactor(programmatic): cleanup useProgrammatic composable
mlmoravek Mar 2, 2024
e07f6c5
refactor(programmatic): cleanup useProgrammatic composable
mlmoravek Mar 2, 2024
3204511
refactor(programmatic): cleanup useProgrammatic composable
mlmoravek Mar 2, 2024
daa9534
refactor(programmatic): cleanup useProgrammatic composable
mlmoravek Mar 2, 2024
1cc8b59
refactor(programmatic): cleanup useProgrammatic composable
mlmoravek Mar 2, 2024
1f91bc6
refactor: review refactoring
mlmoravek Mar 29, 2024
9674014
Merge remote-tracking branch 'origin/develop' into cleanup/programmat…
mlmoravek Mar 29, 2024
8da713a
refactor(programmatic): update type definitions
mlmoravek Mar 29, 2024
e0d3123
refactor: review refactoring
mlmoravek Mar 29, 2024
89b7b68
Merge branch 'cleanup/programmatic-composable' into feature/useProgra…
mlmoravek Mar 31, 2024
3344e5f
feat: implement useProgrammatic function
mlmoravek Mar 31, 2024
8dca0db
Merge branch 'develop' into cleanup/programmatic-composable
mlmoravek May 1, 2024
eaf3aa3
feat(programmatic): add defineModel defaults
mlmoravek May 1, 2024
d8271c1
Merge branch 'cleanup/programmatic-composable' into feature/useProgra…
mlmoravek May 1, 2024
37af850
feat(programmatic): refactoring
mlmoravek May 1, 2024
2e9c25e
Merge remote-tracking branch 'origin/develop' into feature/useProgram…
mlmoravek Jun 6, 2024
f9dedde
refactor
mlmoravek Jun 6, 2024
b3b45dd
Merge remote-tracking branch 'origin/develop' into feature/useProgram…
mlmoravek Jun 10, 2024
457013a
docs: update example
mlmoravek Jun 10, 2024
2ddde76
refactor
mlmoravek Jun 10, 2024
72fedc3
refactor
mlmoravek Jun 10, 2024
15d1d6f
docs: update examples
mlmoravek Jun 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/oruga/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from "./menu";
export * from "./modal";
export * from "./notification";
export * from "./pagination";
export * from "./programmatic";
export * from "./radio";
export * from "./select";
export * from "./skeleton";
Expand Down
6 changes: 3 additions & 3 deletions packages/oruga/src/components/notification/examples/index.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<script setup lang="ts">
import Programmatically from "./programmatically.vue";
import ProgrammaticallyCode from "./programmatically.vue?raw";

import Base from "./base.vue";
import BaseCode from "./base.vue?raw";

Expand All @@ -13,6 +10,9 @@ import UseTypesCode from "./use-types.vue?raw";

import AddCustomButtons from "./add-custom-buttons.vue";
import AddCustomButtonsCode from "./add-custom-buttons.vue?raw";

import Programmatically from "./programmatically.vue";
import ProgrammaticallyCode from "./programmatically.vue?raw";
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup lang="ts">
// @ts-expect-error Examples are loaded differently.
import { useOruga } from "../../../../dist/oruga";
import NotificationForm from "./_notification-form.vue";

const oruga = useOruga();

Check warning on line 6 in packages/oruga/src/components/notification/examples/programmatic.vue

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/notification/examples/programmatic.vue#L6

Added line #L6 was not covered by tests

async function component(): Promise<void> {
const instance = oruga.programmatic.open({

Check warning on line 9 in packages/oruga/src/components/notification/examples/programmatic.vue

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/notification/examples/programmatic.vue#L8-L9

Added lines #L8 - L9 were not covered by tests
component: NotificationForm,
target: "#notification",
});

// wait until the notification got closed
const result = await instance.promise;

Check warning on line 15 in packages/oruga/src/components/notification/examples/programmatic.vue

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/notification/examples/programmatic.vue#L15

Added line #L15 was not covered by tests

oruga.notification.open({

Check warning on line 17 in packages/oruga/src/components/notification/examples/programmatic.vue

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/notification/examples/programmatic.vue#L17

Added line #L17 was not covered by tests
duration: 5000,
message: "Modal dialog returned " + JSON.stringify(result),
variant: "info",
position: "top",
closable: true,
});
}
</script>

<template>
<section>
<o-button
label="Launch notification (component)"
variant="warning"
size="medium"
@click="component" />
</section>
</template>

<style lang="scss">
.toast-notification {
margin: 0.5em 0;
text-align: center;
box-shadow:
0 1px 4px rgb(0 0 0 / 12%),
0 0 6px rgb(0 0 0 / 4%);
border-radius: 2em;
padding: 0.75em 1.5em;
pointer-events: auto;
color: rgba(0, 0, 0, 0.7);
background: #ffdd57;
}
</style>
2 changes: 2 additions & 0 deletions packages/oruga/src/components/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Menu from "./menu";
import Modal from "./modal";
import Notification from "./notification";
import Pagination from "./pagination";
import Programmatic from "./programmatic";
import Radio from "./radio";
import Select from "./select";
import Skeleton from "./skeleton";
Expand Down Expand Up @@ -45,6 +46,7 @@ export {
Modal,
Notification,
Pagination,
Programmatic,
Radio,
Select,
Skeleton,
Expand Down
145 changes: 145 additions & 0 deletions packages/oruga/src/components/programmatic/Programmatic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {
createVNode,
defineComponent,
render,
getCurrentInstance,
onMounted,
onUnmounted,
type Component,
type ComponentInternalInstance,
type VNode,
} from "vue";

import InstanceRegistry from "@/utils/InstanceRegistry";
import { VueInstance } from "@/utils/plugins";
import { isElement, removeElement } from "@/utils/helpers";
import { isClient } from "@/utils/ssr";

import type { ProgrammaticExpose } from "@/types";

declare module "../../index" {
interface OrugaProgrammatic {
programmatic: typeof Programmatic;
}
}

type ProgrammaticComponentProps = {
/**
* Component to be injected.
* Terminate the component by emitting a 'close' event — emits('close')
*/
component: string | Component;
/**
* Props to be binded to the injected component.
* Both attributes and properties can be used in props.
* Vue automatically picks the right way to assign it.
* `class` and `style` have the same object / array value support like in templates.
* Event listeners should be passed as onXxx.
* @see https://vuejs.org/api/render-function.html#h
*/
props?: Record<string, any>;
/** Callback function to call on close event */
onClose?: (...args: unknown[]) => void;
/** Destroy component on close event */
destroyable?: boolean;
/**
* This is used internally for programmatic usage
* @ignore
*/
instances: InstanceRegistry<ComponentInternalInstance>;
};

const ProgrammaticComponent = defineComponent(
(props: ProgrammaticComponentProps, { expose }) => {

Check warning on line 53 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L52-L53

Added lines #L52 - L53 were not covered by tests
// getting a hold of the internal instance in setup()
const vm = getCurrentInstance();

Check warning on line 55 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L55

Added line #L55 was not covered by tests

let resolve: (value?: unknown) => void = null;
const promise = new Promise((p1) => {
resolve = p1;

Check warning on line 59 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L57-L59

Added lines #L57 - L59 were not covered by tests
});

onMounted(() => {
props.instances.add(vm);

Check warning on line 63 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L62-L63

Added lines #L62 - L63 were not covered by tests
});

onUnmounted(() => {
props.instances.remove(vm);
resolve.apply(null);

Check warning on line 68 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L66-L68

Added lines #L66 - L68 were not covered by tests
});

function close(...args: unknown[]): void {

Check warning on line 71 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L71

Added line #L71 was not covered by tests
// call handler if given
if (typeof props.onClose === "function")
props.onClose.apply(null, args);

Check warning on line 74 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L74

Added line #L74 was not covered by tests

if (typeof props.destroyable === "undefined" || props.destroyable) {
// use timeout for any animation to complete before destroying
setTimeout(() => {
const element = vm.vnode.el as Element;

Check warning on line 79 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L78-L79

Added lines #L78 - L79 were not covered by tests
// remove the component from the container or the body tag
if (element) {
if (isClient)
window.requestAnimationFrame(() =>
removeElement(element),

Check warning on line 84 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L83-L84

Added lines #L83 - L84 were not covered by tests
);
else removeElement(element);

Check warning on line 86 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L86

Added line #L86 was not covered by tests
}
});
}
}

/** expose public functionalities for programmatic usage */
expose({ close, promise });

Check warning on line 93 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L93

Added line #L93 was not covered by tests

// render given component
return (): VNode =>
createVNode(props.component, { ...props.props, onClose: close });

Check warning on line 97 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L96-L97

Added lines #L96 - L97 were not covered by tests
},
{ props: ["component", "props", "onClose", "destroyable", "instances"] },
);

const instances = new InstanceRegistry<ComponentInternalInstance>();

Check warning on line 102 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L102

Added line #L102 was not covered by tests

export type ProgrammaticProps = {
/**
* Specify a target the component get rendered into.
* @default `body`
*/
target?: string | HTMLElement;
} & Omit<ProgrammaticComponentProps, "instances">;

const Programmatic = {
open(props: ProgrammaticProps): ProgrammaticExpose {

Check warning on line 113 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L112-L113

Added lines #L112 - L113 were not covered by tests
const target =
typeof props.target === "string"
? document.querySelector<HTMLElement>(props.target)
: isElement(props.target)
? (props.target as HTMLElement)
: document.body;

Check warning on line 119 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L116-L119

Added lines #L116 - L119 were not covered by tests

// cache container
const container = document.createElement("div");

Check warning on line 122 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L122

Added line #L122 was not covered by tests

// create dynamic component
const vnode = createVNode(ProgrammaticComponent, {

Check warning on line 125 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L125

Added line #L125 was not covered by tests
...props,
instances: instances,
});
vnode.appContext = VueInstance._context;

Check warning on line 129 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L129

Added line #L129 was not covered by tests

// render a new vue instance into the cache container
render(vnode, container);

Check warning on line 132 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L132

Added line #L132 was not covered by tests

// place rendered elements into target element
target.append(...container.childNodes);

Check warning on line 135 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L135

Added line #L135 was not covered by tests

// return exposed functionalities
return vnode.component.exposed as ProgrammaticExpose;

Check warning on line 138 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L138

Added line #L138 was not covered by tests
},
closeAll(...args: any[]): void {
instances.walk((entry) => entry.exposed.close(...args));

Check warning on line 141 in packages/oruga/src/components/programmatic/Programmatic.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/Programmatic.ts#L140-L141

Added lines #L140 - L141 were not covered by tests
},
};

export default Programmatic;
17 changes: 17 additions & 0 deletions packages/oruga/src/components/programmatic/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { App, Plugin } from "vue";

import Programmatic from "./Programmatic";

import { registerComponentProgrammatic } from "@/utils/plugins";

/** export programmatic specific types */
export type { ProgrammaticProps } from "./Programmatic";

/** export programmatic plugin */
export default {
install(app: App) {
registerComponentProgrammatic(app, "programmatic", Programmatic);

Check warning on line 13 in packages/oruga/src/components/programmatic/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/components/programmatic/index.ts#L12-L13

Added lines #L12 - L13 were not covered by tests
},
} as Plugin;

// no component export here
3 changes: 3 additions & 0 deletions packages/oruga/src/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,9 @@ Meaning that the container should be fixed. */
/** Pagination size */
size: string;
}>;
programmatic?: ComponentConfigBase &
Partial<{
}>;
radio?: ComponentConfigBase &
Partial<{
/** Class of the native input element */
Expand Down
2 changes: 1 addition & 1 deletion packages/oruga/src/types/programmatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ export interface ProgrammaticInstance<T = ComponentInternalInstance> {
*/
export interface ProgrammaticExpose {
close(...args: any[]): void;
promise: Promise<unknown>;
promise: Promise<void>;
}
14 changes: 14 additions & 0 deletions packages/oruga/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,20 @@
return false;
}

/**
* Returns true if it is a DOM element
* @source https://stackoverflow.com/questions/384286/how-do-you-check-if-a-javascript-object-is-a-dom-object
*/
export function isElement(o: any): boolean {

Check warning on line 158 in packages/oruga/src/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/utils/helpers.ts#L158

Added line #L158 was not covered by tests
return typeof HTMLElement === "object"
? o instanceof HTMLElement //DOM2
: o &&
typeof o === "object" &&
o !== null &&
o.nodeType === 1 &&
typeof o.nodeName === "string";

Check warning on line 165 in packages/oruga/src/utils/helpers.ts

View check run for this annotation

Codecov / codecov/patch

packages/oruga/src/utils/helpers.ts#L160-L165

Added lines #L160 - L165 were not covered by tests
}

/**
* Clone an obj with Object.assign
*/
Expand Down
Loading