@micazoyolli/foundation 0.2.0 → 0.3.0

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 CHANGED
@@ -1,112 +1,91 @@
1
1
  # @micazoyolli/foundation
2
2
 
3
- Paquete público de fundamentos compartidos no visuales para los proyectos de Micazoyolli. Centraliza utilidades pequeñas y estables para mantener consistencia entre repos independientes sin introducir componentes visuales ni decisiones de marca.
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
- ```
38
-
39
- ## Scripts
3
+ Fundamentos compartidos no visuales para los proyectos de Micazoyolli.
40
4
 
41
- ```bash
42
- yarn install
43
- yarn build
44
- yarn clean
45
- yarn prepack
46
- ```
5
+ `@micazoyolli/foundation` centraliza utilidades pequenas y estables que ya se repiten entre repos independientes: tokens SCSS base, mixins responsive, helpers TypeScript, accesibilidad ligera y utilidades SEO/build. No incluye React, componentes visuales, tokens de marca, layouts ni metadata especifica de ningun proyecto.
47
6
 
48
- ## Instalación
7
+ ## Instalacion
49
8
 
50
9
  ```bash
51
10
  yarn add @micazoyolli/foundation
52
11
  ```
53
12
 
54
- ## Uso
13
+ ## Entrypoints
55
14
 
56
15
  TypeScript:
57
16
 
58
17
  ```ts
59
- import { cx, isElement } from '@micazoyolli/foundation';
18
+ import { cx, getCanonicalUrl } from '@micazoyolli/foundation';
19
+ ```
60
20
 
61
- const className = cx('button', isActive && 'button--active');
21
+ SCSS:
62
22
 
63
- if (isElement(event.target)) {
64
- event.target.closest('[data-protected-media]');
65
- }
23
+ ```scss
24
+ @use '@micazoyolli/foundation/scss' as foundation;
66
25
  ```
67
26
 
68
- SEO/build:
27
+ ## Documentacion
69
28
 
70
- ```ts
71
- import {
72
- applyHtmlMetadata,
73
- buildSitemapXml,
74
- getCanonicalUrl,
75
- } from '@micazoyolli/foundation';
76
-
77
- 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
+ ### Primeros pasos
85
30
 
86
- SCSS:
31
+ - [Getting Started](./docs/getting-started.md)
32
+ - [Installation](./docs/installation.md)
87
33
 
88
- ```scss
89
- @use '@micazoyolli/foundation/scss' as foundation;
34
+ ### SCSS
35
+
36
+ - [Breakpoints](./docs/scss/breakpoints.md)
37
+ - [Spacing](./docs/scss/spacing.md)
38
+ - [Motion](./docs/scss/motion.md)
39
+ - [Mixins](./docs/scss/mixins.md)
40
+
41
+ ### TypeScript
42
+
43
+ - [DOM y accesibilidad](./docs/typescript/dom.md)
44
+ - [SEO y build](./docs/typescript/seo.md)
45
+ - [Utils](./docs/typescript/utils.md)
46
+
47
+ ### Proyecto
48
+
49
+ - [Changelog](./docs/changelog.md)
50
+ - [Roadmap](./docs/roadmap.md)
51
+
52
+ ## Que incluye
53
+
54
+ - Tokens SCSS base para spacing, radius, z-index, motion y breakpoints.
55
+ - Mixins SCSS para responsive y reduced motion.
56
+ - `cx` para composicion simple de clases.
57
+ - Guards TypeScript pequenos.
58
+ - Helpers DOM/a11y para targets, teclado y media protegida.
59
+ - Helpers DOM/a11y para foco, scroll lock, Escape y focus trap.
60
+ - Helpers SEO/build para escaping, canonical, sitemap, metadata DOM y HTML estatico.
90
61
 
91
- .section {
92
- padding: foundation.$space-6;
93
- border-radius: foundation.$radius-md;
62
+ ## Que NO incluye
94
63
 
95
- @include foundation.down(foundation.$breakpoint-md) {
96
- padding: foundation.$space-4;
97
- }
98
- }
64
+ - Componentes React.
65
+ - Botones, cards, layouts o grids visuales.
66
+ - Tokens finales de marca, color o tipografia.
67
+ - Metadata especifica de proyectos.
68
+ - Logica de negocio.
69
+ - Dependencias UI pesadas.
70
+
71
+ ## Scripts
72
+
73
+ ```bash
74
+ yarn install
75
+ yarn build
76
+ yarn clean
77
+ yarn prepack
99
78
  ```
100
79
 
101
- ## Buenas prácticas
80
+ ## Proyectos que ya lo consumen
102
81
 
103
- - Mantener el paquete pequeño y estrictamente no visual
104
- - Evitar tokens de marca, layouts, componentes React, metadata específica o tipografías finales
105
- - Publicar cambios mediante versiones semánticas
106
- - Exportar solo utilidades compartibles entre repos independientes
107
- - Evitar dependencias UI pesadas
108
- - Adoptar el paquete gradualmente en cada proyecto
82
+ - TeInvitaASu Invitaciones
83
+ - TeInvitaASu.Party
84
+ - Micazoyolli
85
+ - Estilo Natura
86
+ - OhMamaMXX
87
+ - WTFashion
109
88
 
110
89
  ## Autora
111
90
 
112
- Una creación de [`<micazoyolli />✨`](https://nadia.dev)
91
+ 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
+ };
@@ -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';
@@ -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;
@@ -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
+ };
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@micazoyolli/foundation",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Fundamentos compartidos no visuales para los proyectos de Micazoyolli.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,7 +25,8 @@
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
- "prepack": "yarn build"
28
+ "prepack": "yarn build",
29
+ "test": "yarn build && node --test tests/*.test.mjs"
29
30
  },
30
31
  "devDependencies": {
31
32
  "typescript": "6.0.3"