hx components Utilities - bradwoo8621/hx GitHub Wiki
Utilities
Utility functions and context providers exported by @hx/components.
Build Content
Constructs action menu content from action/group definitions.
import { buildContent } from '@hx/components';
const menu = buildContent([
{ label: 'Edit', onClick: handleEdit },
{ type: 'separator' },
{ label: 'Delete', onClick: handleDelete, disabled: true },
]);
Input Utilities
Factory functions for input event handlers.
import {
createHxInputFocusHandler,
createHxInputKeyDownHandler,
createHxInputSelectAllHandler,
} from '@hx/components';
Pagination Utility
Computes HxPaginationData from raw item/page counts:
import { computePaginationData } from '@hx/components';
const data = computePaginationData(150, 20, 1);
// => { pageSize: 20, pageNumber: 1, totalPages: 8, totalItems: 150 }
Upload Utilities
import {
parseFileName, // (name: string) => { name: string, ext: string }
mapError, // (error: unknown) => user-friendly string
isImage, // (file: HxUploadFile) => boolean
toImageSrc, // (bytes: ArrayBuffer) => data: URL string
releaseImage, // (src: string) => undefined โ revokes object URL
} from '@hx/components';
Accept Check
Normalizes accept strings/arrays into a validation function:
import { computeAccept } from '@hx/components';
const check = computeAccept(['.pdf', 'image/*']);
check({ name: 'doc.pdf', mimeType: 'application/pdf' }); // true
check({ name: 'photo.jpg', mimeType: 'image/jpeg' }); // true
check({ name: 'data.exe', mimeType: 'application/x-msdownload' }); // false
SVG Icon Defaults
import { computeSvgIconDefaults } from '@hx/components';
// Computes default dimensions and styles from settings
Context Providers
Internal providers using EventEmitter for cross-component communication:
| Provider | State Provided |
|---|---|
HxPanelProvider |
{ collapsed, toggle } |
HxTabProvider |
{ isActive, mark, disabled, activate } |
HxTabsProvider |
{ tabs, activeMark, switchToTab, restoreScroll } |
HxUploadProvider |
{ files, errors, uploading, addFiles, removeFile, uploadAll, clearFiles } |
HxSelectOptionsProvider |
{ options, selectedValue, searchText, selectOption } |
HxOverlayInternalProvider |
Lifecycle: entering โ entered โ exiting โ exited |
Pattern Kits (FormatInput)
Abstract base and built-in implementations for HxFormatInput:
import {
AbstractHxFormatInputPatternKit,
HxFormatInputNumberPatternKit,
HxFormatInputDateTimePatternKit,
HxFormatInputPatternKitsInner, // registry: @n โ NumberKit, @d โ DateTimeKit
} from '@hx/components';
class MyKit extends AbstractHxFormatInputPatternKit {
parse(raw: string): ParsedValue { /* ... */ }
format(parsed: ParsedValue): string { /* ... */ }
}
Date Localization
DateLocaleFormatUtils provides locale-aware formatting for individual date/time parts using Intl.DateTimeFormat.formatToParts(). All formatting is UTC-based โ dates are passed as UTCDate (a timezone-free wrapper over the JS Date), so the host timezone never affects the output.
import { DateLocaleFormatUtils, UTCDate } from '@hx/components';
const date = UTCDate.of(2025, 6, 6); // 2025-07-06
// Locale-aware part formatting (returns part value with literal suffix)
DateLocaleFormatUtils.formatYear(date, 'ja-JP', false); // "ไปคๅ7ๅนด"
DateLocaleFormatUtils.formatYear(date, 'zh-CN', true); // "2025ๅนด" (forced Gregorian)
DateLocaleFormatUtils.formatMonth(date, 'zh-CN', false); // "7ๆ"
DateLocaleFormatUtils.formatDay(date, 'en-US', true); // "6"
DateLocaleFormatUtils.formatWeekday(date, 'zh-CN', true); // "ๅจๆฅ"
Plugin Architecture
Non-Gregorian calendars (Japanese, Minguo, Buddhist, Korean, Coptic, Ethiopic, Hebrew, Islamic ร3, Persian, Indian, Chinese) are managed through a plugin system implementing the DateLocaleNotGregorianProvider interface. Each plugin declares its own accept(), calendar(), supportedLanguages(), and optional eraAs() / yearAs() / yearHeaderLabel() / monthHeaderLabel() / eraOfDays() / monthsOfYear() / yearsAround() methods. Calendar navigation additionally needs a move provider implementing DateMoveNotGregorianProvider (moveYear / moveMonth / boundary hooks), registered via DateMoveUtils.enableNotGregorianMoveProvider().
Enable locale-specific calendar support:
import {
DateBuddhistUtils, DateChineseUtils, DateCopticUtils, DateEthiopicUtils,
DateHebrewUtils, DateIndianUtils, DateIslamicUtils, DateIslamicCivilUtils,
DateIslamicUmalquraUtils, DateJapaneseUtils, DateKoreanUtils, DateMinguoUtils,
DatePersianUtils
} from '@hx/components';
DateCopticUtils.enable(); // ar-EG โ coptic (Anno Martyrum)
DateEthiopicUtils.enable(); // am-ET / ti-ET โ ethiopic (Incarnation Era)
DateJapaneseUtils.enable(); // ja / ja-JP โ japanese
DateMinguoUtils.enable(); // zh-TW / zh-Hant-TW โ roc (Minguo)
DateKoreanUtils.enable(); // ko / ko-KR / ko-KP โ Gregorian (no special calendar)
DateBuddhistUtils.enable(); // th / th-TH โ buddhist
DateHebrewUtils.enable(); // he / he-IL โ hebrew
DateIslamicUtils.enable(); // ar-DZ / ar-MA / ar-TN โ islamic
DateIslamicCivilUtils.enable(); // ar-AE / ar-IQ / ar-SY / ... โ islamic-civil
DateIslamicUmalquraUtils.enable(); // ar-SA / ar-OM / ar-YE / ... โ islamic-umalqura
DateIndianUtils.enable(); // hi / hi-IN / en-IN โ indian
DatePersianUtils.enable(); // fa / fa-IR / ckb-IR / ... โ persian
DateChineseUtils.enable(); // zh-CN โ chinese
DateCopticUtils.disable(); // Remove the Coptic plugin (each plugin also exposes disable())
The Coptic calendar spans two eras: Anno Martyrum (AM, Gregorian 284+) and Before Diocletian (displayed with a "B.D." prefix, e.g. "B.D. 185"). The plugin implements yearAs() to handle the era prefix automatically.
The Ethiopic calendar spans two eras: Anno Incarnationis (A.I., Gregorian 8+) and Before Incarnation (displayed with a "B.I." prefix, e.g. "B.I. 5493"). The B.I. era uses year numbers 5493โ5500 (Gregorian 1โ8 CE). The plugin implements yearAs() to handle the era prefix automatically.
The Persian calendar's leap rule follows ICU (persncal.cpp): a base 33-year
cycle of 8 leap years (years whose remainder is in {1, 5, 9, 13, 17, 22, 26, 30}),
overridden for Persian years 1502โ2987 by a leap-day shift table โ each entry X
makes year X common and year X+1 leap, moving the leap day one year forward.
Years โฅ 2988 fall back to the plain mod-33 rule. The move provider implements
the same rule so navigation and formatting always agree with the runtime Intl.
Calendar resolution โ when gregorian is false, DateLocaleFormatUtils resolves the calendar from the locale via the CALENDAR_MAP, which is populated from the supportedLanguages() of every enabled plugin:
ar-AE/ar-BH/ar-IQ/ar-KW/ar-LB/ar-QA/ar-SYโislamic-civilar-DZ/ar-MA/ar-TNโislamicar-OM/ar-SA/ar-SD/ar-YEโislamic-umalqurafa/fa-AF/fa-IR/ckb-IRโpersianps/ps-AFโpersianmzn/mzn-IR/lrc/lrc-IRโpersianuz-Arab/uz-Arab-AFโpersianhi-IN/en-IN/hiโindianhe-IL/heโhebrew- Plugin-managed:
am-ET/ti-ETโethiopic,ar-EGโcoptic,ja/ja-JPโjapanese,zh-TW/zh-Hant-TWโroc,th/th-THโbuddhist
HxDateTimeFormatCalendar supports all 18 ECMA-402 calendar values:
type HxDateTimeFormatCalendar =
| 'gregory' | 'buddhist' | 'chinese' | 'coptic' | 'dangi'
| 'ethioaa' | 'ethiopic' | 'hebrew' | 'indian'
| 'islamic' | 'islamic-civil' | 'islamic-umalqura'
| 'islamic-tbla' | 'islamic-rgsa'
| 'iso8601' | 'japanese' | 'persian' | 'roc'
| string; // extensible for plugin-defined calendars
Custom Calendar Plugins
Implement the DateLocaleNotGregorianProvider interface to add custom calendar support:
import type { DateLocaleNotGregorianProvider } from '@hx/components';
import { DateLocaleFormatUtils } from '@hx/components';
const myPlugin: DateLocaleNotGregorianProvider = {
accept(lang) { return lang === 'my-LOCALE'; },
calendar() { return 'dangi'; },
supportedLanguages() { return ['my-LOCALE']; },
eraAs(date, partsOf, lang) { /* custom era formatting */ },
yearAs(date, partsOf, lang) { /* custom year formatting */ },
yearHeaderLabel(value, era, year, lang) { /* custom year label */ },
monthHeaderLabel(value, era, year, month, lang) { /* custom month label */ },
eraOfDays(days, lang) { /* era markers per day */ },
};
DateLocaleFormatUtils.enableNotGregorianLocaleProvider(myPlugin);
DateLocaleFormatUtils.disableNotGregorianLocaleProvider(myPlugin);
For calendar navigation, additionally register a move provider implementing DateMoveNotGregorianProvider:
import { DateMoveUtils, type DateMoveNotGregorianProvider } from '@hx/components';
const myMovePlugin: DateMoveNotGregorianProvider = {
accept(lang) { return lang === 'my-LOCALE'; },
moveYear(date, yearOffset, lang) { /* ... */ },
moveMonth(date, monthOffset, lang) { /* ... */ },
isPreviousYearAllowed(lang, firstDayOfCurrentMonthOfGregory) { /* ... */ },
// isNextYearAllowed / isPreviousMonthAllowed / isNextMonthAllowed ...
};
DateMoveUtils.enableNotGregorianMoveProvider(myMovePlugin);
DateLocaleFormatUtils.getWeekInfo() reads locale-aware weekend and first-day-of-week from CLDR data:
const { weekends, firstDayOfWeek } = DateLocaleFormatUtils.getWeekInfo('ar-SA');
// weekends: ['fri', 'sat'], firstDayOfWeek: 'sat'
const { weekends, firstDayOfWeek } = DateLocaleFormatUtils.getWeekInfo('en-US');
// weekends: ['sat', 'sun'], firstDayOfWeek: 'sun'
When gregorian is true, all formatting uses the Gregorian calendar. When gregorian is false, the locale-specific calendar from CALENDAR_MAP is used, falling back to Gregorian if no mapping exists.