@micazoyolli/foundation 0.2.0 → 0.3.1
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/README.md +38 -81
- package/dist/a11y/focus.d.ts +15 -0
- package/dist/a11y/focus.js +57 -0
- package/dist/a11y/index.d.ts +2 -0
- package/dist/a11y/index.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/seo/dom.d.ts +9 -0
- package/dist/seo/dom.js +35 -0
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,112 +1,69 @@
|
|
|
1
1
|
# @micazoyolli/foundation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Características
|
|
6
|
-
|
|
7
|
-
- Tokens SCSS base para spacing, radius, z-index y motion
|
|
8
|
-
- Breakpoints y mixins responsive reutilizables
|
|
9
|
-
- Helper `cx` para composición segura de clases
|
|
10
|
-
- Guards TypeScript pequeños para validaciones comunes
|
|
11
|
-
- Utilidades básicas de accesibilidad y protección selectiva de media
|
|
12
|
-
- Helpers SEO/build puros para HTML estático, canonical y sitemap
|
|
13
|
-
- Exports separados para TypeScript y SCSS
|
|
14
|
-
- Consumo desde npm sin credenciales especiales
|
|
15
|
-
|
|
16
|
-
## Tecnologías
|
|
17
|
-
|
|
18
|
-
- TypeScript
|
|
19
|
-
- SCSS
|
|
20
|
-
- Yarn 1
|
|
21
|
-
- npm
|
|
22
|
-
|
|
23
|
-
## Estructura
|
|
24
|
-
|
|
25
|
-
```txt
|
|
26
|
-
src/
|
|
27
|
-
├── a11y/
|
|
28
|
-
├── scss/
|
|
29
|
-
│ ├── mixins/
|
|
30
|
-
│ └── tokens/
|
|
31
|
-
├── seo/
|
|
32
|
-
├── utils/
|
|
33
|
-
└── index.ts
|
|
34
|
-
|
|
35
|
-
examples/
|
|
36
|
-
dist/
|
|
37
|
-
```
|
|
3
|
+
[](https://www.npmjs.com/package/@micazoyolli/foundation)
|
|
4
|
+
[](./LICENSE)
|
|
38
5
|
|
|
39
|
-
|
|
6
|
+
Fundamentos frontend no visuales para construir repos independientes con una base tecnica consistente.
|
|
40
7
|
|
|
41
|
-
|
|
42
|
-
yarn install
|
|
43
|
-
yarn build
|
|
44
|
-
yarn clean
|
|
45
|
-
yarn prepack
|
|
46
|
-
```
|
|
8
|
+
Foundation centraliza mecanicas pequenas que se repiten entre proyectos: tokens SCSS base, mixins responsive, helpers TypeScript, primitivas DOM de accesibilidad y utilidades SEO/build. No incluye React, componentes visuales, tokens de marca, layouts ni metadata especifica.
|
|
47
9
|
|
|
48
|
-
##
|
|
10
|
+
## Documentacion
|
|
49
11
|
|
|
50
|
-
|
|
51
|
-
yarn add @micazoyolli/foundation
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Uso
|
|
12
|
+
La documentacion completa esta en:
|
|
55
13
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
import { cx, isElement } from '@micazoyolli/foundation';
|
|
14
|
+
[https://foundation.nadia.dev](https://foundation.nadia.dev)
|
|
60
15
|
|
|
61
|
-
|
|
16
|
+
## Instalacion
|
|
62
17
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
18
|
+
```bash
|
|
19
|
+
yarn add @micazoyolli/foundation
|
|
66
20
|
```
|
|
67
21
|
|
|
68
|
-
|
|
22
|
+
## TypeScript
|
|
69
23
|
|
|
70
24
|
```ts
|
|
71
|
-
import {
|
|
72
|
-
applyHtmlMetadata,
|
|
73
|
-
buildSitemapXml,
|
|
74
|
-
getCanonicalUrl,
|
|
75
|
-
} from '@micazoyolli/foundation';
|
|
25
|
+
import { cx, getCanonicalUrl } from '@micazoyolli/foundation';
|
|
76
26
|
|
|
27
|
+
const className = cx('card', isActive && 'card--active');
|
|
77
28
|
const canonical = getCanonicalUrl('https://example.com', '/contacto');
|
|
78
|
-
const html = applyHtmlMetadata(template, {
|
|
79
|
-
canonical,
|
|
80
|
-
title: 'Contacto',
|
|
81
|
-
description: 'Hablemos de tu proyecto.',
|
|
82
|
-
});
|
|
83
|
-
const sitemap = buildSitemapXml([{ loc: canonical, priority: '0.8' }]);
|
|
84
29
|
```
|
|
85
30
|
|
|
86
|
-
SCSS
|
|
31
|
+
## SCSS
|
|
87
32
|
|
|
88
33
|
```scss
|
|
89
34
|
@use '@micazoyolli/foundation/scss' as foundation;
|
|
90
35
|
|
|
91
36
|
.section {
|
|
92
|
-
padding: foundation.$space-6;
|
|
93
|
-
border-radius: foundation.$radius-md;
|
|
37
|
+
padding-block: foundation.$space-6;
|
|
94
38
|
|
|
95
39
|
@include foundation.down(foundation.$breakpoint-md) {
|
|
96
|
-
padding: foundation.$space-4;
|
|
40
|
+
padding-block: foundation.$space-4;
|
|
97
41
|
}
|
|
98
42
|
}
|
|
99
43
|
```
|
|
100
44
|
|
|
101
|
-
##
|
|
45
|
+
## Caracteristicas
|
|
46
|
+
|
|
47
|
+
- Tokens SCSS base: spacing, radius, z-index, motion y breakpoints.
|
|
48
|
+
- Mixins SCSS para responsive y reduced motion.
|
|
49
|
+
- `cx` y guards TypeScript pequenos.
|
|
50
|
+
- Helpers DOM para metadata client-side.
|
|
51
|
+
- Primitivas de accesibilidad para focus, Escape, scroll lock y media protegida.
|
|
52
|
+
- Helpers SEO/build para canonical, sitemap, HTML estatico y escaping.
|
|
53
|
+
- Sin dependencias runtime pesadas y sin React.
|
|
54
|
+
|
|
55
|
+
## Compatibilidad
|
|
56
|
+
|
|
57
|
+
Foundation puede usarse con React, Next.js, Vue, Angular, Astro, Vite y scripts Node segun el tipo de helper. Los helpers DOM deben ejecutarse solo en navegador.
|
|
102
58
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
59
|
+
## Scripts
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
yarn build
|
|
63
|
+
yarn test
|
|
64
|
+
yarn docs:build
|
|
65
|
+
```
|
|
109
66
|
|
|
110
|
-
##
|
|
67
|
+
## Autoria
|
|
111
68
|
|
|
112
|
-
Una
|
|
69
|
+
Una creacion de [`<micazoyolli />`](https://nadia.dev)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const FOCUSABLE_SELECTOR: string;
|
|
2
|
+
export type BodyScrollLock = {
|
|
3
|
+
document: Document;
|
|
4
|
+
previousOverflow: string;
|
|
5
|
+
};
|
|
6
|
+
type FocusableElement = HTMLElement & {
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const getFocusableElements: (container: Pick<ParentNode, "querySelectorAll">) => FocusableElement[];
|
|
10
|
+
export declare const lockBodyScroll: (documentRef?: Document) => BodyScrollLock;
|
|
11
|
+
export declare const unlockBodyScroll: (lock: BodyScrollLock) => void;
|
|
12
|
+
export declare const restoreFocus: (element: Element | null | undefined, documentRef?: Document) => void;
|
|
13
|
+
export declare const trapTabKey: (event: Pick<KeyboardEvent, "key" | "shiftKey" | "preventDefault">, container: Pick<ParentNode, "querySelectorAll">, activeElement?: Element | null | undefined) => boolean;
|
|
14
|
+
export declare const createEscapeKeyHandler: (onEscape: () => void) => (event: Pick<KeyboardEvent, "key">) => void;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { KEYBOARD_KEYS } from './keyboard.js';
|
|
2
|
+
export const FOCUSABLE_SELECTOR = [
|
|
3
|
+
'a[href]',
|
|
4
|
+
'button:not([disabled])',
|
|
5
|
+
'input:not([disabled])',
|
|
6
|
+
'select:not([disabled])',
|
|
7
|
+
'textarea:not([disabled])',
|
|
8
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
9
|
+
].join(',');
|
|
10
|
+
const canReceiveFocus = (element) => !element.disabled
|
|
11
|
+
&& element.getAttribute('aria-hidden') !== 'true'
|
|
12
|
+
&& element.tabIndex !== -1;
|
|
13
|
+
export const getFocusableElements = (container) => Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(canReceiveFocus);
|
|
14
|
+
export const lockBodyScroll = (documentRef = document) => {
|
|
15
|
+
const previousOverflow = documentRef.body.style.overflow;
|
|
16
|
+
documentRef.body.style.overflow = 'hidden';
|
|
17
|
+
return {
|
|
18
|
+
document: documentRef,
|
|
19
|
+
previousOverflow,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export const unlockBodyScroll = (lock) => {
|
|
23
|
+
lock.document.body.style.overflow = lock.previousOverflow;
|
|
24
|
+
};
|
|
25
|
+
export const restoreFocus = (element, documentRef = document) => {
|
|
26
|
+
if (!element || !documentRef.body.contains(element))
|
|
27
|
+
return;
|
|
28
|
+
const focusable = element;
|
|
29
|
+
if (typeof focusable.focus === 'function') {
|
|
30
|
+
focusable.focus();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
export const trapTabKey = (event, container, activeElement = document.activeElement) => {
|
|
34
|
+
if (event.key !== 'Tab')
|
|
35
|
+
return false;
|
|
36
|
+
const focusableElements = getFocusableElements(container);
|
|
37
|
+
const firstElement = focusableElements[0];
|
|
38
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
39
|
+
if (!firstElement || !lastElement)
|
|
40
|
+
return false;
|
|
41
|
+
if (event.shiftKey && activeElement === firstElement) {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
lastElement.focus();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (!event.shiftKey && activeElement === lastElement) {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
firstElement.focus();
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
export const createEscapeKeyHandler = (onEscape) => (event) => {
|
|
54
|
+
if (event.key === KEYBOARD_KEYS.escape) {
|
|
55
|
+
onEscape();
|
|
56
|
+
}
|
|
57
|
+
};
|
package/dist/a11y/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { isElement, isHTMLElement } from './targets.js';
|
|
2
2
|
export { KEYBOARD_KEYS, isKeyboardActivation } from './keyboard.js';
|
|
3
3
|
export { PROTECTED_MEDIA_SELECTOR, isProtectedMediaTarget, } from './protectedMedia.js';
|
|
4
|
+
export { FOCUSABLE_SELECTOR, createEscapeKeyHandler, getFocusableElements, lockBodyScroll, restoreFocus, trapTabKey, unlockBodyScroll, } from './focus.js';
|
|
5
|
+
export type { BodyScrollLock } from './focus.js';
|
package/dist/a11y/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { isElement, isHTMLElement } from './targets.js';
|
|
2
2
|
export { KEYBOARD_KEYS, isKeyboardActivation } from './keyboard.js';
|
|
3
3
|
export { PROTECTED_MEDIA_SELECTOR, isProtectedMediaTarget, } from './protectedMedia.js';
|
|
4
|
+
export { FOCUSABLE_SELECTOR, createEscapeKeyHandler, getFocusableElements, lockBodyScroll, restoreFocus, trapTabKey, unlockBodyScroll, } from './focus.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { KEYBOARD_KEYS, PROTECTED_MEDIA_SELECTOR, isElement, isHTMLElement, isKeyboardActivation, isProtectedMediaTarget, } from './a11y/index.js';
|
|
1
|
+
export { FOCUSABLE_SELECTOR, KEYBOARD_KEYS, PROTECTED_MEDIA_SELECTOR, createEscapeKeyHandler, getFocusableElements, isElement, isHTMLElement, isKeyboardActivation, isProtectedMediaTarget, lockBodyScroll, restoreFocus, trapTabKey, unlockBodyScroll, } from './a11y/index.js';
|
|
2
|
+
export type { BodyScrollLock } from './a11y/index.js';
|
|
2
3
|
export { cx } from './utils/cx.js';
|
|
3
4
|
export type { ClassValue } from './utils/cx.js';
|
|
4
5
|
export { isNonEmptyString } from './utils/guards.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { KEYBOARD_KEYS, PROTECTED_MEDIA_SELECTOR, isElement, isHTMLElement, isKeyboardActivation, isProtectedMediaTarget, } from './a11y/index.js';
|
|
1
|
+
export { FOCUSABLE_SELECTOR, KEYBOARD_KEYS, PROTECTED_MEDIA_SELECTOR, createEscapeKeyHandler, getFocusableElements, isElement, isHTMLElement, isKeyboardActivation, isProtectedMediaTarget, lockBodyScroll, restoreFocus, trapTabKey, unlockBodyScroll, } from './a11y/index.js';
|
|
2
2
|
export { cx } from './utils/cx.js';
|
|
3
3
|
export { isNonEmptyString } from './utils/guards.js';
|
|
4
4
|
export * from './seo/index.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type MetaAttributes = {
|
|
2
|
+
content: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
property?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const updateDocumentTitle: (title: string, documentRef?: Document) => void;
|
|
7
|
+
export declare const upsertMeta: (selector: string, attributes: MetaAttributes, documentRef?: Document) => HTMLMetaElement;
|
|
8
|
+
export declare const upsertCanonical: (href: string, documentRef?: Document) => HTMLLinkElement;
|
|
9
|
+
export declare const upsertAlternate: (hreflang: string, href: string, documentRef?: Document) => HTMLLinkElement;
|
package/dist/seo/dom.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const updateDocumentTitle = (title, documentRef = document) => {
|
|
2
|
+
documentRef.title = title;
|
|
3
|
+
};
|
|
4
|
+
export const upsertMeta = (selector, attributes, documentRef = document) => {
|
|
5
|
+
let element = documentRef.head.querySelector(selector);
|
|
6
|
+
if (!element) {
|
|
7
|
+
element = documentRef.createElement('meta');
|
|
8
|
+
documentRef.head.appendChild(element);
|
|
9
|
+
}
|
|
10
|
+
Object.entries(attributes).forEach(([name, value]) => {
|
|
11
|
+
element?.setAttribute(name, value);
|
|
12
|
+
});
|
|
13
|
+
return element;
|
|
14
|
+
};
|
|
15
|
+
export const upsertCanonical = (href, documentRef = document) => {
|
|
16
|
+
let element = documentRef.head.querySelector('link[rel="canonical"]');
|
|
17
|
+
if (!element) {
|
|
18
|
+
element = documentRef.createElement('link');
|
|
19
|
+
element.rel = 'canonical';
|
|
20
|
+
documentRef.head.appendChild(element);
|
|
21
|
+
}
|
|
22
|
+
element.href = href;
|
|
23
|
+
return element;
|
|
24
|
+
};
|
|
25
|
+
export const upsertAlternate = (hreflang, href, documentRef = document) => {
|
|
26
|
+
let element = documentRef.head.querySelector(`link[rel="alternate"][hreflang="${hreflang}"]`);
|
|
27
|
+
if (!element) {
|
|
28
|
+
element = documentRef.createElement('link');
|
|
29
|
+
element.rel = 'alternate';
|
|
30
|
+
element.hreflang = hreflang;
|
|
31
|
+
documentRef.head.appendChild(element);
|
|
32
|
+
}
|
|
33
|
+
element.href = href;
|
|
34
|
+
return element;
|
|
35
|
+
};
|
package/dist/seo/index.d.ts
CHANGED
|
@@ -5,3 +5,5 @@ export type { SitemapEntry, SitemapOptions } from './sitemap.js';
|
|
|
5
5
|
export { escapeHtml, escapeXml } from './text.js';
|
|
6
6
|
export { getAbsoluteUrl, getCanonicalUrl, isAbsoluteUrl, normalizeBaseUrl, normalizeRoutePath, } from './url.js';
|
|
7
7
|
export type { CanonicalUrlOptions } from './url.js';
|
|
8
|
+
export { updateDocumentTitle, upsertAlternate, upsertCanonical, upsertMeta, } from './dom.js';
|
|
9
|
+
export type { MetaAttributes } from './dom.js';
|
package/dist/seo/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { applyHtmlMetadata, getAlternateLinkTags, getStaticRouteOutputPath, remo
|
|
|
2
2
|
export { buildSitemapXml } from './sitemap.js';
|
|
3
3
|
export { escapeHtml, escapeXml } from './text.js';
|
|
4
4
|
export { getAbsoluteUrl, getCanonicalUrl, isAbsoluteUrl, normalizeBaseUrl, normalizeRoutePath, } from './url.js';
|
|
5
|
+
export { updateDocumentTitle, upsertAlternate, upsertCanonical, upsertMeta, } from './dom.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@micazoyolli/foundation",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Fundamentos
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Fundamentos frontend no visuales para el ecosistema tecnico de Nad.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -25,10 +25,15 @@
|
|
|
25
25
|
"build": "tsc -p tsconfig.json && yarn copy-scss",
|
|
26
26
|
"clean": "rm -rf dist",
|
|
27
27
|
"copy-scss": "rsync -av --include='*/' --include='*.scss' --exclude='*' src/scss/ dist/scss/",
|
|
28
|
-
"
|
|
28
|
+
"docs:build": "vitepress build docs",
|
|
29
|
+
"docs:dev": "vitepress dev docs --host 0.0.0.0",
|
|
30
|
+
"docs:preview": "vitepress preview docs --host 0.0.0.0",
|
|
31
|
+
"prepack": "yarn build",
|
|
32
|
+
"test": "yarn build && node --test tests/*.test.mjs"
|
|
29
33
|
},
|
|
30
34
|
"devDependencies": {
|
|
31
|
-
"typescript": "6.0.3"
|
|
35
|
+
"typescript": "6.0.3",
|
|
36
|
+
"vitepress": "^1.6.4"
|
|
32
37
|
},
|
|
33
38
|
"packageManager": "yarn@1.22.22"
|
|
34
39
|
}
|