@charcoal-ui/icons 6.0.0-beta.0 → 6.0.0-beta.2

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/dist/index.cjs CHANGED
@@ -183,12 +183,58 @@ function resolveIconLoader(name) {
183
183
  const __SERVER__ = typeof window === "undefined";
184
184
  if (__SERVER__ || !(typeof HTMLElement !== "undefined")) globalThis.HTMLElement = class {};
185
185
 
186
+ //#endregion
187
+ //#region src/calcActualSize.ts
188
+ const isPositiveFinite = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
189
+ const parseIconName = (name) => {
190
+ if (!name.includes("/")) throw new TypeError(`"${name}" is not a valid icon name. "name" must be named like [size]/[Name].`);
191
+ const [size] = name.split("/");
192
+ if (size === "Inline") return {
193
+ size,
194
+ baseSize: 16
195
+ };
196
+ const baseSize = parseInt(size, 10);
197
+ if (Number.isNaN(baseSize) || baseSize <= 0) throw new TypeError(`"${name}" has invalid size prefix "${size}". Must be "Inline" or a positive number.`);
198
+ return {
199
+ size,
200
+ baseSize
201
+ };
202
+ };
203
+ function inlineSize(scale) {
204
+ switch (scale) {
205
+ case 2: return 32;
206
+ default: return 16;
207
+ }
208
+ }
209
+ function guidelineSize24(scale) {
210
+ return 24 * scale;
211
+ }
212
+ const resolveSize = ({ name, scale, unsafeNonGuidelineScale, fixedSize }) => {
213
+ if (isPositiveFinite(fixedSize)) return fixedSize;
214
+ if (fixedSize !== void 0) throw new TypeError(`fixedSize must be a positive finite number, got ${fixedSize}`);
215
+ const { size, baseSize } = parseIconName(name);
216
+ if (isPositiveFinite(unsafeNonGuidelineScale)) return baseSize * unsafeNonGuidelineScale;
217
+ if (unsafeNonGuidelineScale !== void 0) throw new TypeError(`unsafeNonGuidelineScale must be a positive finite number, got ${unsafeNonGuidelineScale}`);
218
+ const numericScale = parseInt(`${scale ?? "1"}`, 10);
219
+ switch (size) {
220
+ case "Inline": return inlineSize(numericScale);
221
+ case "24": return guidelineSize24(numericScale);
222
+ default: return baseSize;
223
+ }
224
+ };
225
+ const calcActualSize = (params) => {
226
+ const actualSize = resolveSize(params);
227
+ if (!isPositiveFinite(actualSize)) throw new TypeError(`icon size must be a positive finite number, got ${actualSize}`);
228
+ return actualSize;
229
+ };
230
+
186
231
  //#endregion
187
232
  //#region src/PixivIcon.ts
188
233
  const attributes = [
189
234
  "name",
190
235
  "scale",
191
- "unsafe-non-guideline-scale"
236
+ "unsafe-non-guideline-scale",
237
+ "fixed-size"
192
238
  ];
193
239
  const ROOT_MARGIN = 50;
194
240
  var PixivIcon = class extends HTMLElement {
@@ -224,27 +270,6 @@ var PixivIcon = class extends HTMLElement {
224
270
  name
225
271
  };
226
272
  }
227
- get forceResizedSize() {
228
- if (this.props["unsafe-non-guideline-scale"] === null) return null;
229
- const [size] = this.props.name.split("/");
230
- const scale = Number(this.props["unsafe-non-guideline-scale"]);
231
- switch (size) {
232
- case "Inline": return 16 * scale;
233
- default: return Number(size) * scale;
234
- }
235
- }
236
- get scaledSize() {
237
- const [size] = this.props.name.split("/");
238
- const scale = Number(this.props.scale ?? "1");
239
- switch (size) {
240
- case "Inline": switch (scale) {
241
- case 2: return 32;
242
- default: return 16;
243
- }
244
- case "24": return Number(size) * scale;
245
- default: return Number(size);
246
- }
247
- }
248
273
  constructor() {
249
274
  super();
250
275
  this.attachShadow({ mode: "open" });
@@ -273,17 +298,22 @@ var PixivIcon = class extends HTMLElement {
273
298
  this.loadSvg(this.props.name);
274
299
  }
275
300
  render() {
276
- const size = this.forceResizedSize ?? this.scaledSize;
277
- if (!Number.isFinite(size)) throw new TypeError(`icon size must not be NaN`);
301
+ const { name, scale, ...rest } = this.props;
302
+ const unsafeNonGuidelineScale = rest["unsafe-non-guideline-scale"];
303
+ const fixedSize = rest["fixed-size"];
304
+ const size = calcActualSize({
305
+ name,
306
+ ...fixedSize !== null ? { fixedSize: parseFloat(fixedSize) } : unsafeNonGuidelineScale !== null ? { unsafeNonGuidelineScale: parseFloat(unsafeNonGuidelineScale) } : { scale: scale ?? void 0 }
307
+ });
308
+ this.style.setProperty("--charcoal-icon-size", `${size}px`);
278
309
  const style = `<style>
279
310
  :host {
280
311
  display: inline-flex;
281
- --size: ${size}px;
282
312
  }
283
313
 
284
314
  svg {
285
- width: var(--size);
286
- height: var(--size);
315
+ width: var(--charcoal-icon-size);
316
+ height: var(--charcoal-icon-size);
287
317
  }
288
318
  </style>`;
289
319
  const svg = this.svgContent !== void 0 ? this.svgContent : `<svg viewBox="0 0 ${size} ${size}"></svg>`;
@@ -320,4 +350,5 @@ if (!__SERVER__) {
320
350
  exports.KNOWN_ICON_FILES = KNOWN_ICON_FILES;
321
351
  exports.PixivIcon = PixivIcon;
322
352
  exports.PixivIconLoadError = PixivIconLoadError;
353
+ exports.calcActualSize = calcActualSize;
323
354
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["charcoalIconFiles","charcoalIconFiles"],"sources":["../src/charcoalIconFiles.ts","../src/loaders/PixivIconLoadError.ts","../src/loaders/CharcoalIconFilesLoader.ts","../src/loaders/CustomRawFileLoader.ts","../src/loaders/CustomIconLoader.ts","../src/loaders/index.ts","../src/ssr.ts","../src/PixivIcon.ts","../src/index.ts"],"sourcesContent":["import charcoalIconFiles from '@charcoal-ui/icon-files'\nimport type charcoalIconFilesV2 from '@charcoal-ui/icon-files/v2'\n\nexport default charcoalIconFiles\nexport type KnownIconFile = keyof typeof charcoalIconFiles\nexport type KnownIconFileV2 = keyof typeof charcoalIconFilesV2\nexport const KNOWN_ICON_FILES = Object.keys(\n charcoalIconFiles,\n) as KnownIconFile[]\n\nexport function isKnownIconFile(name: string): name is KnownIconFile {\n return name in charcoalIconFiles\n}\n","export class PixivIconLoadError extends Error {\n constructor(name: string, cause: unknown) {\n const message = formatMessage(name, cause)\n\n super(message, { cause })\n this.name = 'PixivIconLoadError'\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\nfunction formatMessage(name: string, cause: unknown) {\n const message = `Failed to fetch <pixiv-icon name=\"${name}\">`\n if (cause == null) {\n return message\n }\n\n if (cause instanceof Error) {\n return `${message}: ${cause.toString()})`\n }\n\n return `${message}: ${JSON.stringify(cause)})`\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\nimport charcoalIconFiles, { KnownIconFile } from '../charcoalIconFiles'\n\n/**\n * `@charcoal-ui/icon-files` に収録されているアイコンを取ってくる\n */\nexport class CharcoalIconFilesLoader implements Loadable {\n protected _name: KnownIconFile\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: KnownIconFile) {\n this._name = name\n }\n\n get importIconFile(): () => Promise<string> {\n return charcoalIconFiles[this._name]\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = this.importIconFile()\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { KnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\n\nexport class CustomRawFileLoader extends CharcoalIconFilesLoader {\n /**\n * icons-filesと同じ型のアイコンをしまっとくところ\n */\n static filePackages: Map<string, () => Promise<string>> = new Map()\n\n get importIconFile(): () => Promise<string> {\n const icon = CustomRawFileLoader.filePackages.get(this._name)\n if (icon !== undefined) return icon\n\n throw new Error('Custom icon file was not found')\n }\n}\n\nexport function addRawFile(\n name: string,\n importFn: () => Promise<string>,\n): void {\n CustomRawFileLoader.filePackages.set(name, importFn)\n}\n\n/**\n * 登録されているfile packagesにiconがあればtrue\n */\nexport function isKnownRawIconFile(name: string): name is KnownIconFile {\n return CustomRawFileLoader.filePackages.has(name)\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\n\n/**\n * `PixivIcon.extend()` で登録されたカスタムのアイコンを取得する\n */\nexport class CustomIconLoader implements Loadable {\n private _name: string\n private _filePathOrUrl: string\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: string, filePathOrUrl: string) {\n this._name = name\n this._filePathOrUrl = filePathOrUrl\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = fetch(this._filePathOrUrl)\n .then((response) => {\n if (!response.ok) {\n throw new PixivIconLoadError(this._name, 'Response is not ok')\n }\n\n return response.text()\n })\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { isKnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\nimport { CustomRawFileLoader, isKnownRawIconFile } from './CustomRawFileLoader'\nimport { CustomIconLoader } from './CustomIconLoader'\nimport { Loadable } from './Loadable'\nimport { PixivIconLoadError } from './PixivIconLoadError'\n\n/**\n * icon をロードするオブジェクトのプール。Loader のインスタンスは作り次第ここに入れる\n *\n * 同じアイコンへの複数回のリクエストが起きないためには、Loader がこの中でユニークでないと行けない\n */\nconst loaders = new Map<string, Loadable>()\n\nexport function addCustomIcon(name: string, filePathOrUrl: string): void {\n loaders.set(name, new CustomIconLoader(name, filePathOrUrl))\n}\n\nexport async function getIcon(name: string): Promise<string> {\n const loader = resolveIconLoader(name)\n if (loader == null) {\n throw new PixivIconLoadError(name, 'Loader was not found')\n }\n\n try {\n const svg = await loader.fetch()\n if (typeof svg !== 'string') {\n // eslint-disable-next-line no-console\n console.warn(\n `${name}: Expected load result to be a string, but received an unexpected type.`,\n )\n }\n\n return svg\n } catch (e) {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(name, e)\n }\n}\n\nfunction resolveIconLoader(name: string) {\n // 登録済み or キャッシュ済みの場合\n // addCustomIcon で登録された CustomIconLoader は常にこっちを通る\n const registeredLoader = loaders.get(name)\n if (registeredLoader) {\n return registeredLoader\n }\n\n // addRawFile で登録されたもの\n if (isKnownRawIconFile(name)) {\n const customFilePackageLoader = new CustomRawFileLoader(name)\n loaders.set(name, customFilePackageLoader)\n return customFilePackageLoader\n }\n\n if (isKnownIconFile(name)) {\n // `@charcoal-ui/icon-files` に収録されているアイコンにはこれを返す\n const charcoalIconFilesLoader = new CharcoalIconFilesLoader(name)\n loaders.set(name, charcoalIconFilesLoader)\n return charcoalIconFilesLoader\n }\n\n // 存在しないアイコンにはこれを返す\n return null\n}\n","export const __SERVER__: boolean = typeof window === 'undefined'\n\nconst CAN_USE_DOM = typeof HTMLElement !== 'undefined'\n\n// Workaround: `extends HTMLElement` の形式でないとbabelのトランスパイルがおかしくなる\nif (__SERVER__ || !CAN_USE_DOM) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n globalThis.HTMLElement = class {} as any\n}\n","import type React from 'react'\nimport { KnownIconFile } from './charcoalIconFiles'\nimport { getIcon, addCustomIcon } from './loaders'\nimport { addRawFile } from './loaders/CustomRawFileLoader'\nimport { __SERVER__ } from './ssr'\n\nconst attributes = ['name', 'scale', 'unsafe-non-guideline-scale'] as const\n\nconst ROOT_MARGIN = 50\n\nexport interface KnownIconType extends Record<KnownIconFile, unknown> {}\n\nexport interface Props\n extends Omit<\n React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>,\n 'className' | 'css'\n > {\n name: keyof KnownIconType\n scale?: 1 | 2 | 3 | '1' | '2' | '3'\n 'unsafe-non-guideline-scale'?: number | string\n\n // CustomElements は className が使えない。class と書く必要がある\n // https://ja.reactjs.org/docs/web-components.html#using-web-components-in-react\n class?: string\n}\n\ntype ExtendedIconFile = Exclude<keyof KnownIconType, KnownIconFile>\ntype Extended = [ExtendedIconFile] extends [never] // NOTE: ExtendedIconFileがneverならKnownIconTypeは拡張されていない\n ? false\n : true\n\nexport class PixivIcon extends HTMLElement {\n static readonly tagName = 'pixiv-icon'\n\n /**\n * NOTE: icon content should be sanitized before pass to extend()\n *\n * XSSに注意すること。\n * 登録したファイルの中身が直接domに反映されるため、XSSに繋がる可能性があります。\n * 信用していないソースからアイコンを追加する場合dom-purifyなどを経由してください。\n */\n static extend(\n map: Extended extends true\n ? Record<ExtendedIconFile, string | (() => Promise<string>)>\n : Record<string, string | (() => Promise<string>)>,\n ): void {\n if (__SERVER__) {\n return\n }\n\n Object.entries(map).forEach(([name, filePathOrUrlOrImportFn]) => {\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n if (typeof filePathOrUrlOrImportFn === 'string') {\n addCustomIcon(name, filePathOrUrlOrImportFn)\n }\n\n if (typeof filePathOrUrlOrImportFn === 'function') {\n addRawFile(name, filePathOrUrlOrImportFn)\n }\n })\n }\n\n static get observedAttributes(): typeof attributes {\n return attributes\n }\n\n private svgContent?: string\n private observer?: IntersectionObserver\n private isVisible = false\n\n get props(): {\n name: string\n scale: string | null\n 'unsafe-non-guideline-scale': string | null\n } {\n const partial = Object.fromEntries(\n attributes.map((attribute) => [attribute, this.getAttribute(attribute)]),\n ) as Record<(typeof attributes)[number], string | null>\n\n const name = partial.name\n\n if (name === null) {\n throw new TypeError('property \"name\" is required.')\n }\n\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n return {\n ...partial,\n name,\n }\n }\n\n get forceResizedSize(): number | null {\n if (this.props['unsafe-non-guideline-scale'] === null) {\n return null\n }\n\n const [size] = this.props.name.split('/')\n const scale = Number(this.props['unsafe-non-guideline-scale'])\n\n switch (size) {\n case 'Inline': {\n return 16 * scale\n }\n\n default: {\n return Number(size) * scale\n }\n }\n }\n\n get scaledSize(): number {\n const [size] = this.props.name.split('/')\n\n const scale = Number(this.props.scale ?? '1')\n\n switch (size) {\n case 'Inline': {\n switch (scale) {\n case 2: {\n return 32\n }\n\n default: {\n return 16\n }\n }\n }\n\n case '24': {\n return Number(size) * scale\n }\n\n default: {\n return Number(size)\n }\n }\n }\n\n constructor() {\n super()\n this.attachShadow({ mode: 'open' })\n }\n\n async connectedCallback(): Promise<void> {\n this.render()\n await this.waitUntilVisible()\n this.isVisible = true\n await this.loadSvg(this.props.name)\n }\n\n disconnectedCallback(): void {\n this.observer?.disconnect()\n this.observer = undefined\n this.isVisible = false\n }\n\n attributeChangedCallback(\n attr: string,\n _oldValue: string | null,\n newValue: string,\n ): void {\n // 非表示の場合はロードしない\n if (!this.isVisible) {\n return\n }\n\n // name が変更された場合必ず再読み込みを試みる\n if (attr === 'name') {\n void this.loadSvg(newValue)\n return\n }\n\n // SVG が読み込み済み && scale などの変更だけならそこだけ反映すればいい\n if (this.svgContent !== undefined) {\n this.render()\n return\n }\n\n // まだ SVG が読み込めてないなら load\n void this.loadSvg(this.props.name)\n }\n\n render(): void {\n const size = this.forceResizedSize ?? this.scaledSize\n\n if (!Number.isFinite(size)) {\n throw new TypeError(`icon size must not be NaN`)\n }\n\n const style = `<style>\n :host {\n display: inline-flex;\n --size: ${size}px;\n }\n\n svg {\n width: var(--size);\n height: var(--size);\n }\n</style>`\n\n const svg =\n this.svgContent !== undefined\n ? this.svgContent\n : `<svg viewBox=\"0 0 ${size} ${size}\"></svg>`\n\n // NOTE: User should sanitize the svg content before passing to charcoal.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.shadowRoot!.innerHTML = style + svg\n }\n\n private async loadSvg(name: string) {\n this.svgContent = await getIcon(name)\n this.render()\n }\n\n private waitUntilVisible() {\n return new Promise<void>((resolve) => {\n this.observer = new IntersectionObserver(\n (entries) => {\n // In Chromium based browsers, multiple entries can be returned even only observe once.\n // Here, we don't care about the entry time but only if isIntersecting happened.\n const isIntersecting = entries.some((entry) => entry.isIntersecting)\n if (isIntersecting) {\n this.observer?.disconnect()\n this.observer = undefined\n resolve()\n }\n },\n { rootMargin: `${ROOT_MARGIN}px` },\n )\n\n this.observer.observe(this)\n })\n }\n}\n","import { PixivIcon } from './PixivIcon'\nimport { __SERVER__ } from './ssr'\nexport { PixivIcon, type KnownIconType, type Props } from './PixivIcon'\nexport { KNOWN_ICON_FILES } from './charcoalIconFiles'\nexport { PixivIconLoadError } from './loaders/PixivIconLoadError'\n\ndeclare global {\n interface Window {\n PixivIcon: typeof PixivIcon\n }\n}\n\ndeclare module 'react' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'pixiv-icon': import('./PixivIcon').Props\n }\n }\n}\n\nif (!__SERVER__) {\n // TODO: HMR対応\n if (!window.customElements.get(PixivIcon.tagName)) {\n window.PixivIcon = PixivIcon\n window.customElements.define(PixivIcon.tagName, PixivIcon)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["charcoalIconFiles","charcoalIconFiles"],"sources":["../src/charcoalIconFiles.ts","../src/loaders/PixivIconLoadError.ts","../src/loaders/CharcoalIconFilesLoader.ts","../src/loaders/CustomRawFileLoader.ts","../src/loaders/CustomIconLoader.ts","../src/loaders/index.ts","../src/ssr.ts","../src/calcActualSize.ts","../src/PixivIcon.ts","../src/index.ts"],"sourcesContent":["import charcoalIconFiles from '@charcoal-ui/icon-files'\nimport type charcoalIconFilesV2 from '@charcoal-ui/icon-files/v2'\n\nexport default charcoalIconFiles\nexport type KnownIconFile = keyof typeof charcoalIconFiles\nexport type KnownIconFileV2 = keyof typeof charcoalIconFilesV2\nexport const KNOWN_ICON_FILES = Object.keys(\n charcoalIconFiles,\n) as KnownIconFile[]\n\nexport function isKnownIconFile(name: string): name is KnownIconFile {\n return name in charcoalIconFiles\n}\n","export class PixivIconLoadError extends Error {\n constructor(name: string, cause: unknown) {\n const message = formatMessage(name, cause)\n\n super(message, { cause })\n this.name = 'PixivIconLoadError'\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\nfunction formatMessage(name: string, cause: unknown) {\n const message = `Failed to fetch <pixiv-icon name=\"${name}\">`\n if (cause == null) {\n return message\n }\n\n if (cause instanceof Error) {\n return `${message}: ${cause.toString()})`\n }\n\n return `${message}: ${JSON.stringify(cause)})`\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\nimport charcoalIconFiles, { KnownIconFile } from '../charcoalIconFiles'\n\n/**\n * `@charcoal-ui/icon-files` に収録されているアイコンを取ってくる\n */\nexport class CharcoalIconFilesLoader implements Loadable {\n protected _name: KnownIconFile\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: KnownIconFile) {\n this._name = name\n }\n\n get importIconFile(): () => Promise<string> {\n return charcoalIconFiles[this._name]\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = this.importIconFile()\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { KnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\n\nexport class CustomRawFileLoader extends CharcoalIconFilesLoader {\n /**\n * icons-filesと同じ型のアイコンをしまっとくところ\n */\n static filePackages: Map<string, () => Promise<string>> = new Map()\n\n get importIconFile(): () => Promise<string> {\n const icon = CustomRawFileLoader.filePackages.get(this._name)\n if (icon !== undefined) return icon\n\n throw new Error('Custom icon file was not found')\n }\n}\n\nexport function addRawFile(\n name: string,\n importFn: () => Promise<string>,\n): void {\n CustomRawFileLoader.filePackages.set(name, importFn)\n}\n\n/**\n * 登録されているfile packagesにiconがあればtrue\n */\nexport function isKnownRawIconFile(name: string): name is KnownIconFile {\n return CustomRawFileLoader.filePackages.has(name)\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\n\n/**\n * `PixivIcon.extend()` で登録されたカスタムのアイコンを取得する\n */\nexport class CustomIconLoader implements Loadable {\n private _name: string\n private _filePathOrUrl: string\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: string, filePathOrUrl: string) {\n this._name = name\n this._filePathOrUrl = filePathOrUrl\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = fetch(this._filePathOrUrl)\n .then((response) => {\n if (!response.ok) {\n throw new PixivIconLoadError(this._name, 'Response is not ok')\n }\n\n return response.text()\n })\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { isKnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\nimport { CustomRawFileLoader, isKnownRawIconFile } from './CustomRawFileLoader'\nimport { CustomIconLoader } from './CustomIconLoader'\nimport { Loadable } from './Loadable'\nimport { PixivIconLoadError } from './PixivIconLoadError'\n\n/**\n * icon をロードするオブジェクトのプール。Loader のインスタンスは作り次第ここに入れる\n *\n * 同じアイコンへの複数回のリクエストが起きないためには、Loader がこの中でユニークでないと行けない\n */\nconst loaders = new Map<string, Loadable>()\n\nexport function addCustomIcon(name: string, filePathOrUrl: string): void {\n loaders.set(name, new CustomIconLoader(name, filePathOrUrl))\n}\n\nexport async function getIcon(name: string): Promise<string> {\n const loader = resolveIconLoader(name)\n if (loader == null) {\n throw new PixivIconLoadError(name, 'Loader was not found')\n }\n\n try {\n const svg = await loader.fetch()\n if (typeof svg !== 'string') {\n // eslint-disable-next-line no-console\n console.warn(\n `${name}: Expected load result to be a string, but received an unexpected type.`,\n )\n }\n\n return svg\n } catch (e) {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(name, e)\n }\n}\n\nfunction resolveIconLoader(name: string) {\n // 登録済み or キャッシュ済みの場合\n // addCustomIcon で登録された CustomIconLoader は常にこっちを通る\n const registeredLoader = loaders.get(name)\n if (registeredLoader) {\n return registeredLoader\n }\n\n // addRawFile で登録されたもの\n if (isKnownRawIconFile(name)) {\n const customFilePackageLoader = new CustomRawFileLoader(name)\n loaders.set(name, customFilePackageLoader)\n return customFilePackageLoader\n }\n\n if (isKnownIconFile(name)) {\n // `@charcoal-ui/icon-files` に収録されているアイコンにはこれを返す\n const charcoalIconFilesLoader = new CharcoalIconFilesLoader(name)\n loaders.set(name, charcoalIconFilesLoader)\n return charcoalIconFilesLoader\n }\n\n // 存在しないアイコンにはこれを返す\n return null\n}\n","export const __SERVER__: boolean = typeof window === 'undefined'\n\nconst CAN_USE_DOM = typeof HTMLElement !== 'undefined'\n\n// Workaround: `extends HTMLElement` の形式でないとbabelのトランスパイルがおかしくなる\nif (__SERVER__ || !CAN_USE_DOM) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n globalThis.HTMLElement = class {} as any\n}\n","export type IconSizing =\n | {\n scale?: 1 | 2 | 3 | '1' | '2' | '3'\n /** @deprecated `fixedSize` を利用してください。 */\n unsafeNonGuidelineScale?: never\n fixedSize?: never\n }\n | {\n scale?: never\n /** @deprecated `fixedSize` を利用してください。 */\n unsafeNonGuidelineScale: number\n fixedSize?: never\n }\n | {\n scale?: never\n /** @deprecated `fixedSize` を利用してください。 */\n unsafeNonGuidelineScale?: never\n fixedSize: number\n }\n\nconst isPositiveFinite = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0\n\nconst parseIconName = (name: string): { size: string; baseSize: number } => {\n if (!name.includes('/')) {\n throw new TypeError(\n `\"${name}\" is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n const [size] = name.split('/')\n\n if (size === 'Inline') {\n return { size, baseSize: 16 }\n }\n\n const baseSize = parseInt(size, 10)\n if (Number.isNaN(baseSize) || baseSize <= 0) {\n throw new TypeError(\n `\"${name}\" has invalid size prefix \"${size}\". Must be \"Inline\" or a positive number.`,\n )\n }\n\n return { size, baseSize }\n}\n\nfunction inlineSize(scale: number): number {\n switch (scale) {\n case 2:\n return 32\n default:\n return 16\n }\n}\n\nfunction guidelineSize24(scale: number): number {\n return 24 * scale\n}\n\n// fixedSize > unsafeNonGuidelineScale > scale の優先順位で生のサイズを算出する。\n// 戻り値の最終 validation は呼び出し元 (calcActualSize) で行う。\nconst resolveSize = ({\n name,\n scale,\n unsafeNonGuidelineScale,\n fixedSize,\n}: { name: string } & IconSizing): number => {\n // fixedSize (px 直接指定) が最優先\n if (isPositiveFinite(fixedSize)) {\n return fixedSize\n }\n if (fixedSize !== undefined) {\n throw new TypeError(\n `fixedSize must be a positive finite number, got ${fixedSize}`,\n )\n }\n\n const { size, baseSize } = parseIconName(name)\n\n // unsafeNonGuidelineScale (deprecated) が次に優先\n if (isPositiveFinite(unsafeNonGuidelineScale)) {\n return baseSize * unsafeNonGuidelineScale\n }\n if (unsafeNonGuidelineScale !== undefined) {\n throw new TypeError(\n `unsafeNonGuidelineScale must be a positive finite number, got ${unsafeNonGuidelineScale}`,\n )\n }\n\n // ガイドライン scale\n const numericScale = parseInt(`${scale ?? '1'}`, 10)\n switch (size) {\n case 'Inline':\n return inlineSize(numericScale)\n case '24':\n return guidelineSize24(numericScale)\n default:\n return baseSize\n }\n}\n\nexport const calcActualSize = (\n params: { name: string } & IconSizing,\n): number => {\n const actualSize = resolveSize(params)\n\n // 全 return パスの結果が正の有限数であることを Single Source of Truth として保証する\n if (!isPositiveFinite(actualSize)) {\n throw new TypeError(\n `icon size must be a positive finite number, got ${actualSize}`,\n )\n }\n\n return actualSize\n}\n","import type React from 'react'\nimport { KnownIconFile } from './charcoalIconFiles'\nimport { getIcon, addCustomIcon } from './loaders'\nimport { addRawFile } from './loaders/CustomRawFileLoader'\nimport { __SERVER__ } from './ssr'\nimport { calcActualSize } from './calcActualSize'\n\nconst attributes = [\n 'name',\n 'scale',\n 'unsafe-non-guideline-scale',\n 'fixed-size',\n] as const\n\nconst ROOT_MARGIN = 50\n\nexport interface KnownIconType extends Record<KnownIconFile, unknown> {}\n\nexport interface Props\n extends Omit<\n React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>,\n 'className' | 'css'\n > {\n name: keyof KnownIconType\n scale?: 1 | 2 | 3 | '1' | '2' | '3'\n /**\n * @deprecated `fixed-size` を利用してください。\n * `attr()` の数値解釈サポートが限定的なため、リセット CSS だけではレイアウトシフトが防げません。\n */\n 'unsafe-non-guideline-scale'?: number | string\n /**\n * 固定 px サイズで描画します。ガイドライン外のサイズを利用する場合に推奨される指定方法で、\n * 他のサイズ指定 (`scale` / `unsafe-non-guideline-scale`) よりも常に優先されます。\n *\n * Web Component の upgrade 前 (= SSR 直後の CSS-only 状態) でも CLS を防ぐには、\n * 同じ値を `style=\"--charcoal-icon-size: Npx\"` としてインラインに指定してください。\n * React の `<Icon fixedSize>` 経由で利用する場合はインラインスタイルが自動的に付与されます。\n */\n 'fixed-size'?: number | string\n\n // CustomElements は className が使えない。class と書く必要がある\n // https://ja.reactjs.org/docs/web-components.html#using-web-components-in-react\n class?: string\n}\n\ntype ExtendedIconFile = Exclude<keyof KnownIconType, KnownIconFile>\ntype Extended = [ExtendedIconFile] extends [never] // NOTE: ExtendedIconFileがneverならKnownIconTypeは拡張されていない\n ? false\n : true\n\nexport class PixivIcon extends HTMLElement {\n static readonly tagName = 'pixiv-icon'\n\n /**\n * NOTE: icon content should be sanitized before pass to extend()\n *\n * XSSに注意すること。\n * 登録したファイルの中身が直接domに反映されるため、XSSに繋がる可能性があります。\n * 信用していないソースからアイコンを追加する場合dom-purifyなどを経由してください。\n */\n static extend(\n map: Extended extends true\n ? Record<ExtendedIconFile, string | (() => Promise<string>)>\n : Record<string, string | (() => Promise<string>)>,\n ): void {\n if (__SERVER__) {\n return\n }\n\n Object.entries(map).forEach(([name, filePathOrUrlOrImportFn]) => {\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n if (typeof filePathOrUrlOrImportFn === 'string') {\n addCustomIcon(name, filePathOrUrlOrImportFn)\n }\n\n if (typeof filePathOrUrlOrImportFn === 'function') {\n addRawFile(name, filePathOrUrlOrImportFn)\n }\n })\n }\n\n static get observedAttributes(): typeof attributes {\n return attributes\n }\n\n private svgContent?: string\n private observer?: IntersectionObserver\n private isVisible = false\n\n get props(): {\n name: string\n scale: string | null\n 'unsafe-non-guideline-scale': string | null\n 'fixed-size': string | null\n } {\n const partial = Object.fromEntries(\n attributes.map((attribute) => [attribute, this.getAttribute(attribute)]),\n ) as Record<(typeof attributes)[number], string | null>\n\n const name = partial.name\n\n if (name === null) {\n throw new TypeError('property \"name\" is required.')\n }\n\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n return {\n ...partial,\n name,\n }\n }\n\n constructor() {\n super()\n this.attachShadow({ mode: 'open' })\n }\n\n async connectedCallback(): Promise<void> {\n this.render()\n await this.waitUntilVisible()\n this.isVisible = true\n await this.loadSvg(this.props.name)\n }\n\n disconnectedCallback(): void {\n this.observer?.disconnect()\n this.observer = undefined\n this.isVisible = false\n }\n\n attributeChangedCallback(\n attr: string,\n _oldValue: string | null,\n newValue: string,\n ): void {\n // 非表示の場合はロードしない\n if (!this.isVisible) {\n return\n }\n\n // name が変更された場合必ず再読み込みを試みる\n if (attr === 'name') {\n void this.loadSvg(newValue)\n return\n }\n\n // SVG が読み込み済み && scale などの変更だけならそこだけ反映すればいい\n if (this.svgContent !== undefined) {\n this.render()\n return\n }\n\n // まだ SVG が読み込めてないなら load\n void this.loadSvg(this.props.name)\n }\n\n render(): void {\n const { name, scale, ...rest } = this.props\n const unsafeNonGuidelineScale = rest['unsafe-non-guideline-scale']\n const fixedSize = rest['fixed-size']\n\n const size = calcActualSize({\n name,\n ...(fixedSize !== null\n ? { fixedSize: parseFloat(fixedSize) }\n : unsafeNonGuidelineScale !== null\n ? { unsafeNonGuidelineScale: parseFloat(unsafeNonGuidelineScale) }\n : { scale: scale ?? undefined }),\n } as Parameters<typeof calcActualSize>[0])\n\n // icon.css の pixiv-icon:not(:defined) ルールと同じ CSS variable に\n // 計算結果を流し込むことで、hydrate 前後で width / height が揺れないようにする\n this.style.setProperty('--charcoal-icon-size', `${size}px`)\n\n const style = `<style>\n :host {\n display: inline-flex;\n }\n\n svg {\n width: var(--charcoal-icon-size);\n height: var(--charcoal-icon-size);\n }\n</style>`\n\n const svg =\n this.svgContent !== undefined\n ? this.svgContent\n : `<svg viewBox=\"0 0 ${size} ${size}\"></svg>`\n\n // NOTE: User should sanitize the svg content before passing to charcoal.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.shadowRoot!.innerHTML = style + svg\n }\n\n private async loadSvg(name: string) {\n this.svgContent = await getIcon(name)\n this.render()\n }\n\n private waitUntilVisible() {\n return new Promise<void>((resolve) => {\n this.observer = new IntersectionObserver(\n (entries) => {\n // In Chromium based browsers, multiple entries can be returned even only observe once.\n // Here, we don't care about the entry time but only if isIntersecting happened.\n const isIntersecting = entries.some((entry) => entry.isIntersecting)\n if (isIntersecting) {\n this.observer?.disconnect()\n this.observer = undefined\n resolve()\n }\n },\n { rootMargin: `${ROOT_MARGIN}px` },\n )\n\n this.observer.observe(this)\n })\n }\n}\n","import { PixivIcon } from './PixivIcon'\nimport { __SERVER__ } from './ssr'\nexport { PixivIcon, type KnownIconType, type Props } from './PixivIcon'\nexport { calcActualSize, type IconSizing } from './calcActualSize'\nexport { KNOWN_ICON_FILES } from './charcoalIconFiles'\nexport { PixivIconLoadError } from './loaders/PixivIconLoadError'\n\ndeclare global {\n interface Window {\n PixivIcon: typeof PixivIcon\n }\n}\n\ndeclare module 'react' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'pixiv-icon': import('./PixivIcon').Props\n }\n }\n}\n\nif (!__SERVER__) {\n // TODO: HMR対応\n if (!window.customElements.get(PixivIcon.tagName)) {\n window.PixivIcon = PixivIcon\n window.customElements.define(PixivIcon.tagName, PixivIcon)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,gCAAeA;AAGf,MAAa,mBAAmB,OAAO,KACrCA,gCACD;AAED,SAAgB,gBAAgB,MAAqC;CACnE,OAAO,QAAQA;;;;;ACXjB,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,MAAc,OAAgB;EACxC,MAAM,UAAU,cAAc,MAAM,MAAM;EAE1C,MAAM,SAAS,EAAE,OAAO,CAAC;EACzB,KAAK,OAAO;EACZ,OAAO,eAAe,MAAM,IAAI,OAAO,UAAU;;;AAIrD,SAAS,cAAc,MAAc,OAAgB;CACnD,MAAM,UAAU,qCAAqC,KAAK;CAC1D,IAAI,SAAS,MACX,OAAO;CAGT,IAAI,iBAAiB,OACnB,OAAO,GAAG,QAAQ,IAAI,MAAM,UAAU,CAAC;CAGzC,OAAO,GAAG,QAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;;;;;;;;ACb9C,IAAa,0BAAb,MAAyD;CACvD,AAAU;CACV,AAAQ,aAAiC;CACzC,AAAQ,WAAwC;CAEhD,YAAY,MAAqB;EAC/B,KAAK,QAAQ;;CAGf,IAAI,iBAAwC;EAC1C,OAAOC,0BAAkB,KAAK;;CAGhC,MAAM,QAAyB;EAC7B,IAAI,KAAK,eAAe,QACtB,OAAO,KAAK;EAGd,IAAI,KAAK,UACP,OAAO,KAAK;EAGd,KAAK,WAAW,KAAK,gBAAgB,CAClC,MAAM,QAAQ;GACb,KAAK,aAAa;GAClB,OAAO,KAAK;IACZ,CACD,OAAO,MAAM;GACZ,MAAM,IAAI,mBAAmB,KAAK,OAAO,EAAE;IAC3C,CACD,cAAc;GACb,KAAK,WAAW;IAChB;EAEJ,OAAO,KAAK;;;;;;ACtChB,IAAa,sBAAb,MAAa,4BAA4B,wBAAwB;;;;CAI/D,OAAO,+BAAmD,IAAI,KAAK;CAEnE,IAAI,iBAAwC;EAC1C,MAAM,OAAO,oBAAoB,aAAa,IAAI,KAAK,MAAM;EAC7D,IAAI,SAAS,QAAW,OAAO;EAE/B,MAAM,IAAI,MAAM,iCAAiC;;;AAIrD,SAAgB,WACd,MACA,UACM;CACN,oBAAoB,aAAa,IAAI,MAAM,SAAS;;;;;AAMtD,SAAgB,mBAAmB,MAAqC;CACtE,OAAO,oBAAoB,aAAa,IAAI,KAAK;;;;;;;;ACtBnD,IAAa,mBAAb,MAAkD;CAChD,AAAQ;CACR,AAAQ;CACR,AAAQ,aAAiC;CACzC,AAAQ,WAAwC;CAEhD,YAAY,MAAc,eAAuB;EAC/C,KAAK,QAAQ;EACb,KAAK,iBAAiB;;CAGxB,MAAM,QAAyB;EAC7B,IAAI,KAAK,eAAe,QACtB,OAAO,KAAK;EAGd,IAAI,KAAK,UACP,OAAO,KAAK;EAGd,KAAK,WAAW,MAAM,KAAK,eAAe,CACvC,MAAM,aAAa;GAClB,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,mBAAmB,KAAK,OAAO,qBAAqB;GAGhE,OAAO,SAAS,MAAM;IACtB,CACD,MAAM,QAAQ;GACb,KAAK,aAAa;GAClB,OAAO,KAAK;IACZ,CACD,OAAO,MAAM;GACZ,IAAI,aAAa,oBACf,MAAM;GAER,MAAM,IAAI,mBAAmB,KAAK,OAAO,EAAE;IAC3C,CACD,cAAc;GACb,KAAK,WAAW;IAChB;EAEJ,OAAO,KAAK;;;;;;;;;;;ACpChB,MAAM,0BAAU,IAAI,KAAuB;AAE3C,SAAgB,cAAc,MAAc,eAA6B;CACvE,QAAQ,IAAI,MAAM,IAAI,iBAAiB,MAAM,cAAc,CAAC;;AAG9D,eAAsB,QAAQ,MAA+B;CAC3D,MAAM,SAAS,kBAAkB,KAAK;CACtC,IAAI,UAAU,MACZ,MAAM,IAAI,mBAAmB,MAAM,uBAAuB;CAG5D,IAAI;EACF,MAAM,MAAM,MAAM,OAAO,OAAO;EAChC,IAAI,OAAO,QAAQ,UAEjB,QAAQ,KACN,GAAG,KAAK,yEACT;EAGH,OAAO;UACA,GAAG;EACV,IAAI,aAAa,oBACf,MAAM;EAER,MAAM,IAAI,mBAAmB,MAAM,EAAE;;;AAIzC,SAAS,kBAAkB,MAAc;CAGvC,MAAM,mBAAmB,QAAQ,IAAI,KAAK;CAC1C,IAAI,kBACF,OAAO;CAIT,IAAI,mBAAmB,KAAK,EAAE;EAC5B,MAAM,0BAA0B,IAAI,oBAAoB,KAAK;EAC7D,QAAQ,IAAI,MAAM,wBAAwB;EAC1C,OAAO;;CAGT,IAAI,gBAAgB,KAAK,EAAE;EAEzB,MAAM,0BAA0B,IAAI,wBAAwB,KAAK;EACjE,QAAQ,IAAI,MAAM,wBAAwB;EAC1C,OAAO;;CAIT,OAAO;;;;;ACjET,MAAa,aAAsB,OAAO,WAAW;AAKrD,IAAI,cAAc,EAHE,OAAO,gBAAgB,cAKzC,WAAW,cAAc,MAAM;;;;ACajC,MAAM,oBAAoB,UACxB,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,IAAI,QAAQ;AAEjE,MAAM,iBAAiB,SAAqD;CAC1E,IAAI,CAAC,KAAK,SAAS,IAAI,EACrB,MAAM,IAAI,UACR,IAAI,KAAK,sEACV;CAGH,MAAM,CAAC,QAAQ,KAAK,MAAM,IAAI;CAE9B,IAAI,SAAS,UACX,OAAO;EAAE;EAAM,UAAU;EAAI;CAG/B,MAAM,WAAW,SAAS,MAAM,GAAG;CACnC,IAAI,OAAO,MAAM,SAAS,IAAI,YAAY,GACxC,MAAM,IAAI,UACR,IAAI,KAAK,6BAA6B,KAAK,2CAC5C;CAGH,OAAO;EAAE;EAAM;EAAU;;AAG3B,SAAS,WAAW,OAAuB;CACzC,QAAQ,OAAR;EACE,KAAK,GACH,OAAO;EACT,SACE,OAAO;;;AAIb,SAAS,gBAAgB,OAAuB;CAC9C,OAAO,KAAK;;AAKd,MAAM,eAAe,EACnB,MACA,OACA,yBACA,gBAC2C;CAE3C,IAAI,iBAAiB,UAAU,EAC7B,OAAO;CAET,IAAI,cAAc,QAChB,MAAM,IAAI,UACR,mDAAmD,YACpD;CAGH,MAAM,EAAE,MAAM,aAAa,cAAc,KAAK;CAG9C,IAAI,iBAAiB,wBAAwB,EAC3C,OAAO,WAAW;CAEpB,IAAI,4BAA4B,QAC9B,MAAM,IAAI,UACR,iEAAiE,0BAClE;CAIH,MAAM,eAAe,SAAS,GAAG,SAAS,OAAO,GAAG;CACpD,QAAQ,MAAR;EACE,KAAK,UACH,OAAO,WAAW,aAAa;EACjC,KAAK,MACH,OAAO,gBAAgB,aAAa;EACtC,SACE,OAAO;;;AAIb,MAAa,kBACX,WACW;CACX,MAAM,aAAa,YAAY,OAAO;CAGtC,IAAI,CAAC,iBAAiB,WAAW,EAC/B,MAAM,IAAI,UACR,mDAAmD,aACpD;CAGH,OAAO;;;;;AC1GT,MAAM,aAAa;CACjB;CACA;CACA;CACA;CACD;AAED,MAAM,cAAc;AAoCpB,IAAa,YAAb,cAA+B,YAAY;CACzC,OAAgB,UAAU;;;;;;;;CAS1B,OAAO,OACL,KAGM;EACN,IAAI,YACF;EAGF,OAAO,QAAQ,IAAI,CAAC,SAAS,CAAC,MAAM,6BAA6B;GAC/D,IAAI,CAAC,KAAK,SAAS,IAAI,EACrB,MAAM,IAAI,UACR,GAAG,KAAK,qEACT;GAGH,IAAI,OAAO,4BAA4B,UACrC,cAAc,MAAM,wBAAwB;GAG9C,IAAI,OAAO,4BAA4B,YACrC,WAAW,MAAM,wBAAwB;IAE3C;;CAGJ,WAAW,qBAAwC;EACjD,OAAO;;CAGT,AAAQ;CACR,AAAQ;CACR,AAAQ,YAAY;CAEpB,IAAI,QAKF;EACA,MAAM,UAAU,OAAO,YACrB,WAAW,KAAK,cAAc,CAAC,WAAW,KAAK,aAAa,UAAU,CAAC,CAAC,CACzE;EAED,MAAM,OAAO,QAAQ;EAErB,IAAI,SAAS,MACX,MAAM,IAAI,UAAU,iCAA+B;EAGrD,IAAI,CAAC,KAAK,SAAS,IAAI,EACrB,MAAM,IAAI,UACR,GAAG,KAAK,qEACT;EAGH,OAAO;GACL,GAAG;GACH;GACD;;CAGH,cAAc;EACZ,OAAO;EACP,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;;CAGrC,MAAM,oBAAmC;EACvC,KAAK,QAAQ;EACb,MAAM,KAAK,kBAAkB;EAC7B,KAAK,YAAY;EACjB,MAAM,KAAK,QAAQ,KAAK,MAAM,KAAK;;CAGrC,uBAA6B;EAC3B,KAAK,UAAU,YAAY;EAC3B,KAAK,WAAW;EAChB,KAAK,YAAY;;CAGnB,yBACE,MACA,WACA,UACM;EAEN,IAAI,CAAC,KAAK,WACR;EAIF,IAAI,SAAS,QAAQ;GACnB,AAAK,KAAK,QAAQ,SAAS;GAC3B;;EAIF,IAAI,KAAK,eAAe,QAAW;GACjC,KAAK,QAAQ;GACb;;EAIF,AAAK,KAAK,QAAQ,KAAK,MAAM,KAAK;;CAGpC,SAAe;EACb,MAAM,EAAE,MAAM,OAAO,GAAG,SAAS,KAAK;EACtC,MAAM,0BAA0B,KAAK;EACrC,MAAM,YAAY,KAAK;EAEvB,MAAM,OAAO,eAAe;GAC1B;GACA,GAAI,cAAc,OACd,EAAE,WAAW,WAAW,UAAU,EAAE,GACpC,4BAA4B,OAC1B,EAAE,yBAAyB,WAAW,wBAAwB,EAAE,GAChE,EAAE,OAAO,SAAS,QAAW;GACpC,CAAyC;EAI1C,KAAK,MAAM,YAAY,wBAAwB,GAAG,KAAK,IAAI;EAE3D,MAAM,QAAQ;;;;;;;;;;EAWd,MAAM,MACJ,KAAK,eAAe,SAChB,KAAK,aACL,qBAAqB,KAAK,GAAG,KAAK;EAIxC,KAAK,WAAY,YAAY,QAAQ;;CAGvC,MAAc,QAAQ,MAAc;EAClC,KAAK,aAAa,MAAM,QAAQ,KAAK;EACrC,KAAK,QAAQ;;CAGf,AAAQ,mBAAmB;EACzB,OAAO,IAAI,SAAe,YAAY;GACpC,KAAK,WAAW,IAAI,sBACjB,YAAY;IAIX,IADuB,QAAQ,MAAM,UAAU,MAAM,eACnC,EAAE;KAClB,KAAK,UAAU,YAAY;KAC3B,KAAK,WAAW;KAChB,SAAS;;MAGb,EAAE,YAAY,GAAG,YAAY,KAAK,CACnC;GAED,KAAK,SAAS,QAAQ,KAAK;IAC3B;;;;;;AC7MN,IAAI,CAAC,YAEH;KAAI,CAAC,OAAO,eAAe,IAAI,UAAU,QAAQ,EAAE;EACjD,OAAO,YAAY;EACnB,OAAO,eAAe,OAAO,UAAU,SAAS,UAAU"}
package/dist/index.d.cts CHANGED
@@ -5,12 +5,25 @@ type KnownIconFile = keyof typeof charcoalIconFiles;
5
5
  declare const KNOWN_ICON_FILES: KnownIconFile[];
6
6
  //#endregion
7
7
  //#region src/PixivIcon.d.ts
8
- declare const attributes: readonly ["name", "scale", "unsafe-non-guideline-scale"];
8
+ declare const attributes: readonly ["name", "scale", "unsafe-non-guideline-scale", "fixed-size"];
9
9
  interface KnownIconType extends Record<KnownIconFile, unknown> {}
10
10
  interface Props extends Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "className" | "css"> {
11
11
  name: keyof KnownIconType;
12
12
  scale?: 1 | 2 | 3 | "1" | "2" | "3";
13
+ /**
14
+ * @deprecated `fixed-size` を利用してください。
15
+ * `attr()` の数値解釈サポートが限定的なため、リセット CSS だけではレイアウトシフトが防げません。
16
+ */
13
17
  "unsafe-non-guideline-scale"?: number | string;
18
+ /**
19
+ * 固定 px サイズで描画します。ガイドライン外のサイズを利用する場合に推奨される指定方法で、
20
+ * 他のサイズ指定 (`scale` / `unsafe-non-guideline-scale`) よりも常に優先されます。
21
+ *
22
+ * Web Component の upgrade 前 (= SSR 直後の CSS-only 状態) でも CLS を防ぐには、
23
+ * 同じ値を `style="--charcoal-icon-size: Npx"` としてインラインに指定してください。
24
+ * React の `<Icon fixedSize>` 経由で利用する場合はインラインスタイルが自動的に付与されます。
25
+ */
26
+ "fixed-size"?: number | string;
14
27
  class?: string;
15
28
  }
16
29
  type ExtendedIconFile = Exclude<keyof KnownIconType, KnownIconFile>;
@@ -33,9 +46,8 @@ declare class PixivIcon extends HTMLElement {
33
46
  name: string;
34
47
  scale: string | null;
35
48
  "unsafe-non-guideline-scale": string | null;
49
+ "fixed-size": string | null;
36
50
  };
37
- get forceResizedSize(): number | null;
38
- get scaledSize(): number;
39
51
  constructor();
40
52
  connectedCallback(): Promise<void>;
41
53
  disconnectedCallback(): void;
@@ -45,6 +57,24 @@ declare class PixivIcon extends HTMLElement {
45
57
  private waitUntilVisible;
46
58
  }
47
59
  //#endregion
60
+ //#region src/calcActualSize.d.ts
61
+ type IconSizing = {
62
+ scale?: 1 | 2 | 3 | "1" | "2" | "3"; /** @deprecated `fixedSize` を利用してください。 */
63
+ unsafeNonGuidelineScale?: never;
64
+ fixedSize?: never;
65
+ } | {
66
+ scale?: never; /** @deprecated `fixedSize` を利用してください。 */
67
+ unsafeNonGuidelineScale: number;
68
+ fixedSize?: never;
69
+ } | {
70
+ scale?: never; /** @deprecated `fixedSize` を利用してください。 */
71
+ unsafeNonGuidelineScale?: never;
72
+ fixedSize: number;
73
+ };
74
+ declare const calcActualSize: (params: {
75
+ name: string;
76
+ } & IconSizing) => number;
77
+ //#endregion
48
78
  //#region src/loaders/PixivIconLoadError.d.ts
49
79
  declare class PixivIconLoadError extends Error {
50
80
  constructor(name: string, cause: unknown);
@@ -64,5 +94,5 @@ declare module "react" {
64
94
  }
65
95
  }
66
96
  //#endregion
67
- export { KNOWN_ICON_FILES, type KnownIconType, PixivIcon, PixivIconLoadError, type Props };
97
+ export { type IconSizing, KNOWN_ICON_FILES, type KnownIconType, PixivIcon, PixivIconLoadError, type Props, calcActualSize };
68
98
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/charcoalIconFiles.ts","../src/PixivIcon.ts","../src/loaders/PixivIconLoadError.ts","../src/index.ts"],"mappings":";;;KAIY,aAAA,gBAA6B,iBAAA;AAAA,cAE5B,gBAAA,EAER,aAAA;;;cCFC,UAAA;AAAA,UAIW,aAAA,SAAsB,MAAA,CAAO,aAAA;AAAA,UAE7B,KAAA,SACP,IAAA,CACN,KAAA,CAAM,iBAAA,CAAkB,KAAA,CAAM,cAAA,CAAe,WAAA,GAAc,WAAA;EAG7D,IAAA,QAAY,aAAA;EACZ,KAAA;EACA,4BAAA;EAIA,KAAA;AAAA;AAAA,KAGG,gBAAA,GAAmB,OAAA,OAAc,aAAA,EAAe,aAAA;AAAA,KAChD,QAAA,IAAY,gBAAA;AAAA,cAIJ,SAAA,SAAkB,WAAA;EAAA,gBACb,OAAA;EDxBb;;;;ACPyB;;;EDOzB,OCiCI,MAAA,CACL,GAAA,EAAK,QAAA,gBACD,MAAA,CAAO,gBAAA,kBAAkC,OAAA,aACzC,MAAA,yBAA+B,OAAA;EAAA,WAuB1B,kBAAA,CAAA,UAA6B,UAAA;EAAA,QAIhC,UAAA;EAAA,QACA,QAAA;EAAA,QACA,SAAA;EAAA,IAEJ,KAAA,CAAA;IACF,IAAA;IACA,KAAA;IACA,4BAAA;EAAA;EAAA,IAwBE,gBAAA,CAAA;EAAA,IAmBA,UAAA,CAAA;EA4BJ,WAAA,CAAA;EAKA,iBAAA,CAAA,GAA2B,OAAA;EAO3B,oBAAA,CAAA;EAMA,wBAAA,CACE,IAAA,UACA,SAAA,iBACA,QAAA;EAuBF,MAAA,CAAA;EAAA,QA6Bc,OAAA;EAAA,QAKN,gBAAA;AAAA;;;cCnOG,kBAAA,SAA2B,KAAA;EACtC,WAAA,CAAY,IAAA,UAAc,KAAA;AAAA;;;;YCMhB,MAAA;IACR,SAAA,SAAkB,SAAA;EAAA;AAAA;AAAA;EAAA,UAMH,GAAA;IAAA,UACL,iBAAA;MACR,YAAA,EARgB,KAAA;IAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/charcoalIconFiles.ts","../src/PixivIcon.ts","../src/calcActualSize.ts","../src/loaders/PixivIconLoadError.ts","../src/index.ts"],"mappings":";;;KAIY,aAAA,gBAA6B,iBAAA;AAAA,cAE5B,gBAAA,EAER,aAAA;;;cCDC,UAAA;AAAA,UASW,aAAA,SAAsB,MAAA,CAAO,aAAA;AAAA,UAE7B,KAAA,SACP,IAAA,CACN,KAAA,CAAM,iBAAA,CAAkB,KAAA,CAAM,cAAA,CAAe,WAAA,GAAc,WAAA;EAG7D,IAAA,QAAY,aAAA;EACZ,KAAA;;;;ADlBF;ECuBE,4BAAA;;;;;;;AA5B4B;;EAqC5B,YAAA;EAIA,KAAA;AAAA;AAAA,KAGG,gBAAA,GAAmB,OAAA,OAAc,aAAA,EAAe,aAAA;AAAA,KAChD,QAAA,IAAY,gBAAA;AAAA,cAIJ,SAAA,SAAkB,WAAA;EAAA,gBACb,OAAA;EAnC4B;AAE9C;;;;;;EAF8C,OA4CrC,MAAA,CACL,GAAA,EAAK,QAAA,gBACD,MAAA,CAAO,gBAAA,kBAAkC,OAAA,aACzC,MAAA,yBAA+B,OAAA;EAAA,WAuB1B,kBAAA,CAAA,UAA6B,UAAA;EAAA,QAIhC,UAAA;EAAA,QACA,QAAA;EAAA,QACA,SAAA;EAAA,IAEJ,KAAA,CAAA;IACF,IAAA;IACA,KAAA;IACA,4BAAA;IACA,YAAA;EAAA;EAwBF,WAAA,CAAA;EAKA,iBAAA,CAAA,GAA2B,OAAA;EAO3B,oBAAA,CAAA;EAMA,wBAAA,CACE,IAAA,UACA,SAAA,iBACA,QAAA;EAuBF,MAAA,CAAA;EAAA,QAuCc,OAAA;EAAA,QAKN,gBAAA;AAAA;;;KClNE,UAAA;EAEN,KAAA;EAEA,uBAAA;EACA,SAAA;AAAA;EAGA,KAAA;EAEA,uBAAA;EACA,SAAA;AAAA;EAGA,KAAA;EAEA,uBAAA;EACA,SAAA;AAAA;AAAA,cAoFO,cAAA,GACX,MAAA;EAAU,IAAA;AAAA,IAAiB,UAAA;;;cCtGhB,kBAAA,SAA2B,KAAA;EACtC,WAAA,CAAY,IAAA,UAAc,KAAA;AAAA;;;;YCOhB,MAAA;IACR,SAAA,SAAkB,SAAA;EAAA;AAAA;AAAA;EAAA,UAMH,GAAA;IAAA,UACL,iBAAA;MACR,YAAA,EARgB,KAAA;IAAA;EAAA;AAAA"}
package/dist/index.d.ts CHANGED
@@ -5,12 +5,25 @@ type KnownIconFile = keyof typeof charcoalIconFiles$1;
5
5
  declare const KNOWN_ICON_FILES: KnownIconFile[];
6
6
  //#endregion
7
7
  //#region src/PixivIcon.d.ts
8
- declare const attributes: readonly ["name", "scale", "unsafe-non-guideline-scale"];
8
+ declare const attributes: readonly ["name", "scale", "unsafe-non-guideline-scale", "fixed-size"];
9
9
  interface KnownIconType extends Record<KnownIconFile, unknown> {}
10
10
  interface Props extends Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "className" | "css"> {
11
11
  name: keyof KnownIconType;
12
12
  scale?: 1 | 2 | 3 | "1" | "2" | "3";
13
+ /**
14
+ * @deprecated `fixed-size` を利用してください。
15
+ * `attr()` の数値解釈サポートが限定的なため、リセット CSS だけではレイアウトシフトが防げません。
16
+ */
13
17
  "unsafe-non-guideline-scale"?: number | string;
18
+ /**
19
+ * 固定 px サイズで描画します。ガイドライン外のサイズを利用する場合に推奨される指定方法で、
20
+ * 他のサイズ指定 (`scale` / `unsafe-non-guideline-scale`) よりも常に優先されます。
21
+ *
22
+ * Web Component の upgrade 前 (= SSR 直後の CSS-only 状態) でも CLS を防ぐには、
23
+ * 同じ値を `style="--charcoal-icon-size: Npx"` としてインラインに指定してください。
24
+ * React の `<Icon fixedSize>` 経由で利用する場合はインラインスタイルが自動的に付与されます。
25
+ */
26
+ "fixed-size"?: number | string;
14
27
  class?: string;
15
28
  }
16
29
  type ExtendedIconFile = Exclude<keyof KnownIconType, KnownIconFile>;
@@ -33,9 +46,8 @@ declare class PixivIcon extends HTMLElement {
33
46
  name: string;
34
47
  scale: string | null;
35
48
  "unsafe-non-guideline-scale": string | null;
49
+ "fixed-size": string | null;
36
50
  };
37
- get forceResizedSize(): number | null;
38
- get scaledSize(): number;
39
51
  constructor();
40
52
  connectedCallback(): Promise<void>;
41
53
  disconnectedCallback(): void;
@@ -45,6 +57,24 @@ declare class PixivIcon extends HTMLElement {
45
57
  private waitUntilVisible;
46
58
  }
47
59
  //#endregion
60
+ //#region src/calcActualSize.d.ts
61
+ type IconSizing = {
62
+ scale?: 1 | 2 | 3 | "1" | "2" | "3"; /** @deprecated `fixedSize` を利用してください。 */
63
+ unsafeNonGuidelineScale?: never;
64
+ fixedSize?: never;
65
+ } | {
66
+ scale?: never; /** @deprecated `fixedSize` を利用してください。 */
67
+ unsafeNonGuidelineScale: number;
68
+ fixedSize?: never;
69
+ } | {
70
+ scale?: never; /** @deprecated `fixedSize` を利用してください。 */
71
+ unsafeNonGuidelineScale?: never;
72
+ fixedSize: number;
73
+ };
74
+ declare const calcActualSize: (params: {
75
+ name: string;
76
+ } & IconSizing) => number;
77
+ //#endregion
48
78
  //#region src/loaders/PixivIconLoadError.d.ts
49
79
  declare class PixivIconLoadError extends Error {
50
80
  constructor(name: string, cause: unknown);
@@ -64,5 +94,5 @@ declare module "react" {
64
94
  }
65
95
  }
66
96
  //#endregion
67
- export { KNOWN_ICON_FILES, type KnownIconType, PixivIcon, PixivIconLoadError, type Props };
97
+ export { type IconSizing, KNOWN_ICON_FILES, type KnownIconType, PixivIcon, PixivIconLoadError, type Props, calcActualSize };
68
98
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/charcoalIconFiles.ts","../src/PixivIcon.ts","../src/loaders/PixivIconLoadError.ts","../src/index.ts"],"mappings":";;;KAIY,aAAA,gBAA6B,mBAAA;AAAA,cAE5B,gBAAA,EAER,aAAA;;;cCFC,UAAA;AAAA,UAIW,aAAA,SAAsB,MAAA,CAAO,aAAA;AAAA,UAE7B,KAAA,SACP,IAAA,CACN,KAAA,CAAM,iBAAA,CAAkB,KAAA,CAAM,cAAA,CAAe,WAAA,GAAc,WAAA;EAG7D,IAAA,QAAY,aAAA;EACZ,KAAA;EACA,4BAAA;EAIA,KAAA;AAAA;AAAA,KAGG,gBAAA,GAAmB,OAAA,OAAc,aAAA,EAAe,aAAA;AAAA,KAChD,QAAA,IAAY,gBAAA;AAAA,cAIJ,SAAA,SAAkB,WAAA;EAAA,gBACb,OAAA;EDxBb;;;;ACPyB;;;EDOzB,OCiCI,MAAA,CACL,GAAA,EAAK,QAAA,gBACD,MAAA,CAAO,gBAAA,kBAAkC,OAAA,aACzC,MAAA,yBAA+B,OAAA;EAAA,WAuB1B,kBAAA,CAAA,UAA6B,UAAA;EAAA,QAIhC,UAAA;EAAA,QACA,QAAA;EAAA,QACA,SAAA;EAAA,IAEJ,KAAA,CAAA;IACF,IAAA;IACA,KAAA;IACA,4BAAA;EAAA;EAAA,IAwBE,gBAAA,CAAA;EAAA,IAmBA,UAAA,CAAA;EA4BJ,WAAA,CAAA;EAKA,iBAAA,CAAA,GAA2B,OAAA;EAO3B,oBAAA,CAAA;EAMA,wBAAA,CACE,IAAA,UACA,SAAA,iBACA,QAAA;EAuBF,MAAA,CAAA;EAAA,QA6Bc,OAAA;EAAA,QAKN,gBAAA;AAAA;;;cCnOG,kBAAA,SAA2B,KAAA;EACtC,WAAA,CAAY,IAAA,UAAc,KAAA;AAAA;;;;YCMhB,MAAA;IACR,SAAA,SAAkB,SAAA;EAAA;AAAA;AAAA;EAAA,UAMH,GAAA;IAAA,UACL,iBAAA;MACR,YAAA,EARgB,KAAA;IAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/charcoalIconFiles.ts","../src/PixivIcon.ts","../src/calcActualSize.ts","../src/loaders/PixivIconLoadError.ts","../src/index.ts"],"mappings":";;;KAIY,aAAA,gBAA6B,mBAAA;AAAA,cAE5B,gBAAA,EAER,aAAA;;;cCDC,UAAA;AAAA,UASW,aAAA,SAAsB,MAAA,CAAO,aAAA;AAAA,UAE7B,KAAA,SACP,IAAA,CACN,KAAA,CAAM,iBAAA,CAAkB,KAAA,CAAM,cAAA,CAAe,WAAA,GAAc,WAAA;EAG7D,IAAA,QAAY,aAAA;EACZ,KAAA;;;;ADlBF;ECuBE,4BAAA;;;;;;;AA5B4B;;EAqC5B,YAAA;EAIA,KAAA;AAAA;AAAA,KAGG,gBAAA,GAAmB,OAAA,OAAc,aAAA,EAAe,aAAA;AAAA,KAChD,QAAA,IAAY,gBAAA;AAAA,cAIJ,SAAA,SAAkB,WAAA;EAAA,gBACb,OAAA;EAnC4B;AAE9C;;;;;;EAF8C,OA4CrC,MAAA,CACL,GAAA,EAAK,QAAA,gBACD,MAAA,CAAO,gBAAA,kBAAkC,OAAA,aACzC,MAAA,yBAA+B,OAAA;EAAA,WAuB1B,kBAAA,CAAA,UAA6B,UAAA;EAAA,QAIhC,UAAA;EAAA,QACA,QAAA;EAAA,QACA,SAAA;EAAA,IAEJ,KAAA,CAAA;IACF,IAAA;IACA,KAAA;IACA,4BAAA;IACA,YAAA;EAAA;EAwBF,WAAA,CAAA;EAKA,iBAAA,CAAA,GAA2B,OAAA;EAO3B,oBAAA,CAAA;EAMA,wBAAA,CACE,IAAA,UACA,SAAA,iBACA,QAAA;EAuBF,MAAA,CAAA;EAAA,QAuCc,OAAA;EAAA,QAKN,gBAAA;AAAA;;;KClNE,UAAA;EAEN,KAAA;EAEA,uBAAA;EACA,SAAA;AAAA;EAGA,KAAA;EAEA,uBAAA;EACA,SAAA;AAAA;EAGA,KAAA;EAEA,uBAAA;EACA,SAAA;AAAA;AAAA,cAoFO,cAAA,GACX,MAAA;EAAU,IAAA;AAAA,IAAiB,UAAA;;;cCtGhB,kBAAA,SAA2B,KAAA;EACtC,WAAA,CAAY,IAAA,UAAc,KAAA;AAAA;;;;YCOhB,MAAA;IACR,SAAA,SAAkB,SAAA;EAAA;AAAA;AAAA;EAAA,UAMH,GAAA;IAAA,UACL,iBAAA;MACR,YAAA,EARgB,KAAA;IAAA;EAAA;AAAA"}
package/dist/index.js CHANGED
@@ -154,12 +154,58 @@ function resolveIconLoader(name) {
154
154
  const __SERVER__ = typeof window === "undefined";
155
155
  if (__SERVER__ || !(typeof HTMLElement !== "undefined")) globalThis.HTMLElement = class {};
156
156
 
157
+ //#endregion
158
+ //#region src/calcActualSize.ts
159
+ const isPositiveFinite = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
160
+ const parseIconName = (name) => {
161
+ if (!name.includes("/")) throw new TypeError(`"${name}" is not a valid icon name. "name" must be named like [size]/[Name].`);
162
+ const [size] = name.split("/");
163
+ if (size === "Inline") return {
164
+ size,
165
+ baseSize: 16
166
+ };
167
+ const baseSize = parseInt(size, 10);
168
+ if (Number.isNaN(baseSize) || baseSize <= 0) throw new TypeError(`"${name}" has invalid size prefix "${size}". Must be "Inline" or a positive number.`);
169
+ return {
170
+ size,
171
+ baseSize
172
+ };
173
+ };
174
+ function inlineSize(scale) {
175
+ switch (scale) {
176
+ case 2: return 32;
177
+ default: return 16;
178
+ }
179
+ }
180
+ function guidelineSize24(scale) {
181
+ return 24 * scale;
182
+ }
183
+ const resolveSize = ({ name, scale, unsafeNonGuidelineScale, fixedSize }) => {
184
+ if (isPositiveFinite(fixedSize)) return fixedSize;
185
+ if (fixedSize !== void 0) throw new TypeError(`fixedSize must be a positive finite number, got ${fixedSize}`);
186
+ const { size, baseSize } = parseIconName(name);
187
+ if (isPositiveFinite(unsafeNonGuidelineScale)) return baseSize * unsafeNonGuidelineScale;
188
+ if (unsafeNonGuidelineScale !== void 0) throw new TypeError(`unsafeNonGuidelineScale must be a positive finite number, got ${unsafeNonGuidelineScale}`);
189
+ const numericScale = parseInt(`${scale ?? "1"}`, 10);
190
+ switch (size) {
191
+ case "Inline": return inlineSize(numericScale);
192
+ case "24": return guidelineSize24(numericScale);
193
+ default: return baseSize;
194
+ }
195
+ };
196
+ const calcActualSize = (params) => {
197
+ const actualSize = resolveSize(params);
198
+ if (!isPositiveFinite(actualSize)) throw new TypeError(`icon size must be a positive finite number, got ${actualSize}`);
199
+ return actualSize;
200
+ };
201
+
157
202
  //#endregion
158
203
  //#region src/PixivIcon.ts
159
204
  const attributes = [
160
205
  "name",
161
206
  "scale",
162
- "unsafe-non-guideline-scale"
207
+ "unsafe-non-guideline-scale",
208
+ "fixed-size"
163
209
  ];
164
210
  const ROOT_MARGIN = 50;
165
211
  var PixivIcon = class extends HTMLElement {
@@ -195,27 +241,6 @@ var PixivIcon = class extends HTMLElement {
195
241
  name
196
242
  };
197
243
  }
198
- get forceResizedSize() {
199
- if (this.props["unsafe-non-guideline-scale"] === null) return null;
200
- const [size] = this.props.name.split("/");
201
- const scale = Number(this.props["unsafe-non-guideline-scale"]);
202
- switch (size) {
203
- case "Inline": return 16 * scale;
204
- default: return Number(size) * scale;
205
- }
206
- }
207
- get scaledSize() {
208
- const [size] = this.props.name.split("/");
209
- const scale = Number(this.props.scale ?? "1");
210
- switch (size) {
211
- case "Inline": switch (scale) {
212
- case 2: return 32;
213
- default: return 16;
214
- }
215
- case "24": return Number(size) * scale;
216
- default: return Number(size);
217
- }
218
- }
219
244
  constructor() {
220
245
  super();
221
246
  this.attachShadow({ mode: "open" });
@@ -244,17 +269,22 @@ var PixivIcon = class extends HTMLElement {
244
269
  this.loadSvg(this.props.name);
245
270
  }
246
271
  render() {
247
- const size = this.forceResizedSize ?? this.scaledSize;
248
- if (!Number.isFinite(size)) throw new TypeError(`icon size must not be NaN`);
272
+ const { name, scale, ...rest } = this.props;
273
+ const unsafeNonGuidelineScale = rest["unsafe-non-guideline-scale"];
274
+ const fixedSize = rest["fixed-size"];
275
+ const size = calcActualSize({
276
+ name,
277
+ ...fixedSize !== null ? { fixedSize: parseFloat(fixedSize) } : unsafeNonGuidelineScale !== null ? { unsafeNonGuidelineScale: parseFloat(unsafeNonGuidelineScale) } : { scale: scale ?? void 0 }
278
+ });
279
+ this.style.setProperty("--charcoal-icon-size", `${size}px`);
249
280
  const style = `<style>
250
281
  :host {
251
282
  display: inline-flex;
252
- --size: ${size}px;
253
283
  }
254
284
 
255
285
  svg {
256
- width: var(--size);
257
- height: var(--size);
286
+ width: var(--charcoal-icon-size);
287
+ height: var(--charcoal-icon-size);
258
288
  }
259
289
  </style>`;
260
290
  const svg = this.svgContent !== void 0 ? this.svgContent : `<svg viewBox="0 0 ${size} ${size}"></svg>`;
@@ -288,5 +318,5 @@ if (!__SERVER__) {
288
318
  }
289
319
 
290
320
  //#endregion
291
- export { KNOWN_ICON_FILES, PixivIcon, PixivIconLoadError };
321
+ export { KNOWN_ICON_FILES, PixivIcon, PixivIconLoadError, calcActualSize };
292
322
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["charcoalIconFiles"],"sources":["../src/charcoalIconFiles.ts","../src/loaders/PixivIconLoadError.ts","../src/loaders/CharcoalIconFilesLoader.ts","../src/loaders/CustomRawFileLoader.ts","../src/loaders/CustomIconLoader.ts","../src/loaders/index.ts","../src/ssr.ts","../src/PixivIcon.ts","../src/index.ts"],"sourcesContent":["import charcoalIconFiles from '@charcoal-ui/icon-files'\nimport type charcoalIconFilesV2 from '@charcoal-ui/icon-files/v2'\n\nexport default charcoalIconFiles\nexport type KnownIconFile = keyof typeof charcoalIconFiles\nexport type KnownIconFileV2 = keyof typeof charcoalIconFilesV2\nexport const KNOWN_ICON_FILES = Object.keys(\n charcoalIconFiles,\n) as KnownIconFile[]\n\nexport function isKnownIconFile(name: string): name is KnownIconFile {\n return name in charcoalIconFiles\n}\n","export class PixivIconLoadError extends Error {\n constructor(name: string, cause: unknown) {\n const message = formatMessage(name, cause)\n\n super(message, { cause })\n this.name = 'PixivIconLoadError'\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\nfunction formatMessage(name: string, cause: unknown) {\n const message = `Failed to fetch <pixiv-icon name=\"${name}\">`\n if (cause == null) {\n return message\n }\n\n if (cause instanceof Error) {\n return `${message}: ${cause.toString()})`\n }\n\n return `${message}: ${JSON.stringify(cause)})`\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\nimport charcoalIconFiles, { KnownIconFile } from '../charcoalIconFiles'\n\n/**\n * `@charcoal-ui/icon-files` に収録されているアイコンを取ってくる\n */\nexport class CharcoalIconFilesLoader implements Loadable {\n protected _name: KnownIconFile\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: KnownIconFile) {\n this._name = name\n }\n\n get importIconFile(): () => Promise<string> {\n return charcoalIconFiles[this._name]\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = this.importIconFile()\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { KnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\n\nexport class CustomRawFileLoader extends CharcoalIconFilesLoader {\n /**\n * icons-filesと同じ型のアイコンをしまっとくところ\n */\n static filePackages: Map<string, () => Promise<string>> = new Map()\n\n get importIconFile(): () => Promise<string> {\n const icon = CustomRawFileLoader.filePackages.get(this._name)\n if (icon !== undefined) return icon\n\n throw new Error('Custom icon file was not found')\n }\n}\n\nexport function addRawFile(\n name: string,\n importFn: () => Promise<string>,\n): void {\n CustomRawFileLoader.filePackages.set(name, importFn)\n}\n\n/**\n * 登録されているfile packagesにiconがあればtrue\n */\nexport function isKnownRawIconFile(name: string): name is KnownIconFile {\n return CustomRawFileLoader.filePackages.has(name)\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\n\n/**\n * `PixivIcon.extend()` で登録されたカスタムのアイコンを取得する\n */\nexport class CustomIconLoader implements Loadable {\n private _name: string\n private _filePathOrUrl: string\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: string, filePathOrUrl: string) {\n this._name = name\n this._filePathOrUrl = filePathOrUrl\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = fetch(this._filePathOrUrl)\n .then((response) => {\n if (!response.ok) {\n throw new PixivIconLoadError(this._name, 'Response is not ok')\n }\n\n return response.text()\n })\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { isKnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\nimport { CustomRawFileLoader, isKnownRawIconFile } from './CustomRawFileLoader'\nimport { CustomIconLoader } from './CustomIconLoader'\nimport { Loadable } from './Loadable'\nimport { PixivIconLoadError } from './PixivIconLoadError'\n\n/**\n * icon をロードするオブジェクトのプール。Loader のインスタンスは作り次第ここに入れる\n *\n * 同じアイコンへの複数回のリクエストが起きないためには、Loader がこの中でユニークでないと行けない\n */\nconst loaders = new Map<string, Loadable>()\n\nexport function addCustomIcon(name: string, filePathOrUrl: string): void {\n loaders.set(name, new CustomIconLoader(name, filePathOrUrl))\n}\n\nexport async function getIcon(name: string): Promise<string> {\n const loader = resolveIconLoader(name)\n if (loader == null) {\n throw new PixivIconLoadError(name, 'Loader was not found')\n }\n\n try {\n const svg = await loader.fetch()\n if (typeof svg !== 'string') {\n // eslint-disable-next-line no-console\n console.warn(\n `${name}: Expected load result to be a string, but received an unexpected type.`,\n )\n }\n\n return svg\n } catch (e) {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(name, e)\n }\n}\n\nfunction resolveIconLoader(name: string) {\n // 登録済み or キャッシュ済みの場合\n // addCustomIcon で登録された CustomIconLoader は常にこっちを通る\n const registeredLoader = loaders.get(name)\n if (registeredLoader) {\n return registeredLoader\n }\n\n // addRawFile で登録されたもの\n if (isKnownRawIconFile(name)) {\n const customFilePackageLoader = new CustomRawFileLoader(name)\n loaders.set(name, customFilePackageLoader)\n return customFilePackageLoader\n }\n\n if (isKnownIconFile(name)) {\n // `@charcoal-ui/icon-files` に収録されているアイコンにはこれを返す\n const charcoalIconFilesLoader = new CharcoalIconFilesLoader(name)\n loaders.set(name, charcoalIconFilesLoader)\n return charcoalIconFilesLoader\n }\n\n // 存在しないアイコンにはこれを返す\n return null\n}\n","export const __SERVER__: boolean = typeof window === 'undefined'\n\nconst CAN_USE_DOM = typeof HTMLElement !== 'undefined'\n\n// Workaround: `extends HTMLElement` の形式でないとbabelのトランスパイルがおかしくなる\nif (__SERVER__ || !CAN_USE_DOM) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n globalThis.HTMLElement = class {} as any\n}\n","import type React from 'react'\nimport { KnownIconFile } from './charcoalIconFiles'\nimport { getIcon, addCustomIcon } from './loaders'\nimport { addRawFile } from './loaders/CustomRawFileLoader'\nimport { __SERVER__ } from './ssr'\n\nconst attributes = ['name', 'scale', 'unsafe-non-guideline-scale'] as const\n\nconst ROOT_MARGIN = 50\n\nexport interface KnownIconType extends Record<KnownIconFile, unknown> {}\n\nexport interface Props\n extends Omit<\n React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>,\n 'className' | 'css'\n > {\n name: keyof KnownIconType\n scale?: 1 | 2 | 3 | '1' | '2' | '3'\n 'unsafe-non-guideline-scale'?: number | string\n\n // CustomElements は className が使えない。class と書く必要がある\n // https://ja.reactjs.org/docs/web-components.html#using-web-components-in-react\n class?: string\n}\n\ntype ExtendedIconFile = Exclude<keyof KnownIconType, KnownIconFile>\ntype Extended = [ExtendedIconFile] extends [never] // NOTE: ExtendedIconFileがneverならKnownIconTypeは拡張されていない\n ? false\n : true\n\nexport class PixivIcon extends HTMLElement {\n static readonly tagName = 'pixiv-icon'\n\n /**\n * NOTE: icon content should be sanitized before pass to extend()\n *\n * XSSに注意すること。\n * 登録したファイルの中身が直接domに反映されるため、XSSに繋がる可能性があります。\n * 信用していないソースからアイコンを追加する場合dom-purifyなどを経由してください。\n */\n static extend(\n map: Extended extends true\n ? Record<ExtendedIconFile, string | (() => Promise<string>)>\n : Record<string, string | (() => Promise<string>)>,\n ): void {\n if (__SERVER__) {\n return\n }\n\n Object.entries(map).forEach(([name, filePathOrUrlOrImportFn]) => {\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n if (typeof filePathOrUrlOrImportFn === 'string') {\n addCustomIcon(name, filePathOrUrlOrImportFn)\n }\n\n if (typeof filePathOrUrlOrImportFn === 'function') {\n addRawFile(name, filePathOrUrlOrImportFn)\n }\n })\n }\n\n static get observedAttributes(): typeof attributes {\n return attributes\n }\n\n private svgContent?: string\n private observer?: IntersectionObserver\n private isVisible = false\n\n get props(): {\n name: string\n scale: string | null\n 'unsafe-non-guideline-scale': string | null\n } {\n const partial = Object.fromEntries(\n attributes.map((attribute) => [attribute, this.getAttribute(attribute)]),\n ) as Record<(typeof attributes)[number], string | null>\n\n const name = partial.name\n\n if (name === null) {\n throw new TypeError('property \"name\" is required.')\n }\n\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n return {\n ...partial,\n name,\n }\n }\n\n get forceResizedSize(): number | null {\n if (this.props['unsafe-non-guideline-scale'] === null) {\n return null\n }\n\n const [size] = this.props.name.split('/')\n const scale = Number(this.props['unsafe-non-guideline-scale'])\n\n switch (size) {\n case 'Inline': {\n return 16 * scale\n }\n\n default: {\n return Number(size) * scale\n }\n }\n }\n\n get scaledSize(): number {\n const [size] = this.props.name.split('/')\n\n const scale = Number(this.props.scale ?? '1')\n\n switch (size) {\n case 'Inline': {\n switch (scale) {\n case 2: {\n return 32\n }\n\n default: {\n return 16\n }\n }\n }\n\n case '24': {\n return Number(size) * scale\n }\n\n default: {\n return Number(size)\n }\n }\n }\n\n constructor() {\n super()\n this.attachShadow({ mode: 'open' })\n }\n\n async connectedCallback(): Promise<void> {\n this.render()\n await this.waitUntilVisible()\n this.isVisible = true\n await this.loadSvg(this.props.name)\n }\n\n disconnectedCallback(): void {\n this.observer?.disconnect()\n this.observer = undefined\n this.isVisible = false\n }\n\n attributeChangedCallback(\n attr: string,\n _oldValue: string | null,\n newValue: string,\n ): void {\n // 非表示の場合はロードしない\n if (!this.isVisible) {\n return\n }\n\n // name が変更された場合必ず再読み込みを試みる\n if (attr === 'name') {\n void this.loadSvg(newValue)\n return\n }\n\n // SVG が読み込み済み && scale などの変更だけならそこだけ反映すればいい\n if (this.svgContent !== undefined) {\n this.render()\n return\n }\n\n // まだ SVG が読み込めてないなら load\n void this.loadSvg(this.props.name)\n }\n\n render(): void {\n const size = this.forceResizedSize ?? this.scaledSize\n\n if (!Number.isFinite(size)) {\n throw new TypeError(`icon size must not be NaN`)\n }\n\n const style = `<style>\n :host {\n display: inline-flex;\n --size: ${size}px;\n }\n\n svg {\n width: var(--size);\n height: var(--size);\n }\n</style>`\n\n const svg =\n this.svgContent !== undefined\n ? this.svgContent\n : `<svg viewBox=\"0 0 ${size} ${size}\"></svg>`\n\n // NOTE: User should sanitize the svg content before passing to charcoal.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.shadowRoot!.innerHTML = style + svg\n }\n\n private async loadSvg(name: string) {\n this.svgContent = await getIcon(name)\n this.render()\n }\n\n private waitUntilVisible() {\n return new Promise<void>((resolve) => {\n this.observer = new IntersectionObserver(\n (entries) => {\n // In Chromium based browsers, multiple entries can be returned even only observe once.\n // Here, we don't care about the entry time but only if isIntersecting happened.\n const isIntersecting = entries.some((entry) => entry.isIntersecting)\n if (isIntersecting) {\n this.observer?.disconnect()\n this.observer = undefined\n resolve()\n }\n },\n { rootMargin: `${ROOT_MARGIN}px` },\n )\n\n this.observer.observe(this)\n })\n }\n}\n","import { PixivIcon } from './PixivIcon'\nimport { __SERVER__ } from './ssr'\nexport { PixivIcon, type KnownIconType, type Props } from './PixivIcon'\nexport { KNOWN_ICON_FILES } from './charcoalIconFiles'\nexport { PixivIconLoadError } from './loaders/PixivIconLoadError'\n\ndeclare global {\n interface Window {\n PixivIcon: typeof PixivIcon\n }\n}\n\ndeclare module 'react' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'pixiv-icon': import('./PixivIcon').Props\n }\n }\n}\n\nif (!__SERVER__) {\n // TODO: HMR対応\n if (!window.customElements.get(PixivIcon.tagName)) {\n window.PixivIcon = PixivIcon\n window.customElements.define(PixivIcon.tagName, PixivIcon)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["charcoalIconFiles"],"sources":["../src/charcoalIconFiles.ts","../src/loaders/PixivIconLoadError.ts","../src/loaders/CharcoalIconFilesLoader.ts","../src/loaders/CustomRawFileLoader.ts","../src/loaders/CustomIconLoader.ts","../src/loaders/index.ts","../src/ssr.ts","../src/calcActualSize.ts","../src/PixivIcon.ts","../src/index.ts"],"sourcesContent":["import charcoalIconFiles from '@charcoal-ui/icon-files'\nimport type charcoalIconFilesV2 from '@charcoal-ui/icon-files/v2'\n\nexport default charcoalIconFiles\nexport type KnownIconFile = keyof typeof charcoalIconFiles\nexport type KnownIconFileV2 = keyof typeof charcoalIconFilesV2\nexport const KNOWN_ICON_FILES = Object.keys(\n charcoalIconFiles,\n) as KnownIconFile[]\n\nexport function isKnownIconFile(name: string): name is KnownIconFile {\n return name in charcoalIconFiles\n}\n","export class PixivIconLoadError extends Error {\n constructor(name: string, cause: unknown) {\n const message = formatMessage(name, cause)\n\n super(message, { cause })\n this.name = 'PixivIconLoadError'\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\nfunction formatMessage(name: string, cause: unknown) {\n const message = `Failed to fetch <pixiv-icon name=\"${name}\">`\n if (cause == null) {\n return message\n }\n\n if (cause instanceof Error) {\n return `${message}: ${cause.toString()})`\n }\n\n return `${message}: ${JSON.stringify(cause)})`\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\nimport charcoalIconFiles, { KnownIconFile } from '../charcoalIconFiles'\n\n/**\n * `@charcoal-ui/icon-files` に収録されているアイコンを取ってくる\n */\nexport class CharcoalIconFilesLoader implements Loadable {\n protected _name: KnownIconFile\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: KnownIconFile) {\n this._name = name\n }\n\n get importIconFile(): () => Promise<string> {\n return charcoalIconFiles[this._name]\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = this.importIconFile()\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { KnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\n\nexport class CustomRawFileLoader extends CharcoalIconFilesLoader {\n /**\n * icons-filesと同じ型のアイコンをしまっとくところ\n */\n static filePackages: Map<string, () => Promise<string>> = new Map()\n\n get importIconFile(): () => Promise<string> {\n const icon = CustomRawFileLoader.filePackages.get(this._name)\n if (icon !== undefined) return icon\n\n throw new Error('Custom icon file was not found')\n }\n}\n\nexport function addRawFile(\n name: string,\n importFn: () => Promise<string>,\n): void {\n CustomRawFileLoader.filePackages.set(name, importFn)\n}\n\n/**\n * 登録されているfile packagesにiconがあればtrue\n */\nexport function isKnownRawIconFile(name: string): name is KnownIconFile {\n return CustomRawFileLoader.filePackages.has(name)\n}\n","import { PixivIconLoadError } from './PixivIconLoadError'\nimport { Loadable } from './Loadable'\n\n/**\n * `PixivIcon.extend()` で登録されたカスタムのアイコンを取得する\n */\nexport class CustomIconLoader implements Loadable {\n private _name: string\n private _filePathOrUrl: string\n private _resultSvg: string | undefined = undefined\n private _promise: Promise<string> | undefined = undefined\n\n constructor(name: string, filePathOrUrl: string) {\n this._name = name\n this._filePathOrUrl = filePathOrUrl\n }\n\n async fetch(): Promise<string> {\n if (this._resultSvg !== undefined) {\n return this._resultSvg\n }\n\n if (this._promise) {\n return this._promise\n }\n\n this._promise = fetch(this._filePathOrUrl)\n .then((response) => {\n if (!response.ok) {\n throw new PixivIconLoadError(this._name, 'Response is not ok')\n }\n\n return response.text()\n })\n .then((svg) => {\n this._resultSvg = svg\n return this._resultSvg\n })\n .catch((e) => {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(this._name, e)\n })\n .finally(() => {\n this._promise = undefined\n })\n\n return this._promise\n }\n}\n","import { isKnownIconFile } from '../charcoalIconFiles'\nimport { CharcoalIconFilesLoader } from './CharcoalIconFilesLoader'\nimport { CustomRawFileLoader, isKnownRawIconFile } from './CustomRawFileLoader'\nimport { CustomIconLoader } from './CustomIconLoader'\nimport { Loadable } from './Loadable'\nimport { PixivIconLoadError } from './PixivIconLoadError'\n\n/**\n * icon をロードするオブジェクトのプール。Loader のインスタンスは作り次第ここに入れる\n *\n * 同じアイコンへの複数回のリクエストが起きないためには、Loader がこの中でユニークでないと行けない\n */\nconst loaders = new Map<string, Loadable>()\n\nexport function addCustomIcon(name: string, filePathOrUrl: string): void {\n loaders.set(name, new CustomIconLoader(name, filePathOrUrl))\n}\n\nexport async function getIcon(name: string): Promise<string> {\n const loader = resolveIconLoader(name)\n if (loader == null) {\n throw new PixivIconLoadError(name, 'Loader was not found')\n }\n\n try {\n const svg = await loader.fetch()\n if (typeof svg !== 'string') {\n // eslint-disable-next-line no-console\n console.warn(\n `${name}: Expected load result to be a string, but received an unexpected type.`,\n )\n }\n\n return svg\n } catch (e) {\n if (e instanceof PixivIconLoadError) {\n throw e\n }\n throw new PixivIconLoadError(name, e)\n }\n}\n\nfunction resolveIconLoader(name: string) {\n // 登録済み or キャッシュ済みの場合\n // addCustomIcon で登録された CustomIconLoader は常にこっちを通る\n const registeredLoader = loaders.get(name)\n if (registeredLoader) {\n return registeredLoader\n }\n\n // addRawFile で登録されたもの\n if (isKnownRawIconFile(name)) {\n const customFilePackageLoader = new CustomRawFileLoader(name)\n loaders.set(name, customFilePackageLoader)\n return customFilePackageLoader\n }\n\n if (isKnownIconFile(name)) {\n // `@charcoal-ui/icon-files` に収録されているアイコンにはこれを返す\n const charcoalIconFilesLoader = new CharcoalIconFilesLoader(name)\n loaders.set(name, charcoalIconFilesLoader)\n return charcoalIconFilesLoader\n }\n\n // 存在しないアイコンにはこれを返す\n return null\n}\n","export const __SERVER__: boolean = typeof window === 'undefined'\n\nconst CAN_USE_DOM = typeof HTMLElement !== 'undefined'\n\n// Workaround: `extends HTMLElement` の形式でないとbabelのトランスパイルがおかしくなる\nif (__SERVER__ || !CAN_USE_DOM) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n globalThis.HTMLElement = class {} as any\n}\n","export type IconSizing =\n | {\n scale?: 1 | 2 | 3 | '1' | '2' | '3'\n /** @deprecated `fixedSize` を利用してください。 */\n unsafeNonGuidelineScale?: never\n fixedSize?: never\n }\n | {\n scale?: never\n /** @deprecated `fixedSize` を利用してください。 */\n unsafeNonGuidelineScale: number\n fixedSize?: never\n }\n | {\n scale?: never\n /** @deprecated `fixedSize` を利用してください。 */\n unsafeNonGuidelineScale?: never\n fixedSize: number\n }\n\nconst isPositiveFinite = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0\n\nconst parseIconName = (name: string): { size: string; baseSize: number } => {\n if (!name.includes('/')) {\n throw new TypeError(\n `\"${name}\" is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n const [size] = name.split('/')\n\n if (size === 'Inline') {\n return { size, baseSize: 16 }\n }\n\n const baseSize = parseInt(size, 10)\n if (Number.isNaN(baseSize) || baseSize <= 0) {\n throw new TypeError(\n `\"${name}\" has invalid size prefix \"${size}\". Must be \"Inline\" or a positive number.`,\n )\n }\n\n return { size, baseSize }\n}\n\nfunction inlineSize(scale: number): number {\n switch (scale) {\n case 2:\n return 32\n default:\n return 16\n }\n}\n\nfunction guidelineSize24(scale: number): number {\n return 24 * scale\n}\n\n// fixedSize > unsafeNonGuidelineScale > scale の優先順位で生のサイズを算出する。\n// 戻り値の最終 validation は呼び出し元 (calcActualSize) で行う。\nconst resolveSize = ({\n name,\n scale,\n unsafeNonGuidelineScale,\n fixedSize,\n}: { name: string } & IconSizing): number => {\n // fixedSize (px 直接指定) が最優先\n if (isPositiveFinite(fixedSize)) {\n return fixedSize\n }\n if (fixedSize !== undefined) {\n throw new TypeError(\n `fixedSize must be a positive finite number, got ${fixedSize}`,\n )\n }\n\n const { size, baseSize } = parseIconName(name)\n\n // unsafeNonGuidelineScale (deprecated) が次に優先\n if (isPositiveFinite(unsafeNonGuidelineScale)) {\n return baseSize * unsafeNonGuidelineScale\n }\n if (unsafeNonGuidelineScale !== undefined) {\n throw new TypeError(\n `unsafeNonGuidelineScale must be a positive finite number, got ${unsafeNonGuidelineScale}`,\n )\n }\n\n // ガイドライン scale\n const numericScale = parseInt(`${scale ?? '1'}`, 10)\n switch (size) {\n case 'Inline':\n return inlineSize(numericScale)\n case '24':\n return guidelineSize24(numericScale)\n default:\n return baseSize\n }\n}\n\nexport const calcActualSize = (\n params: { name: string } & IconSizing,\n): number => {\n const actualSize = resolveSize(params)\n\n // 全 return パスの結果が正の有限数であることを Single Source of Truth として保証する\n if (!isPositiveFinite(actualSize)) {\n throw new TypeError(\n `icon size must be a positive finite number, got ${actualSize}`,\n )\n }\n\n return actualSize\n}\n","import type React from 'react'\nimport { KnownIconFile } from './charcoalIconFiles'\nimport { getIcon, addCustomIcon } from './loaders'\nimport { addRawFile } from './loaders/CustomRawFileLoader'\nimport { __SERVER__ } from './ssr'\nimport { calcActualSize } from './calcActualSize'\n\nconst attributes = [\n 'name',\n 'scale',\n 'unsafe-non-guideline-scale',\n 'fixed-size',\n] as const\n\nconst ROOT_MARGIN = 50\n\nexport interface KnownIconType extends Record<KnownIconFile, unknown> {}\n\nexport interface Props\n extends Omit<\n React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>,\n 'className' | 'css'\n > {\n name: keyof KnownIconType\n scale?: 1 | 2 | 3 | '1' | '2' | '3'\n /**\n * @deprecated `fixed-size` を利用してください。\n * `attr()` の数値解釈サポートが限定的なため、リセット CSS だけではレイアウトシフトが防げません。\n */\n 'unsafe-non-guideline-scale'?: number | string\n /**\n * 固定 px サイズで描画します。ガイドライン外のサイズを利用する場合に推奨される指定方法で、\n * 他のサイズ指定 (`scale` / `unsafe-non-guideline-scale`) よりも常に優先されます。\n *\n * Web Component の upgrade 前 (= SSR 直後の CSS-only 状態) でも CLS を防ぐには、\n * 同じ値を `style=\"--charcoal-icon-size: Npx\"` としてインラインに指定してください。\n * React の `<Icon fixedSize>` 経由で利用する場合はインラインスタイルが自動的に付与されます。\n */\n 'fixed-size'?: number | string\n\n // CustomElements は className が使えない。class と書く必要がある\n // https://ja.reactjs.org/docs/web-components.html#using-web-components-in-react\n class?: string\n}\n\ntype ExtendedIconFile = Exclude<keyof KnownIconType, KnownIconFile>\ntype Extended = [ExtendedIconFile] extends [never] // NOTE: ExtendedIconFileがneverならKnownIconTypeは拡張されていない\n ? false\n : true\n\nexport class PixivIcon extends HTMLElement {\n static readonly tagName = 'pixiv-icon'\n\n /**\n * NOTE: icon content should be sanitized before pass to extend()\n *\n * XSSに注意すること。\n * 登録したファイルの中身が直接domに反映されるため、XSSに繋がる可能性があります。\n * 信用していないソースからアイコンを追加する場合dom-purifyなどを経由してください。\n */\n static extend(\n map: Extended extends true\n ? Record<ExtendedIconFile, string | (() => Promise<string>)>\n : Record<string, string | (() => Promise<string>)>,\n ): void {\n if (__SERVER__) {\n return\n }\n\n Object.entries(map).forEach(([name, filePathOrUrlOrImportFn]) => {\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n if (typeof filePathOrUrlOrImportFn === 'string') {\n addCustomIcon(name, filePathOrUrlOrImportFn)\n }\n\n if (typeof filePathOrUrlOrImportFn === 'function') {\n addRawFile(name, filePathOrUrlOrImportFn)\n }\n })\n }\n\n static get observedAttributes(): typeof attributes {\n return attributes\n }\n\n private svgContent?: string\n private observer?: IntersectionObserver\n private isVisible = false\n\n get props(): {\n name: string\n scale: string | null\n 'unsafe-non-guideline-scale': string | null\n 'fixed-size': string | null\n } {\n const partial = Object.fromEntries(\n attributes.map((attribute) => [attribute, this.getAttribute(attribute)]),\n ) as Record<(typeof attributes)[number], string | null>\n\n const name = partial.name\n\n if (name === null) {\n throw new TypeError('property \"name\" is required.')\n }\n\n if (!name.includes('/')) {\n throw new TypeError(\n `${name} is not a valid icon name. \"name\" must be named like [size]/[Name].`,\n )\n }\n\n return {\n ...partial,\n name,\n }\n }\n\n constructor() {\n super()\n this.attachShadow({ mode: 'open' })\n }\n\n async connectedCallback(): Promise<void> {\n this.render()\n await this.waitUntilVisible()\n this.isVisible = true\n await this.loadSvg(this.props.name)\n }\n\n disconnectedCallback(): void {\n this.observer?.disconnect()\n this.observer = undefined\n this.isVisible = false\n }\n\n attributeChangedCallback(\n attr: string,\n _oldValue: string | null,\n newValue: string,\n ): void {\n // 非表示の場合はロードしない\n if (!this.isVisible) {\n return\n }\n\n // name が変更された場合必ず再読み込みを試みる\n if (attr === 'name') {\n void this.loadSvg(newValue)\n return\n }\n\n // SVG が読み込み済み && scale などの変更だけならそこだけ反映すればいい\n if (this.svgContent !== undefined) {\n this.render()\n return\n }\n\n // まだ SVG が読み込めてないなら load\n void this.loadSvg(this.props.name)\n }\n\n render(): void {\n const { name, scale, ...rest } = this.props\n const unsafeNonGuidelineScale = rest['unsafe-non-guideline-scale']\n const fixedSize = rest['fixed-size']\n\n const size = calcActualSize({\n name,\n ...(fixedSize !== null\n ? { fixedSize: parseFloat(fixedSize) }\n : unsafeNonGuidelineScale !== null\n ? { unsafeNonGuidelineScale: parseFloat(unsafeNonGuidelineScale) }\n : { scale: scale ?? undefined }),\n } as Parameters<typeof calcActualSize>[0])\n\n // icon.css の pixiv-icon:not(:defined) ルールと同じ CSS variable に\n // 計算結果を流し込むことで、hydrate 前後で width / height が揺れないようにする\n this.style.setProperty('--charcoal-icon-size', `${size}px`)\n\n const style = `<style>\n :host {\n display: inline-flex;\n }\n\n svg {\n width: var(--charcoal-icon-size);\n height: var(--charcoal-icon-size);\n }\n</style>`\n\n const svg =\n this.svgContent !== undefined\n ? this.svgContent\n : `<svg viewBox=\"0 0 ${size} ${size}\"></svg>`\n\n // NOTE: User should sanitize the svg content before passing to charcoal.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n this.shadowRoot!.innerHTML = style + svg\n }\n\n private async loadSvg(name: string) {\n this.svgContent = await getIcon(name)\n this.render()\n }\n\n private waitUntilVisible() {\n return new Promise<void>((resolve) => {\n this.observer = new IntersectionObserver(\n (entries) => {\n // In Chromium based browsers, multiple entries can be returned even only observe once.\n // Here, we don't care about the entry time but only if isIntersecting happened.\n const isIntersecting = entries.some((entry) => entry.isIntersecting)\n if (isIntersecting) {\n this.observer?.disconnect()\n this.observer = undefined\n resolve()\n }\n },\n { rootMargin: `${ROOT_MARGIN}px` },\n )\n\n this.observer.observe(this)\n })\n }\n}\n","import { PixivIcon } from './PixivIcon'\nimport { __SERVER__ } from './ssr'\nexport { PixivIcon, type KnownIconType, type Props } from './PixivIcon'\nexport { calcActualSize, type IconSizing } from './calcActualSize'\nexport { KNOWN_ICON_FILES } from './charcoalIconFiles'\nexport { PixivIconLoadError } from './loaders/PixivIconLoadError'\n\ndeclare global {\n interface Window {\n PixivIcon: typeof PixivIcon\n }\n}\n\ndeclare module 'react' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'pixiv-icon': import('./PixivIcon').Props\n }\n }\n}\n\nif (!__SERVER__) {\n // TODO: HMR対応\n if (!window.customElements.get(PixivIcon.tagName)) {\n window.PixivIcon = PixivIcon\n window.customElements.define(PixivIcon.tagName, PixivIcon)\n }\n}\n"],"mappings":"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"}