@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 CHANGED
@@ -1,112 +1,69 @@
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
- ```
3
+ [![npm version](https://img.shields.io/npm/v/@micazoyolli/foundation.svg)](https://www.npmjs.com/package/@micazoyolli/foundation)
4
+ [![license](https://img.shields.io/npm/l/@micazoyolli/foundation.svg)](./LICENSE)
38
5
 
39
- ## Scripts
6
+ Fundamentos frontend no visuales para construir repos independientes con una base tecnica consistente.
40
7
 
41
- ```bash
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
- ## Instalación
10
+ ## Documentacion
49
11
 
50
- ```bash
51
- yarn add @micazoyolli/foundation
52
- ```
53
-
54
- ## Uso
12
+ La documentacion completa esta en:
55
13
 
56
- TypeScript:
57
-
58
- ```ts
59
- import { cx, isElement } from '@micazoyolli/foundation';
14
+ [https://foundation.nadia.dev](https://foundation.nadia.dev)
60
15
 
61
- const className = cx('button', isActive && 'button--active');
16
+ ## Instalacion
62
17
 
63
- if (isElement(event.target)) {
64
- event.target.closest('[data-protected-media]');
65
- }
18
+ ```bash
19
+ yarn add @micazoyolli/foundation
66
20
  ```
67
21
 
68
- SEO/build:
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
- ## Buenas prácticas
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
- - 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
59
+ ## Scripts
60
+
61
+ ```bash
62
+ yarn build
63
+ yarn test
64
+ yarn docs:build
65
+ ```
109
66
 
110
- ## Autora
67
+ ## Autoria
111
68
 
112
- Una creación de [`<micazoyolli />✨`](https://nadia.dev)
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
+ };
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "@micazoyolli/foundation",
3
- "version": "0.2.0",
4
- "description": "Fundamentos compartidos no visuales para los proyectos de Micazoyolli.",
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
- "prepack": "yarn build"
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
  }