diff --git a/CLAUDE.md b/CLAUDE.md index 663f9981..2cb841fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,6 +53,12 @@ All eight libraries are attached on every page via `druid.info.yml` `libraries:` *Only when re-vendoring a new modern-normalize release — see the file header for the workflow. +## JavaScript + +There's one JS file: `js/druid.behaviors.js`, registered as the `druid/behaviors` library with `core/drupal` + `core/once` as dependencies, attached on every page. It's currently empty inside its IIFE — a scaffold ready for the first behavior. + +All theme JS should use Drupal's behaviors system rather than plain `DOMContentLoaded` listeners, because Drupal swaps DOM fragments at runtime (AJAX views, form errors, contextual links, modals) and `attach()` is what re-runs on each injected fragment. Use `once()` to prevent double-initialization. The file's own header comment is the detailed pattern reference; if you split JS into multiple files, either co-locate them in the `behaviors:` library entry or register new libraries for conditional loading via `libraries-extend`. + ### Tokens / Open Props `css/tokens.css` holds CSS custom properties on `:root`. We do **not** import the full Open Props library — instead, we cherry-pick individual tokens by copying their declarations into this file. The workflow is documented in the file's header comment; re-read it before adding tokens. The short version: browse https://open-props.style, copy the `--prop: value;` line, paste into the relevant section, preserve the Open Props name. @@ -68,6 +74,7 @@ All eight libraries are attached on every page via `druid.info.yml` `libraries:` - **Cache clear**: required after any `.yml`, `.theme`, or template change — `drush cr` from the site root. CSS *content* edits do not require it; only adding/removing files or library entries does. - **New component CSS**: create the file in `css/components/`, wrap its rules in `@layer components { … }`, register it under the `components:` library in `druid.libraries.yml` with `weight: -10`. If the styles should only load when a specific core library is in use, give it its own library and wire it via `libraries-extend` in `druid.info.yml` instead. +- **New JS behavior**: add it inside `js/druid.behaviors.js` following the `Drupal.behaviors.druidX = { attach(context) { once('druid-x', '.selector', context).forEach(…) } }` pattern. Always scope selectors to `context`, not `document`. For a separate file, register it in the `behaviors:` library (same dependencies) or define a new library if it should load conditionally. - **New Twig override**: copy the upstream template from `core/themes/stable9/templates/…` (or the originating module) into the matching subdirectory here — don't write from scratch; the docblock comments document available variables and are worth keeping. - **New preprocess hook**: follow the existing signature in `druid.theme` (`array &$variables`, `: void` return type, `/** Implements hook_X(). */` docblock). - **Drupal core / contrib CSS is unlayered.** That means it wins against any `@layer components` rule you write — this is the one place the architecture leaks. Options: bump specificity for the override, or wrap the offending core CSS via a future `hook_css_alter` (not currently implemented). Don't reach for `!important` reflexively. diff --git a/druid.info.yml b/druid.info.yml index 88e74fbc..7aff538c 100644 --- a/druid.info.yml +++ b/druid.info.yml @@ -25,6 +25,7 @@ libraries: - druid/layout - druid/components - druid/utilities + - druid/behaviors - druid/messages libraries-extend: user/drupal.user: diff --git a/druid.libraries.yml b/druid.libraries.yml index dbeab74f..97b71be5 100644 --- a/druid.libraries.yml +++ b/druid.libraries.yml @@ -61,6 +61,13 @@ utilities: theme: css/utilities.css: weight: -50 +behaviors: + version: VERSION + js: + js/druid.behaviors.js: {} + dependencies: + - core/drupal + - core/once components: version: VERSION css: diff --git a/fonts/adelle/Adelle_Bold.otf b/fonts/adelle/Adelle_Bold.otf deleted file mode 100644 index b3884d2c..00000000 Binary files a/fonts/adelle/Adelle_Bold.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_BoldItalic.otf b/fonts/adelle/Adelle_BoldItalic.otf deleted file mode 100644 index 8ba6c634..00000000 Binary files a/fonts/adelle/Adelle_BoldItalic.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_ExtraBold.otf b/fonts/adelle/Adelle_ExtraBold.otf deleted file mode 100644 index e9ffe35d..00000000 Binary files a/fonts/adelle/Adelle_ExtraBold.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_ExtraBoldItalic.otf b/fonts/adelle/Adelle_ExtraBoldItalic.otf deleted file mode 100644 index 6912c839..00000000 Binary files a/fonts/adelle/Adelle_ExtraBoldItalic.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_Heavy.otf b/fonts/adelle/Adelle_Heavy.otf deleted file mode 100644 index 76d18a39..00000000 Binary files a/fonts/adelle/Adelle_Heavy.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_HeavyItalic.otf b/fonts/adelle/Adelle_HeavyItalic.otf deleted file mode 100644 index 9c518425..00000000 Binary files a/fonts/adelle/Adelle_HeavyItalic.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_Italic.otf b/fonts/adelle/Adelle_Italic.otf deleted file mode 100644 index 1e78bf9e..00000000 Binary files a/fonts/adelle/Adelle_Italic.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_LightItalic.otf b/fonts/adelle/Adelle_LightItalic.otf deleted file mode 100644 index 8e7e12be..00000000 Binary files a/fonts/adelle/Adelle_LightItalic.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_Reg.otf b/fonts/adelle/Adelle_Reg.otf deleted file mode 100644 index 84975150..00000000 Binary files a/fonts/adelle/Adelle_Reg.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_SemiBoldItalic.otf b/fonts/adelle/Adelle_SemiBoldItalic.otf deleted file mode 100644 index e8e2dd56..00000000 Binary files a/fonts/adelle/Adelle_SemiBoldItalic.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_Semibold.otf b/fonts/adelle/Adelle_Semibold.otf deleted file mode 100644 index 2bca2d66..00000000 Binary files a/fonts/adelle/Adelle_Semibold.otf and /dev/null differ diff --git a/fonts/adelle/Adelle_light.otf b/fonts/adelle/Adelle_light.otf deleted file mode 100644 index 3ef1f04c..00000000 Binary files a/fonts/adelle/Adelle_light.otf and /dev/null differ diff --git a/js/druid.behaviors.js b/js/druid.behaviors.js new file mode 100644 index 00000000..0cf041c2 --- /dev/null +++ b/js/druid.behaviors.js @@ -0,0 +1,71 @@ +/** + * @file + * JavaScript behaviors for the druid theme. + * + * ───────────────────────────────────────────────────────────────────────── + * What Drupal.behaviors solves + * ───────────────────────────────────────────────────────────────────────── + * + * Drupal swaps DOM fragments at runtime — form errors, AJAX-loaded views, + * contextual links, modal dialogs, and inline edits all inject new HTML + * into the page after the initial load. A plain `DOMContentLoaded` handler + * runs once and misses everything that arrives later. + * + * The behaviors system runs your `attach()` on every fragment Drupal + * inserts (including the initial document), so the same code initializes + * new content automatically. Use `once()` to make sure an element is only + * initialized one time even if `attach()` is invoked repeatedly. + * + * ───────────────────────────────────────────────────────────────────────── + * Pattern + * ───────────────────────────────────────────────────────────────────────── + * + * Drupal.behaviors.druidSomething = { + * attach(context, settings) { + * once('druid-something', '.selector', context).forEach((el) => { + * // initialize el — add listeners, hydrate state, etc. + * }); + * }, + * detach(context, settings, trigger) { + * // OPTIONAL — clean up listeners/observers when Drupal removes the + * // fragment. Skip if you don't allocate anything that leaks. + * }, + * }; + * + * - `context` is the DOM subtree being attached. On the initial page load + * it's `document`; on AJAX updates it's just the newly inserted fragment. + * Always scope queries to `context`, never to `document`, or you'll + * re-initialize the whole page. + * - The first arg to `once()` is a unique string ID for this behavior. The + * second is a CSS selector. `once()` returns an array of elements that + * haven't been initialized yet (it marks them with a data attribute so + * subsequent calls skip them). + * - `settings` is the JS object Drupal exposes via `drupalSettings` — use + * it to read PHP-side configuration (`drupalSettings.druid.someValue`). + * + * Naming: prefix behavior keys with `druid` so they don't collide with + * core/contrib behaviors. The `once()` ID should match the behavior name + * minus the prefix, lowercased and kebab-case. + * + * Library wiring: this file is registered in druid.libraries.yml under the + * `behaviors` library, with `core/drupal` and `core/once` as dependencies. + * If you split JS into multiple files, either add them under the same + * library or define new libraries and attach them via `libraries-extend` + * (for conditional loading) or the always-on list in druid.info.yml. + * + * Reference: https://www.drupal.org/docs/develop/standards/javascript + */ + +((Drupal, once) => { + // Behaviors go here. Example skeleton — uncomment and adapt: + // + // Drupal.behaviors.druidExample = { + // attach(context) { + // once('druid-example', '[data-druid-example]', context).forEach((el) => { + // el.addEventListener('click', () => { + // el.classList.toggle('is-active'); + // }); + // }); + // }, + // }; +})(Drupal, once);