Skip to content

Commit a0a4751

Browse files
starredevBryan van der Starre
and
Bryan van der Starre
authored
feat: dynamic custom components (#54)
* Replaced hardcoded component keys with a dynamic mapped type based on CustomComponentName. * calendarApp is required — so no need to check it --------- Co-authored-by: Bryan van der Starre <[email protected]>
1 parent ef26c18 commit a0a4751

9 files changed

+105
-128
lines changed

CHANGELOG.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ All notable changes to this project will be documented in this file. See [Conven
44

55
# [2.2.0](https://github.com/schedule-x/svelte/compare/v2.1.0...v2.2.0) (2024-12-25)
66

7-
87
### Features
98

10-
* support svelte 5 ([#43](https://github.com/schedule-x/svelte/issues/43)) ([b6923fa](https://github.com/schedule-x/svelte/commit/b6923fa396233bbe5b7196bd1ddfccf7b2d6c4d7))
9+
- support svelte 5 ([#43](https://github.com/schedule-x/svelte/issues/43)) ([b6923fa](https://github.com/schedule-x/svelte/commit/b6923fa396233bbe5b7196bd1ddfccf7b2d6c4d7))
1110

1211
# [2.1.0](https://github.com/schedule-x/svelte/compare/v2.0.1...v2.1.0) (2024-11-25)
1312

package-lock.json

+47-43
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,18 @@
2828
"!dist/**/*.spec.*"
2929
],
3030
"peerDependencies": {
31-
"@schedule-x/calendar": "^2.0.0",
31+
"@schedule-x/calendar": "^2.23.0",
32+
"@schedule-x/shared": "^2.23.0",
3233
"svelte": "^4 || ^5"
3334
},
3435
"devDependencies": {
35-
"@schedule-x/theme-default": "^2.0.0",
36+
"@schedule-x/theme-default": "^2.18.0",
3637
"@semantic-release/changelog": "^6.0.3",
3738
"@semantic-release/git": "^10.0.1",
38-
"@sveltejs/adapter-auto": "^3.0.0",
39+
"@sveltejs/adapter-auto": "^3.3.1",
3940
"@sveltejs/kit": "^2.0.0",
4041
"@sveltejs/package": "^2.0.0",
41-
"@sveltejs/vite-plugin-svelte": "^3.0.0",
42+
"@sveltejs/vite-plugin-svelte": "^4.0.0",
4243
"@types/eslint": "^9.0.0",
4344
"eslint": "^9.0.0",
4445
"eslint-config-prettier": "^9.1.0",

src/lib/components/schedule-x-calendar.svelte

+7-42
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,43 @@
44
import { createCustomComponentFn } from '$lib/utils/create-custom-component-fn.js';
55
import Portal from '$lib/utils/Portal.svelte';
66
7-
import type { CustomComponentMeta, CustomComponents } from '$lib/types/custom-components.js';
7+
import type { CustomComponentMeta } from '$lib/types/custom-components.js';
88
import type { CalendarApp } from '@schedule-x/calendar';
99
1010
export let calendarApp: CalendarApp;
1111
12-
export let timeGridEvent: CustomComponents['timeGridEvent'] | undefined = undefined;
13-
export let dateGridEvent: CustomComponents['dateGridEvent'] | undefined = undefined;
14-
export let monthGridEvent: CustomComponents['monthGridEvent'] | undefined = undefined;
15-
export let monthAgendaEvent: CustomComponents['monthAgendaEvent'] | undefined = undefined;
16-
export let eventModal: CustomComponents['eventModal'] | undefined = undefined;
17-
export let headerContentLeftPrepend: CustomComponents['headerContentLeftPrepend'] | undefined =
18-
undefined;
19-
export let headerContentLeftAppend: CustomComponents['headerContentLeftAppend'] | undefined =
20-
undefined;
21-
export let headerContentRightPrepend: CustomComponents['headerContentRightPrepend'] | undefined =
22-
undefined;
23-
export let headerContentRightAppend: CustomComponents['headerContentRightAppend'] | undefined =
24-
undefined;
25-
export let headerContent: CustomComponents['headerContent'] | undefined = undefined;
26-
2712
let customComponentsMeta: CustomComponentMeta[] = [];
28-
2913
$: wrapperId = randomStringId();
30-
$: customComponentsMeta = [];
3114
3215
const setComponent = (component: CustomComponentMeta) => {
3316
const newComponents = [...customComponentsMeta];
3417
const ccid = component.wrapperElement.dataset.ccid;
35-
const existingComponent = newComponents.find((c) => c.wrapperElement.dataset.ccid === ccid);
18+
const existing = newComponents.find((c) => c.wrapperElement.dataset.ccid === ccid);
3619
37-
if (existingComponent) {
38-
newComponents.splice(newComponents.indexOf(existingComponent), 1);
20+
if (existing) {
21+
newComponents.splice(newComponents.indexOf(existing), 1);
3922
}
4023
4124
customComponentsMeta = [...newComponents, component];
4225
};
4326
4427
const setCustomComponentFns = () => {
45-
const customComponentsAvailable: {
46-
name: keyof CustomComponents;
47-
component: CustomComponents[keyof CustomComponents];
48-
}[] = [
49-
{ name: 'timeGridEvent', component: timeGridEvent },
50-
{ name: 'dateGridEvent', component: dateGridEvent },
51-
{ name: 'monthGridEvent', component: monthGridEvent },
52-
{ name: 'monthAgendaEvent', component: monthAgendaEvent },
53-
{ name: 'eventModal', component: eventModal },
54-
{ name: 'headerContentLeftPrepend', component: headerContentLeftPrepend },
55-
{ name: 'headerContentLeftAppend', component: headerContentLeftAppend },
56-
{ name: 'headerContentRightPrepend', component: headerContentRightPrepend },
57-
{ name: 'headerContentRightAppend', component: headerContentRightAppend },
58-
{ name: 'headerContent', component: headerContent }
59-
];
60-
61-
customComponentsAvailable.forEach(({ name, component }) => {
28+
Object.entries($$restProps).forEach(([name, component]) => {
6229
if (component) {
63-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
64-
// @ts-ignore
6530
calendarApp._setCustomComponentFn(name, createCustomComponentFn(setComponent, component));
6631
}
6732
});
6833
};
6934
7035
onMount(() => {
71-
setCustomComponentFns();
72-
7336
const wrapper = document.getElementById(wrapperId);
7437
if (!(wrapper instanceof HTMLElement)) {
7538
console.warn('Could not find wrapper element to mount calendar on');
7639
return;
7740
}
7841
42+
setCustomComponentFns();
43+
7944
calendarApp.render(wrapper);
8045
});
8146
</script>

src/lib/types/custom-components.ts

+8-20
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
11
import type { SvelteComponent } from 'svelte';
2-
import type { CalendarEvent } from '@schedule-x/calendar';
2+
import type { CustomComponentName } from '@schedule-x/shared';
33

4-
type TimeGridEvent = typeof SvelteComponent<{ calendarEvent: CalendarEvent }>;
5-
type DateGridEvent = typeof SvelteComponent<{ calendarEvent: CalendarEvent }>;
6-
type MonthGridEvent = typeof SvelteComponent<{
7-
calendarEvent: CalendarEvent;
8-
hasStartDate: boolean;
9-
}>;
10-
type MonthAgendaEvent = typeof SvelteComponent<{ calendarEvent: CalendarEvent }>;
11-
type EventModal = typeof SvelteComponent<{ calendarEvent: CalendarEvent }>;
4+
type ComponentType<Props extends Record<string, unknown> = Record<string, unknown>> = new (
5+
...args: unknown[]
6+
) => SvelteComponent<Props>;
127

138
export type CustomComponents = {
14-
timeGridEvent?: TimeGridEvent;
15-
dateGridEvent?: DateGridEvent;
16-
monthGridEvent?: MonthGridEvent;
17-
monthAgendaEvent?: MonthAgendaEvent;
18-
eventModal?: EventModal;
19-
headerContentLeftPrepend?: typeof SvelteComponent<{ [x: string]: never }>;
20-
headerContentLeftAppend?: typeof SvelteComponent<{ [x: string]: never }>;
21-
headerContentRightPrepend?: typeof SvelteComponent<{ [x: string]: never }>;
22-
headerContentRightAppend?: typeof SvelteComponent<{ [x: string]: never }>;
23-
headerContent?: typeof SvelteComponent<{ [x: string]: never }>;
9+
[key in CustomComponentName]?: ComponentType;
2410
};
11+
2512
export type CustomComponentMeta = {
26-
Component: SvelteComponent;
13+
Component: ComponentType;
2714
wrapperElement: HTMLElement;
2815
props: Record<string, unknown>;
2916
};
17+
3018
export type CustomComponentsMeta = CustomComponentMeta[];

src/lib/utils/Portal.svelte

+18-15
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
1-
<script context="module">
1+
<script context="module" lang="ts">
22
import { tick } from 'svelte';
33
44
/**
55
* Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}>
66
*
7-
* @param {HTMLElement} el
8-
* @param {HTMLElement|string} target DOM Element or CSS Selector
7+
* @param el - The element to move
8+
* @param target - DOM element or CSS selector
99
*/
10-
export function portal(el, target = 'body') {
11-
let targetEl;
12-
async function update(newTarget) {
10+
export function portal(el: HTMLElement, target: HTMLElement | string = 'body') {
11+
let targetEl: HTMLElement;
12+
13+
async function update(newTarget: string | HTMLElement) {
1314
target = newTarget;
15+
1416
if (typeof target === 'string') {
15-
targetEl = document.querySelector(target);
17+
targetEl = document.querySelector(target)!;
18+
1619
if (targetEl === null) {
1720
await tick();
18-
targetEl = document.querySelector(target);
21+
targetEl = document.querySelector(target)!;
1922
}
23+
2024
if (targetEl === null) {
21-
throw new Error(`No element found matching css selector: "${target}"`);
25+
throw new Error(`No element found matching CSS selector: "${target}"`);
2226
}
2327
} else if (target instanceof HTMLElement) {
2428
targetEl = target;
@@ -29,6 +33,7 @@
2933
}. Allowed types: string (CSS selector) or HTMLElement.`
3034
);
3135
}
36+
3237
targetEl.appendChild(el);
3338
el.hidden = false;
3439
}
@@ -40,19 +45,17 @@
4045
}
4146
4247
update(target);
48+
4349
return {
4450
update,
4551
destroy
4652
};
4753
}
4854
</script>
4955

50-
<script>
51-
/**
52-
* DOM Element or CSS Selector
53-
* @type { HTMLElement|string}
54-
*/
55-
export let target = 'body';
56+
<script lang="ts">
57+
// Accepts a DOM element or CSS selector
58+
export let target: HTMLElement | string = 'body';
5659
</script>
5760

5861
<div style="height: 100%; width: 100%" use:portal={target} hidden>

src/lib/utils/create-custom-component-fn.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { SvelteComponent } from 'svelte';
44
export const createCustomComponentFn =
55
(
66
setCustomComponent: (component: CustomComponentMeta) => void,
7-
customComponent: SvelteComponent
7+
customComponent: typeof SvelteComponent
88
) =>
99
(wrapperElement: HTMLElement, props: Record<string, unknown>) => {
1010
setCustomComponent({

0 commit comments

Comments
 (0)