From 3b9b28610ff6e5ac64ad580f4d91016a422cb145 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Thu, 27 Feb 2025 19:16:13 +0100 Subject: [PATCH] feat: new calendar control #296 --- development/issues/open/296.html | 22 +++ development/issues/open/296.mjs | 14 ++ development/templates/vite.config.mjs | 92 ++++++++- source/components/time/calendar.mjs | 181 ++++++++++++++++++ source/components/time/style/calendar.pcss | 0 .../components/time/stylesheet/calendar.mjs | 31 +++ 6 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 development/issues/open/296.html create mode 100644 development/issues/open/296.mjs create mode 100644 source/components/time/calendar.mjs create mode 100644 source/components/time/style/calendar.pcss create mode 100644 source/components/time/stylesheet/calendar.mjs diff --git a/development/issues/open/296.html b/development/issues/open/296.html new file mode 100644 index 000000000..053fab092 --- /dev/null +++ b/development/issues/open/296.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>new calendar control #296</title> + <script src="./296.mjs" type="module"></script> +</head> +<body> + <h1>new calendar control #296</h1> + <p></p> + <ul> + <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/296">Issue #296</a></li> + <li><a href="/">Back to overview</a></li> + </ul> + <main> + + <!-- Write your code here --> + + </main> +</body> +</html> diff --git a/development/issues/open/296.mjs b/development/issues/open/296.mjs new file mode 100644 index 000000000..0be173717 --- /dev/null +++ b/development/issues/open/296.mjs @@ -0,0 +1,14 @@ +/** +* @file development/issues/open/296.mjs +* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/296 +* @description new calendar control +* @issue 296 +*/ + +import "../../../source/components/style/property.pcss"; +import "../../../source/components/style/link.pcss"; +import "../../../source/components/style/color.pcss"; +import "../../../source/components/style/theme.pcss"; +import "../../../source/components/style/normalize.pcss"; +import "../../../source/components/style/typography.pcss"; + diff --git a/development/templates/vite.config.mjs b/development/templates/vite.config.mjs index 1d54977cb..fb7f48e9c 100644 --- a/development/templates/vite.config.mjs +++ b/development/templates/vite.config.mjs @@ -1,4 +1,3 @@ -import {resolve} from 'path'; import {defineConfig} from 'vite' import {projectRoot} from './import.mjs'; @@ -15,8 +14,97 @@ import postcssResponsiveType from 'postcss-responsive-type'; import {ViteMinifyPlugin} from 'vite-plugin-minify' import {viteMockServe} from 'vite-plugin-mock'; +import fs from 'fs'; +import path from 'path'; + import {buildCSS, createScriptFilenameFromStyleFilename} from '../scripts/buildStylePostCSS.mjs'; +function createIssueDirectoryListingPlugin() { + return { + name: 'create-issue-directory-listing-plugin', + configureServer(server) { + server.middlewares.use((req, res, next) => { + const urlPath = decodeURIComponent(req.url.split('?')[0]); // Verhindert Invalid URL Fehler + const pp = path.join(server.config.root, '/development/issues'); + const fsPath = path.join(pp, urlPath); + + + if (path.extname(urlPath)) { + if (urlPath.endsWith('.html') || urlPath.match(/\/(open|closed)\/\d+\.m?js$/)) { + req.url = '/development/issues' + req.url; + next(); + return; + } + + next(); + return; + } + + if (fs.existsSync(fsPath) && fs.statSync(fsPath).isDirectory()) { + const files = fs.readdirSync(fsPath); + + + // filter only html files and all directories + const htmlAndDirectories = files.filter(file => { + return fs.statSync(path.join(fsPath, file)).isDirectory() || file.endsWith('.html'); + }); + + // Korrekte Erstellung der Liste mit absoluten URLs + const listItems = htmlAndDirectories.map(file => { + const filePath = path.posix.join(urlPath, file); + const fullFilePath = path.join(fsPath, file); + + // Check if the file is an HTML file to extract title + if (file.endsWith('.html')) { + const content = fs.readFileSync(fullFilePath, 'utf8'); + const titleMatch = content.match(/<title>(.*?)<\/title>/); + const title = titleMatch ? titleMatch[1] : file; // Use the file name if no title is found + return `<li><a href="`+filePath+`">`+title+`</a></li>`; + } else { + return `<li><a href="`+filePath+`">`+file+`</a></li>`; + } + }).join(''); + + let homeButton = ''; + if (urlPath !== '/') { + homeButton = `<a href="/">🏠 Home</a>`; + } + + // Sicherstellen, dass das HTML korrekt zurückgegeben wird + const html = ` + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Index of ${urlPath}</title> + <style> + body { font-family: Arial, sans-serif; margin: 20px; } + ul { list-style-type: none; padding: 0; } + li { margin: 5px 0; } + a { text-decoration: none; color: #007bff; } + a:hover { text-decoration: underline; } + </style> + </head> + <body> + ` + homeButton + ` + <h1>Index of ` + urlPath + `</h1> + <ul>` + listItems + `</ul> + </body> + </html> + `; + + res.setHeader('Content-Type', 'text/html'); + res.end(html); + } else { + next(); + } + }); + }, + }; +} + + export default defineConfig({ clearScreen: false, @@ -41,6 +129,8 @@ export default defineConfig({ ViteMinifyPlugin(), //directoryIndex({}), + createIssueDirectoryListingPlugin(), + viteMockServe({ mockPath: projectRoot + '/development/mock', }), diff --git a/source/components/time/calendar.mjs b/source/components/time/calendar.mjs new file mode 100644 index 000000000..f2507bae0 --- /dev/null +++ b/source/components/time/calendar.mjs @@ -0,0 +1,181 @@ +/** + * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. + * Node module: @schukai/monster + * + * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). + * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html + * + * For those who do not wish to adhere to the AGPLv3, a commercial license is available. + * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. + * For more information about purchasing a commercial license, please contact schukai GmbH. + */ + +import { instanceSymbol } from "../../constants.mjs"; +import { addAttributeToken } from "../../dom/attributes.mjs"; +import { + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_ROLE, +} from "../../dom/constants.mjs"; +import { CustomControl } from "../../dom/customcontrol.mjs"; +import { CustomElement } from "../../dom/customelement.mjs"; +import { + assembleMethodSymbol, + registerCustomElement, +} from "../../dom/customelement.mjs"; +import { findTargetElementFromEvent } from "../../dom/events.mjs"; +import { isFunction } from "../../types/is.mjs"; +import { CalendarStyleSheet } from "./stylesheet/calendar.mjs"; +import { fireCustomEvent } from "../../dom/events.mjs"; + +export { Calendar }; + +/** + * @private + * @type {symbol} + */ +export const calendarElementSymbol = Symbol("calendarElement"); + +/** + * A Calendar + * + * @fragments /fragments/components/time/calendar/ + * + * @example /examples/components/time/calendar-simple + * + * @since 3.112.0 + * @copyright schukai GmbH + * @summary A beautiful Calendar that can make your life easier and also looks good. + */ + class Calendar extends CustomElement { + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/time/calendar@@instance"); + } + + /** + * + * @return {Components.Time.Calendar + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + initControlReferences.call(this); + initEventHandler.call(this); + return this; + } + + /** + * To set the options via the HTML Tag, the attribute `data-monster-options` must be used. + * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {Object} labels Label definitions + * @property {Object} actions Callbacks + * @property {string} actions.click="throw Error" Callback when clicked + * @property {Object} features Features + * @property {Object} classes CSS classes + * @property {boolean} disabled=false Disabled state + */ + get defaults() { + return Object.assign({}, super.defaults, { + templates: { + main: getTemplate(), + }, + labels: { + }, + classes: { + }, + disabled: false, + features: { + }, + actions: { + click: () => { + throw new Error("the click action is not defined"); + }, + } + }); + } + + /** + * @return {string} + */ + static getTag() { + return "monster-calendar"; + } + + /** + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [CalendarStyleSheet]; + } + + +} + +/** + * @private + * @return {initEventHandler} + * @fires monster-calendar-clicked + */ +function initEventHandler() { + const self = this; + const element = this[calendarElementSymbol]; + + const type = "click"; + + element.addEventListener(type, function (event) { + const callback = self.getOption("actions.click"); + + fireCustomEvent(self, "monster-calendar-clicked", { + element: self, + }); + + if (!isFunction(callback)) { + return; + } + + const element = findTargetElementFromEvent( + event, + ATTRIBUTE_ROLE, + "control", + ); + + if (!(element instanceof Node && self.hasNode(element))) { + return; + } + + callback.call(self, event); + }); + + return this; +} + +/** + * @private + * @return {void} + */ +function initControlReferences() { + this[calendarElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}="control"]`, + ); +} + +/** + * @private + * @return {string} + */ +function getTemplate() { + // language=HTML + return ` + <div data-monster-role="control" part="control"> + </div>`; +} + + +registerCustomElement(Calendar); diff --git a/source/components/time/style/calendar.pcss b/source/components/time/style/calendar.pcss new file mode 100644 index 000000000..e69de29bb diff --git a/source/components/time/stylesheet/calendar.mjs b/source/components/time/stylesheet/calendar.mjs new file mode 100644 index 000000000..972beb091 --- /dev/null +++ b/source/components/time/stylesheet/calendar.mjs @@ -0,0 +1,31 @@ +/** + * Copyright © schukai GmbH and all contributing authors, 2025. All rights reserved. + * Node module: @schukai/monster + * + * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). + * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html + * + * For those who do not wish to adhere to the AGPLv3, a commercial license is available. + * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. + * For more information about purchasing a commercial license, please contact schukai GmbH. + */ + +import {addAttributeToken} from "../../../dom/attributes.mjs"; +import {ATTRIBUTE_ERRORMESSAGE} from "../../../dom/constants.mjs"; + +export {CalendarStyleSheet} + +/** + * @private + * @type {CSSStyleSheet} + */ +const CalendarStyleSheet = new CSSStyleSheet(); + +try { + CalendarStyleSheet.insertRule(` +@layer calendar { + +}`, 0); +} catch (e) { + addAttributeToken(document.getRootNode().querySelector('html'), ATTRIBUTE_ERRORMESSAGE, e + ""); +} -- GitLab