ds-one 0.2.0-alpha.1 → 0.2.0-alpha.3

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.
Files changed (93) hide show
  1. package/DS1/1-root/one.css +1 -1
  2. package/DS1/2-core/{article-v1.ts → ds-article.ts} +5 -4
  3. package/DS1/2-core/{attributes-v1.ts → ds-attributes.ts} +4 -3
  4. package/DS1/2-core/{cycle-v1.ts → ds-cycle.ts} +11 -10
  5. package/DS1/2-core/{downloadcv-v1.ts → ds-downloadcv.ts} +9 -8
  6. package/DS1/2-core/{header-v1.ts → ds-header.ts} +4 -3
  7. package/DS1/2-core/{home-v1.ts → ds-home.ts} +3 -2
  8. package/DS1/2-core/{icon-v1.ts → ds-icon.ts} +2 -2
  9. package/DS1/2-core/{link-v1.ts → ds-link.ts} +5 -5
  10. package/DS1/2-core/{markdown-v1.ts → ds-markdown.ts} +3 -2
  11. package/DS1/2-core/{price-v1.ts → ds-price.ts} +4 -3
  12. package/DS1/2-core/{squarecircle-v1.ts → ds-squarecircle.ts} +4 -3
  13. package/DS1/2-core/{title-v1.ts → ds-title.ts} +5 -4
  14. package/DS1/2-core/{tooltip-v1.ts → ds-tooltip.ts} +4 -3
  15. package/DS1/2-core/{viewtoggle-v1.ts → ds-viewtoggle.ts} +4 -3
  16. package/DS1/2-core/{year-v1.ts → ds-year.ts} +4 -3
  17. package/DS1/3-unit/{doublenav-v1.ts → ds-doublenav.ts} +9 -8
  18. package/DS1/3-unit/{list-v1.ts → ds-list.ts} +3 -2
  19. package/DS1/3-unit/{panel-v1.ts → ds-panel.ts} +3 -2
  20. package/DS1/3-unit/{row-v1.ts → ds-row.ts} +3 -2
  21. package/DS1/3-unit/{singlenav-v1.ts → ds-singlenav.ts} +5 -4
  22. package/DS1/index.ts +20 -20
  23. package/DS1/utils/cdn-loader.ts +20 -44
  24. package/README.md +4 -4
  25. package/dist/2-core/ds-article.d.ts +129 -0
  26. package/dist/2-core/ds-article.d.ts.map +1 -0
  27. package/dist/2-core/ds-article.js +361 -0
  28. package/dist/2-core/ds-attributes.d.ts +47 -0
  29. package/dist/2-core/ds-attributes.d.ts.map +1 -0
  30. package/dist/2-core/ds-attributes.js +128 -0
  31. package/dist/2-core/ds-cycle.d.ts +66 -0
  32. package/dist/2-core/ds-cycle.d.ts.map +1 -0
  33. package/dist/2-core/ds-cycle.js +586 -0
  34. package/dist/2-core/ds-downloadcv.d.ts +58 -0
  35. package/dist/2-core/ds-downloadcv.d.ts.map +1 -0
  36. package/dist/2-core/ds-downloadcv.js +119 -0
  37. package/dist/2-core/ds-header.d.ts +28 -0
  38. package/dist/2-core/ds-header.d.ts.map +1 -0
  39. package/dist/2-core/ds-header.js +66 -0
  40. package/dist/2-core/ds-home.d.ts +26 -0
  41. package/dist/2-core/ds-home.d.ts.map +1 -0
  42. package/dist/2-core/ds-home.js +148 -0
  43. package/dist/2-core/ds-icon.d.ts +28 -0
  44. package/dist/2-core/ds-icon.d.ts.map +1 -0
  45. package/dist/2-core/ds-icon.js +297 -0
  46. package/dist/2-core/ds-link.d.ts +35 -0
  47. package/dist/2-core/ds-link.d.ts.map +1 -0
  48. package/dist/2-core/ds-link.js +85 -0
  49. package/dist/2-core/ds-markdown.d.ts +7 -0
  50. package/dist/2-core/ds-markdown.d.ts.map +1 -0
  51. package/dist/2-core/ds-markdown.js +240 -0
  52. package/dist/2-core/ds-price.d.ts +46 -0
  53. package/dist/2-core/ds-price.d.ts.map +1 -0
  54. package/dist/2-core/ds-price.js +72 -0
  55. package/dist/2-core/ds-squarecircle.d.ts +50 -0
  56. package/dist/2-core/ds-squarecircle.d.ts.map +1 -0
  57. package/dist/2-core/ds-squarecircle.js +133 -0
  58. package/dist/2-core/ds-title.d.ts +50 -0
  59. package/dist/2-core/ds-title.d.ts.map +1 -0
  60. package/dist/2-core/ds-title.js +103 -0
  61. package/dist/2-core/ds-tooltip.d.ts +39 -0
  62. package/dist/2-core/ds-tooltip.d.ts.map +1 -0
  63. package/dist/2-core/ds-tooltip.js +145 -0
  64. package/dist/2-core/ds-viewtoggle.d.ts +27 -0
  65. package/dist/2-core/ds-viewtoggle.d.ts.map +1 -0
  66. package/dist/2-core/ds-viewtoggle.js +49 -0
  67. package/dist/2-core/ds-year.d.ts +16 -0
  68. package/dist/2-core/ds-year.d.ts.map +1 -0
  69. package/dist/2-core/ds-year.js +21 -0
  70. package/dist/3-unit/ds-doublenav.d.ts +51 -0
  71. package/dist/3-unit/ds-doublenav.d.ts.map +1 -0
  72. package/dist/3-unit/ds-doublenav.js +88 -0
  73. package/dist/3-unit/ds-list.d.ts +11 -0
  74. package/dist/3-unit/ds-list.d.ts.map +1 -0
  75. package/dist/3-unit/ds-list.js +15 -0
  76. package/dist/3-unit/ds-panel.d.ts +11 -0
  77. package/dist/3-unit/ds-panel.d.ts.map +1 -0
  78. package/dist/3-unit/ds-panel.js +16 -0
  79. package/dist/3-unit/ds-row.d.ts +25 -0
  80. package/dist/3-unit/ds-row.d.ts.map +1 -0
  81. package/dist/3-unit/ds-row.js +32 -0
  82. package/dist/3-unit/ds-singlenav.d.ts +32 -0
  83. package/dist/3-unit/ds-singlenav.d.ts.map +1 -0
  84. package/dist/3-unit/ds-singlenav.js +62 -0
  85. package/dist/ds-one.bundle.js +79 -99
  86. package/dist/ds-one.bundle.js.map +3 -3
  87. package/dist/ds-one.bundle.min.js +47 -47
  88. package/dist/ds-one.bundle.min.js.map +4 -4
  89. package/dist/index.d.ts +20 -20
  90. package/dist/index.js +20 -20
  91. package/dist/utils/cdn-loader.d.ts.map +1 -1
  92. package/dist/utils/cdn-loader.js +20 -38
  93. package/package.json +2 -2
@@ -17,20 +17,7 @@ declare global {
17
17
  // Track if we've already attempted to load
18
18
  let loadAttempted = false;
19
19
 
20
- const DEFAULT_TRANSLATION_FILES = [
21
- "./keys.json",
22
- "./tekst.json",
23
- "./tekster.json",
24
- "./language.json",
25
- "./languages.json",
26
- "./translations.json",
27
- "./translate.json",
28
- "./i18n.json",
29
- "./locales.json",
30
- "./strings.json",
31
- "./text.json",
32
- "./texts.json",
33
- ];
20
+ const DEFAULT_TRANSLATION_FILE = "./translations.json";
34
21
 
35
22
  function normalizeCandidate(path: string): string | null {
36
23
  if (!path) {
@@ -86,26 +73,29 @@ function findAttributeCandidate(): string | null {
86
73
  }
87
74
 
88
75
  function resolveTranslationSources(): string[] {
89
- const candidates = new Set<string>();
76
+ const candidates: string[] = [];
90
77
 
91
78
  const windowCandidate =
92
79
  typeof window !== "undefined" ? window.DS_ONE_TRANSLATIONS_FILE : null;
93
80
  const attributeCandidate = findAttributeCandidate();
94
81
 
95
- const addCandidate = (candidate: string | null) => {
96
- const normalized = normalizeCandidate(candidate ?? "");
97
- if (normalized) {
98
- candidates.add(normalized);
99
- }
100
- };
82
+ // Only use explicitly configured paths, or the single default
83
+ const windowNormalized = normalizeCandidate(windowCandidate ?? "");
84
+ if (windowNormalized) {
85
+ candidates.push(windowNormalized);
86
+ }
101
87
 
102
- addCandidate(windowCandidate ?? null);
103
- addCandidate(attributeCandidate);
88
+ const attrNormalized = normalizeCandidate(attributeCandidate ?? "");
89
+ if (attrNormalized && !candidates.includes(attrNormalized)) {
90
+ candidates.push(attrNormalized);
91
+ }
104
92
 
105
- // Always include defaults as fallback so existing behaviour keeps working
106
- DEFAULT_TRANSLATION_FILES.forEach((file) => addCandidate(file));
93
+ // Only try default if no explicit path was configured
94
+ if (candidates.length === 0) {
95
+ candidates.push(DEFAULT_TRANSLATION_FILE);
96
+ }
107
97
 
108
- return Array.from(candidates);
98
+ return candidates;
109
99
  }
110
100
 
111
101
  function validateTranslationMap(
@@ -124,16 +114,14 @@ async function fetchTranslationFile(
124
114
  source: string
125
115
  ): Promise<TranslationMap | null> {
126
116
  try {
127
- console.log(`[DS one] Attempting to fetch translations from: ${source}`);
128
117
  const response = await fetch(source);
129
118
 
130
119
  if (!response.ok) {
131
- console.log(`[DS one] Failed to fetch ${source} (${response.status})`);
120
+ // 404 is expected if no translations file exists - don't log as error
132
121
  return null;
133
122
  }
134
123
 
135
124
  const translations = await response.json();
136
- console.log(`[DS one] Successfully fetched JSON from ${source}`);
137
125
 
138
126
  if (!validateTranslationMap(translations)) {
139
127
  console.warn(
@@ -148,21 +136,9 @@ async function fetchTranslationFile(
148
136
  return null;
149
137
  }
150
138
 
151
- console.log(`[DS one] Valid translations found: ${languages.join(", ")}`);
152
139
  return translations;
153
- } catch (error) {
154
- if (
155
- error instanceof TypeError &&
156
- error.message.includes("Failed to fetch")
157
- ) {
158
- console.log(`[DS one] Network error fetching ${source}`);
159
- return null;
160
- }
161
-
162
- console.error(
163
- `[DS one] Error loading external translations from ${source}:`,
164
- error
165
- );
140
+ } catch {
141
+ // Silently fail - file likely doesn't exist or isn't valid JSON
166
142
  return null;
167
143
  }
168
144
  }
@@ -212,7 +188,7 @@ export async function loadExternalTranslations(): Promise<boolean> {
212
188
  }
213
189
 
214
190
  console.info(
215
- `[DS one] No external translations found. Looked in: ${sources.join(", ")}. Using bundled translations.`
191
+ `[DS one] No external translations found at ${sources[0] ?? DEFAULT_TRANSLATION_FILE}. Using bundled translations.`
216
192
  );
217
193
 
218
194
  return false;
package/README.md CHANGED
@@ -26,7 +26,7 @@ npm install ds-one@alpha
26
26
  yarn add ds-one@alpha
27
27
  ```
28
28
 
29
- **Note**: Currently published as alpha version `0.2.0-alpha.1`. Use `@alpha` tag to install.
29
+ **Note**: Currently published as alpha version `0.2.0-alpha.3`. Use `@alpha` tag to install.
30
30
 
31
31
  ### Basic Usage (CDN)
32
32
 
@@ -86,13 +86,13 @@ Try DS one in your browser: **[dsone.dev](https://dsone.dev)** (documentation sl
86
86
  - **[Internationalization](./docs/i18n.md)** - Language keys and Notion CMS setup
87
87
  - **[Examples](./docs/examples.md)** - Usage examples and patterns
88
88
 
89
- ## Current Status: v0.2.0-alpha.1
89
+ ## Current Status: v0.2.0-alpha.3
90
90
 
91
91
  **⚠️ Alpha Release**: This is an early alpha version. The API may change as we refine the components and architecture.
92
92
 
93
93
  ### Completed Features
94
94
 
95
- - ✅ Core component library (ds-button, ds-text, icon-v1, etc.)
95
+ - ✅ Core component library (ds-button, ds-text, ds-icon, etc.)
96
96
  - ✅ Theming system with accent color support
97
97
  - ✅ Internationalization with language keys
98
98
  - ✅ Responsive design with mobile scaling
@@ -183,7 +183,7 @@ bun run build
183
183
 
184
184
  ```bash
185
185
  # Create a new alpha release (recommended for now)
186
- bun run release:pre:alpha # Bumps alpha version (e.g., 0.2.0-alpha.1 → 0.2.0-alpha.2)
186
+ bun run release:pre:alpha # Bumps alpha version (e.g., 0.2.0-alpha.3 → 0.2.0-alpha.3)
187
187
 
188
188
  # Other release commands (for future use)
189
189
  bun run release:patch # For patch releases
@@ -0,0 +1,129 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * A component for displaying article content with image/text toggle
4
+ *
5
+ * @element ds-article
6
+ */
7
+ export declare class Article extends LitElement {
8
+ static get properties(): {
9
+ imgLightDesktop: {
10
+ type: StringConstructor;
11
+ reflect: boolean;
12
+ attribute: string;
13
+ };
14
+ imgDarkDesktop: {
15
+ type: StringConstructor;
16
+ reflect: boolean;
17
+ attribute: string;
18
+ };
19
+ imgLightMobile: {
20
+ type: StringConstructor;
21
+ reflect: boolean;
22
+ attribute: string;
23
+ };
24
+ imgDarkMobile: {
25
+ type: StringConstructor;
26
+ reflect: boolean;
27
+ attribute: string;
28
+ };
29
+ src: {
30
+ type: StringConstructor;
31
+ reflect: boolean;
32
+ };
33
+ alt: {
34
+ type: StringConstructor;
35
+ reflect: boolean;
36
+ };
37
+ imageToggle: {
38
+ type: BooleanConstructor;
39
+ reflect: boolean;
40
+ attribute: string;
41
+ };
42
+ imageOnlyWidth: {
43
+ type: NumberConstructor;
44
+ reflect: boolean;
45
+ attribute: string;
46
+ };
47
+ priority: {
48
+ type: BooleanConstructor;
49
+ reflect: boolean;
50
+ };
51
+ externalToggle: {
52
+ type: BooleanConstructor;
53
+ reflect: boolean;
54
+ attribute: string;
55
+ };
56
+ mobileKey: {
57
+ type: StringConstructor;
58
+ reflect: boolean;
59
+ attribute: string;
60
+ };
61
+ desktopKey: {
62
+ type: StringConstructor;
63
+ reflect: boolean;
64
+ attribute: string;
65
+ };
66
+ textKey: {
67
+ type: StringConstructor;
68
+ reflect: boolean;
69
+ attribute: string;
70
+ };
71
+ defaultValue: {
72
+ type: StringConstructor;
73
+ reflect: boolean;
74
+ attribute: string;
75
+ };
76
+ _text: {
77
+ type: StringConstructor;
78
+ state: boolean;
79
+ };
80
+ _currentImage: {
81
+ type: StringConstructor;
82
+ state: boolean;
83
+ };
84
+ _imageVisible: {
85
+ type: BooleanConstructor;
86
+ state: boolean;
87
+ };
88
+ _shouldAnimate: {
89
+ type: BooleanConstructor;
90
+ state: boolean;
91
+ };
92
+ };
93
+ imgLightDesktop: string;
94
+ imgDarkDesktop: string;
95
+ imgLightMobile: string;
96
+ imgDarkMobile: string;
97
+ src: string;
98
+ alt: string;
99
+ imageToggle: boolean;
100
+ imageOnlyWidth: number;
101
+ priority: boolean;
102
+ externalToggle: boolean;
103
+ mobileKey: string;
104
+ desktopKey: string;
105
+ textKey: string;
106
+ defaultValue: string;
107
+ _text: string;
108
+ _currentImage: string;
109
+ _imageVisible: boolean;
110
+ _shouldAnimate: boolean;
111
+ private _mql;
112
+ private boundHandlers;
113
+ constructor();
114
+ static styles: import("lit").CSSResult;
115
+ updated(changed: Map<string, unknown>): void;
116
+ connectedCallback(): void;
117
+ disconnectedCallback(): void;
118
+ private _loadText;
119
+ private _resolveImage;
120
+ private _toggleImage;
121
+ private _handleImageLoad;
122
+ render(): import("lit-html").TemplateResult<1>;
123
+ }
124
+ declare global {
125
+ interface HTMLElementTagNameMap {
126
+ "ds-article": Article;
127
+ }
128
+ }
129
+ //# sourceMappingURL=ds-article.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ds-article.d.ts","sourceRoot":"","sources":["../../DS1/2-core/ds-article.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;GAIG;AACH,qBAAa,OAAQ,SAAQ,UAAU;IACrC,MAAM,KAAK,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA6CpB;IAEO,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IAEhC,OAAO,CAAC,IAAI,CAAwB;IACpC,OAAO,CAAC,aAAa,CAKnB;;IAiDF,MAAM,CAAC,MAAM,0BAmEX;IAEF,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAkCrC,iBAAiB;IAuDjB,oBAAoB;IA+BpB,OAAO,CAAC,SAAS;IAsCjB,OAAO,CAAC,aAAa;IAwCrB,OAAO,CAAC,YAAY,CAIlB;IAEF,OAAO,CAAC,gBAAgB,CAGtB;IAEF,MAAM;CAmCP;AAID,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,OAAO,CAAC;KACvB;CACF"}
@@ -0,0 +1,361 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { getText } from "../utils/language";
3
+ import { getViewMode } from "../utils/viewMode";
4
+ /**
5
+ * A component for displaying article content with image/text toggle
6
+ *
7
+ * @element ds-article
8
+ */
9
+ export class Article extends LitElement {
10
+ static get properties() {
11
+ return {
12
+ imgLightDesktop: {
13
+ type: String,
14
+ reflect: true,
15
+ attribute: "img-light-desktop",
16
+ },
17
+ imgDarkDesktop: {
18
+ type: String,
19
+ reflect: true,
20
+ attribute: "img-dark-desktop",
21
+ },
22
+ imgLightMobile: {
23
+ type: String,
24
+ reflect: true,
25
+ attribute: "img-light-mobile",
26
+ },
27
+ imgDarkMobile: {
28
+ type: String,
29
+ reflect: true,
30
+ attribute: "img-dark-mobile",
31
+ },
32
+ src: { type: String, reflect: true },
33
+ alt: { type: String, reflect: true },
34
+ imageToggle: { type: Boolean, reflect: true, attribute: "image-toggle" },
35
+ imageOnlyWidth: {
36
+ type: Number,
37
+ reflect: true,
38
+ attribute: "image-only-width",
39
+ },
40
+ priority: { type: Boolean, reflect: true },
41
+ externalToggle: {
42
+ type: Boolean,
43
+ reflect: true,
44
+ attribute: "external-toggle",
45
+ },
46
+ mobileKey: { type: String, reflect: true, attribute: "mobile-key" },
47
+ desktopKey: { type: String, reflect: true, attribute: "desktop-key" },
48
+ textKey: { type: String, reflect: true, attribute: "text-key" },
49
+ defaultValue: { type: String, reflect: true, attribute: "default-value" },
50
+ _text: { type: String, state: true },
51
+ _currentImage: { type: String, state: true },
52
+ _imageVisible: { type: Boolean, state: true },
53
+ _shouldAnimate: { type: Boolean, state: true },
54
+ };
55
+ }
56
+ constructor() {
57
+ super();
58
+ this._toggleImage = () => {
59
+ this._shouldAnimate = true;
60
+ this._imageVisible = !this._imageVisible;
61
+ this.requestUpdate();
62
+ };
63
+ this._handleImageLoad = (e) => {
64
+ const img = e.target;
65
+ img.classList.add("loaded");
66
+ };
67
+ this.imgLightDesktop = "";
68
+ this.imgDarkDesktop = "";
69
+ this.imgLightMobile = "";
70
+ this.imgDarkMobile = "";
71
+ this.src = "";
72
+ this.alt = "";
73
+ this.imageToggle = false;
74
+ this.imageOnlyWidth = 480;
75
+ this.priority = false;
76
+ this.externalToggle = false;
77
+ this.mobileKey = "";
78
+ this.desktopKey = "";
79
+ this.textKey = "";
80
+ this.defaultValue = "";
81
+ this._text = "";
82
+ this._currentImage = "";
83
+ this._imageVisible = true;
84
+ this._shouldAnimate = false;
85
+ this._mql = null;
86
+ this.boundHandlers = {
87
+ languageChanged: (() => {
88
+ this._loadText();
89
+ }),
90
+ themeChanged: (() => {
91
+ this._resolveImage();
92
+ }),
93
+ viewportChanged: (e) => {
94
+ this._resolveImage();
95
+ this._loadText();
96
+ },
97
+ viewModeChanged: ((e) => {
98
+ if (this.imageToggle && this.externalToggle) {
99
+ const newMode = e.detail;
100
+ const shouldShowImage = newMode === "image";
101
+ if (this._imageVisible !== shouldShowImage) {
102
+ this._shouldAnimate = true;
103
+ this._imageVisible = shouldShowImage;
104
+ this.requestUpdate();
105
+ }
106
+ }
107
+ }),
108
+ };
109
+ }
110
+ updated(changed) {
111
+ if (changed.has("imageOnlyWidth")) {
112
+ const opx = Number(this.imageOnlyWidth) || 480;
113
+ this.style.setProperty("--image-only-width", `${opx}px`);
114
+ }
115
+ if (changed.has("imgLightDesktop") ||
116
+ changed.has("imgDarkDesktop") ||
117
+ changed.has("imgLightMobile") ||
118
+ changed.has("imgDarkMobile") ||
119
+ changed.has("src")) {
120
+ this._resolveImage();
121
+ }
122
+ if (changed.has("textKey") ||
123
+ changed.has("defaultValue") ||
124
+ changed.has("mobileKey") ||
125
+ changed.has("desktopKey")) {
126
+ this._loadText();
127
+ }
128
+ if (changed.has("imageToggle")) {
129
+ const isExternallyControlled = this.imageToggle && this.externalToggle;
130
+ if (!isExternallyControlled) {
131
+ const newImageVisible = !this.imageToggle;
132
+ if (this._imageVisible !== newImageVisible) {
133
+ this._shouldAnimate = true;
134
+ this._imageVisible = newImageVisible;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ connectedCallback() {
140
+ super.connectedCallback();
141
+ this._loadText();
142
+ this._resolveImage();
143
+ if (this.imageToggle && this.externalToggle) {
144
+ const currentMode = getViewMode();
145
+ this._imageVisible = currentMode === "image";
146
+ }
147
+ else {
148
+ this._imageVisible = !this.imageToggle;
149
+ }
150
+ this.style.setProperty("--image-only-width", `${this.imageOnlyWidth}px`);
151
+ try {
152
+ this._mql = window.matchMedia("(max-width: 720px)");
153
+ if (this._mql && this.boundHandlers.viewportChanged) {
154
+ this._mql.addEventListener("change", this.boundHandlers.viewportChanged);
155
+ }
156
+ }
157
+ catch (err) {
158
+ // no-op
159
+ }
160
+ window.addEventListener("language-changed", this.boundHandlers.languageChanged);
161
+ window.addEventListener("theme-changed", this.boundHandlers.themeChanged);
162
+ if (this.imageToggle && this.externalToggle) {
163
+ window.addEventListener("view-mode-changed", this.boundHandlers.viewModeChanged);
164
+ setTimeout(() => {
165
+ const currentMode = getViewMode();
166
+ const shouldShowImage = currentMode === "image";
167
+ if (this._imageVisible !== shouldShowImage) {
168
+ this._shouldAnimate = true;
169
+ this._imageVisible = shouldShowImage;
170
+ this.requestUpdate();
171
+ }
172
+ }, 100);
173
+ }
174
+ else if (this.imageToggle) {
175
+ window.addEventListener("article-toggle", this._toggleImage);
176
+ }
177
+ }
178
+ disconnectedCallback() {
179
+ super.disconnectedCallback();
180
+ window.removeEventListener("language-changed", this.boundHandlers.languageChanged);
181
+ window.removeEventListener("theme-changed", this.boundHandlers.themeChanged);
182
+ window.removeEventListener("article-toggle", this._toggleImage);
183
+ window.removeEventListener("view-mode-changed", this.boundHandlers.viewModeChanged);
184
+ if (this._mql && this.boundHandlers.viewportChanged) {
185
+ try {
186
+ this._mql.removeEventListener("change", this.boundHandlers.viewportChanged);
187
+ }
188
+ catch (err) {
189
+ // no-op
190
+ }
191
+ }
192
+ }
193
+ _loadText() {
194
+ const isMobile = (() => {
195
+ try {
196
+ return (window.matchMedia && window.matchMedia("(max-width: 720px)").matches);
197
+ }
198
+ catch (err) {
199
+ return false;
200
+ }
201
+ })();
202
+ const selectedKey = (() => {
203
+ const mobile = (this.mobileKey || "").trim();
204
+ const desktop = (this.desktopKey || "").trim();
205
+ const base = (this.textKey || "").trim();
206
+ if (mobile || desktop) {
207
+ return isMobile ? mobile || base : desktop || base;
208
+ }
209
+ return base;
210
+ })();
211
+ if (!selectedKey) {
212
+ this._text = this.defaultValue || "";
213
+ return;
214
+ }
215
+ try {
216
+ const text = getText(selectedKey);
217
+ this._text = text || this.defaultValue || selectedKey;
218
+ }
219
+ catch (error) {
220
+ console.error(`[ds-article] Error loading text for key "${selectedKey}":`, error);
221
+ this._text = this.defaultValue || selectedKey;
222
+ }
223
+ }
224
+ _resolveImage() {
225
+ if (this.src && typeof this.src === "string") {
226
+ const direct = this.src.startsWith("@") ? this.src.slice(1) : this.src;
227
+ this._currentImage = direct;
228
+ return;
229
+ }
230
+ const theme = document.documentElement.getAttribute("data-theme") || "light";
231
+ const isMobile = (() => {
232
+ try {
233
+ return (window.matchMedia && window.matchMedia("(max-width: 720px)").matches);
234
+ }
235
+ catch (err) {
236
+ return false;
237
+ }
238
+ })();
239
+ let chosen = "";
240
+ if (isMobile) {
241
+ if (theme === "dark") {
242
+ chosen = this.imgDarkMobile || this.imgLightMobile;
243
+ }
244
+ else {
245
+ chosen = this.imgLightMobile || this.imgDarkMobile;
246
+ }
247
+ }
248
+ else {
249
+ if (theme === "dark") {
250
+ chosen = this.imgDarkDesktop || this.imgLightDesktop;
251
+ }
252
+ else {
253
+ chosen = this.imgLightDesktop || this.imgDarkDesktop;
254
+ }
255
+ }
256
+ if (typeof chosen === "string" && chosen.startsWith("@")) {
257
+ chosen = chosen.slice(1);
258
+ }
259
+ this._currentImage = chosen || "";
260
+ }
261
+ render() {
262
+ const showImage = (this.imageToggle ? this._imageVisible : true) && !!this._currentImage;
263
+ const animationClass = this._shouldAnimate ? "fade-in" : "";
264
+ if (this._shouldAnimate) {
265
+ this._shouldAnimate = false;
266
+ }
267
+ const buttonText = this._imageVisible
268
+ ? getText("hideImage")
269
+ : getText("viewImage");
270
+ return html `
271
+ <div class="container">
272
+ ${this.imageToggle && !this.externalToggle
273
+ ? html `<button @click="${this._toggleImage}" class="toggle-button">
274
+ ${buttonText}
275
+ </button>`
276
+ : ""}
277
+ ${showImage
278
+ ? html `<figure class="media ${animationClass}">
279
+ <img
280
+ src="${this._currentImage}"
281
+ alt="${this.alt || ""}"
282
+ loading="${this.priority ? "eager" : "lazy"}"
283
+ fetchpriority="${this.priority ? "high" : "auto"}"
284
+ decoding="async"
285
+ @load="${this._handleImageLoad}"
286
+ />
287
+ </figure>`
288
+ : html `<p class="copy ${animationClass}">${this._text}</p>`}
289
+ </div>
290
+ `;
291
+ }
292
+ }
293
+ Article.styles = css `
294
+ :host {
295
+ display: block;
296
+ font-family: var(--typeface);
297
+ overflow-wrap: break-word;
298
+ word-wrap: break-word;
299
+ word-break: break-word;
300
+ hyphens: auto;
301
+ color: light-dark(var(--black), var(--white));
302
+ line-height: calc(20px * var(--scaling-factor));
303
+ padding: calc(2px * var(--scaling-factor));
304
+ }
305
+
306
+ .container {
307
+ position: relative;
308
+ width: min(100%, var(--image-only-width));
309
+ }
310
+
311
+ figure.media,
312
+ .copy {
313
+ margin: 0;
314
+ width: 100%;
315
+ overflow: hidden;
316
+ display: block;
317
+ margin-bottom: calc(8px * var(--scaling-factor));
318
+ }
319
+
320
+ .fade-in {
321
+ animation: fade-in 0.25s ease;
322
+ }
323
+
324
+ @keyframes fade-in {
325
+ from {
326
+ opacity: 0;
327
+ }
328
+ to {
329
+ opacity: 1;
330
+ }
331
+ }
332
+
333
+ img {
334
+ display: block;
335
+ width: 100%;
336
+ height: auto;
337
+ opacity: 0;
338
+ transition: opacity 0.2s ease-in-out;
339
+ }
340
+
341
+ img.loaded {
342
+ opacity: 1;
343
+ }
344
+
345
+ .toggle-button {
346
+ background: none;
347
+ border: none;
348
+ color: inherit;
349
+ cursor: pointer;
350
+ font-family: var(--typeface);
351
+ font-size: calc(14px * var(--scaling-factor));
352
+ margin-bottom: calc(8px * var(--scaling-factor));
353
+ padding: 0;
354
+ text-decoration: underline;
355
+ }
356
+
357
+ .toggle-button:hover {
358
+ opacity: 0.7;
359
+ }
360
+ `;
361
+ customElements.define("ds-article", Article);
@@ -0,0 +1,47 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * A component for displaying project/work attributes (year, category, status)
4
+ *
5
+ * @element ds-attributes
6
+ * @prop {string} year - Year key for translation
7
+ * @prop {string} category - Category key for translation
8
+ * @prop {string} status - Status key for translation
9
+ * @prop {string} type - Type of attributes: "project" or "work"
10
+ */
11
+ export declare class Attributes extends LitElement {
12
+ static get properties(): {
13
+ year: {
14
+ type: StringConstructor;
15
+ reflect: boolean;
16
+ };
17
+ category: {
18
+ type: StringConstructor;
19
+ reflect: boolean;
20
+ };
21
+ status: {
22
+ type: StringConstructor;
23
+ reflect: boolean;
24
+ };
25
+ type: {
26
+ type: StringConstructor;
27
+ reflect: boolean;
28
+ };
29
+ };
30
+ year: string;
31
+ category: string;
32
+ status: string;
33
+ type: string;
34
+ private boundHandlers;
35
+ static styles: import("lit").CSSResult;
36
+ constructor();
37
+ connectedCallback(): void;
38
+ disconnectedCallback(): void;
39
+ _getStatusClass(status: string): string;
40
+ render(): import("lit-html").TemplateResult<1>;
41
+ }
42
+ declare global {
43
+ interface HTMLElementTagNameMap {
44
+ "ds-attributes": Attributes;
45
+ }
46
+ }
47
+ //# sourceMappingURL=ds-attributes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ds-attributes.d.ts","sourceRoot":"","sources":["../../DS1/2-core/ds-attributes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C;;;;;;;;GAQG;AACH,qBAAa,UAAW,SAAQ,UAAU;IACxC,MAAM,KAAK,UAAU;;;;;;;;;;;;;;;;;MAOpB;IAEO,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IAErB,OAAO,CAAC,aAAa,CAAqC;IAE1D,MAAM,CAAC,MAAM,0BA2CX;;IAgBF,iBAAiB;IAQjB,oBAAoB;IAQpB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAQvC,MAAM;CAiCP;AAID,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,UAAU,CAAC;KAC7B;CACF"}