
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
@rhtml/custom-attributes
Advanced tools
npm i @rhtml/custom-attributes
import { CustomAttributeRegistry, Attribute } from '@rhtml/custom-attributes';
const customAttributes = new CustomAttributeRegistry(document);
class BackgroundColor extends Attribute {
OnInit() {
console.log('Attribute initialized');
this.setColor();
}
OnDestroy() {
console.log('Attribute destroyed');
this.element.style.backgroundColor = null;
}
OnUpdate(oldValue: string, newValue: string) {
console.log('Attribute updated');
this.setColor();
}
private setColor() {
this.element.style.backgroundColor = this.value;
}
}
customAttributes.define('background', BackgroundColor);
import { Component, LitElement, html } from '@rxdi/lit-html';
import { Modifier, CustomAttributeRegistry } from '@rhtml/custom-attributes';
@Modifier({
selector: 'background',
registry(this) {
return new CustomAttributeRegistry(this.shadowRoot);
}
})
export class BackgroundColor extends Attribute {
OnInit() {
console.log('Attribute initialized');
this.setColor();
}
OnDestroy() {
console.log('Attribute destroyed');
this.element.style.backgroundColor = null;
}
OnUpdate(oldValue: string, newValue: string) {
console.log('Attribute updated');
this.setColor();
}
private setColor() {
this.element.style.backgroundColor = this.value;
}
}
@Component({
selector: 'home-component',
modifiers: [BackgroundColor],
template(this: HomeComponent) {
return html`
<div background="red">Background</div>
`;
}
})
export class HomeComponent extends LitElement {}
import { Component, LitElement, html } from '@rxdi/lit-html';
import { CustomAttributeRegistry } from '@rhtml/custom-attributes';
export class BackgroundColor extends Attribute {
static options = {
selector: 'myAttribute'
};
OnInit() {
console.log('Attribute initialized');
this.setColor();
}
OnDestroy() {
console.log('Attribute destroyed');
this.element.style.backgroundColor = null;
}
OnUpdate(oldValue: string, newValue: string) {
console.log('Attribute updated');
this.setColor();
}
private setColor() {
this.element.style.backgroundColor = this.value;
}
}
@Component<HomeComponent>({
selector: 'home-component',
registry(this) {
return new CustomAttributeRegistry(this.shadowRoot);
},
modifiers: [BackgroundColor],
template(this) {
return html`
<div myAttribute="red">Background</div>
`;
}
})
export class HomeComponent extends LitElement {}
There is a way to define options static method as a typescript decorator
import { CustomAttribute, Modifier } from '@rhtml/custom-attributes';
@CustomAttribute({
selector: 'background'
})
export class BackgroundColor extends Attribute {}
@Modifier({
selector: 'background'
})
export class BackgroundColor extends Attribute {}
import { Modifier, Input } from '@rhtml/custom-attributes';
import { HostListener } from '@rhtml/decorators';
@Modifier({
selector: 'hover'
})
export class Hoverable extends Attribute {
@Input()
myProperty: string;
@HostListener('mouseenter')
enter(event: Event) {
console.log('Enter', event);
}
@HostListener('mouseleave')
leave(event: Event) {
console.log('Leave', event);
}
}
<div hover myProperty="123">Lorem ipsum dolor</div>
Sometimes we want our Modifier to be more extensible and we want to create complex logic like for example to listen for specific property changes inside of our modifier
import { Modifier, Input } from '@rhtml/custom-attributes';
import { HostListener } from '@rhtml/decorators';
@Modifier({
selector: 'hover'
})
export class Hoverable extends Attribute {
@Input({ observe: true })
myProperty: string;
OnUpdateAttribute(name: string, value: string) {
/* This will be triggered on every update of the attribute myProperty */
console.log(this.myProperty);
}
}
import { Attribute, CustomAttributeRegistry } from '@rhtml/custom-attributes';
export class Animation extends Attribute {
static options = {
selector: 'animated',
registry(this: HTMLElement) {
return new CustomAttributeRegistry(this);
},
observedAttributes: ['delay']
};
get delay() {
return this.element.getAttribute('delay');
}
OnInit() {
this.modify();
}
OnDestroy() {
this.element.classList.remove('animated', this.value);
this.element.style.animationDelay = null;
}
OnUpdate() {
this.modify();
}
OnUpdateAttribute() {
this.modify();
}
private modify() {
this.element.classList.add('animated', this.value);
this.element.style.animationDelay = this.delay;
}
}
import {
Attribute,
CustomAttributeRegistry,
Input,
Modifier
} from '@rhtml/custom-attributes';
import { Animations, AnimationsType } from './animate.css';
interface Styles {
animationDelay: string;
}
@Modifier({
selector: 'animated',
registry(this: HTMLElement) {
return new CustomAttributeRegistry(this);
}
})
export class Animation extends Attribute<Styles> {
@Input({ observe: true })
delay: string;
value: AnimationsType;
OnInit() {
this.pushStylesToParent();
this.modify();
}
OnDestroy() {
this.element.classList.remove('animated', this.value);
this.setStyles({ animationDelay: null })(this.element);
}
OnUpdate() {
this.modify();
}
OnUpdateAttribute() {
this.modify();
}
private modify() {
this.element.classList.add('animated', this.value);
this.setStyles({ animationDelay: this.delay })(this.element);
}
private pushStylesToParent() {
const style = document.createElement('style');
style.innerHTML = `${Animations}`;
const root = this.parent.shadowRoot ?? this.parent;
root.prepend(style);
}
}
By changing delay attribute because we use observe: true method OnUpdateAttribute will be triggered
<h2 animated="slideInLeft" delay="1s">
My Animated Element
</h2>
By extending MediaQueryAttribute we gain two more events to handle media matches OnEnterMediaQuery and OnExitMediaQuery
For example when we define attribute selector color we can use available media breakpoints xs, sm, md,lg,xl.
Extending MediaQueryAttribute will help you to track values from specific resolutions
import {
MediaQueryEvent,
ExitMediaQueryAttributes,
MediaQueryAttribute,
Modifier
} from '@rhtml/custom-attributes';
interface Styles {
color: string;
}
@Modifier({
selector: 'color'
})
export class Color extends MediaQueryAttribute<Styles> {
private prevValue: string;
OnInit() {
this.modify();
/* Executing media matcher init */
super.OnInit();
}
OnDestroy() {
this.clean();
/* Executing media matcher destroy */
super.OnDestroy();
}
OnUpdate() {
this.modify();
}
OnEnterMediaQuery([event, attribute]: MediaQueryEvent) {
console.log(event, attribute.value);
this.prevValue = this.value;
this.value = attribute.value ?? this.value;
this.modify();
}
OnExitMediaQuery([event, selector]: ExitMediaQueryAttributes) {
this.value = this.prevValue ?? this.originalValue;
this.modify();
}
clean() {
this.setStyles({ color: null })(this.element);
}
modify() {
this.setStyles({ color: this.value || null })(this.element);
}
}
<div color="red" color.xs="green" color.md="gray">
My text
</div>
| Breakpoint | Media Query |
|---|---|
| xs | screen and (max-width: 599px) |
| sm | screen and (min-width: 600px) and (max-width: 959px) |
| md | screen and (min-width: 960px) and (max-width: 1279px) |
| lg | screen and (min-width: 1280px) and (max-width: 1919px) |
| xl | screen and (min-width: 1920px) and (max-width: 5000px) |
| lt-sm | screen and (max-width: 599px) (use xs) |
| lt-md | screen and (max-width: 959px) |
| lt-lg | screen and (max-width: 1279px) |
| lt-xl | screen and (max-width: 1919px) |
| gt-xs | screen and (min-width: 600px) |
| gt-sm | screen and (min-width: 960px) |
| gt-md | screen and (min-width: 1280px) |
| gt-lg | screen and (min-width: 1920px) |
import { Attribute, Modifier } from '@rhtml/custom-attributes';
@Modifier({
selector: 'color',
observe: {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['my-attribute']
}
})
export class Color extends Attribute {
OnChange(records: MutationRecord[]) {
console.log(records);
const attributeValue = this.element.getAttribute('my-attribute');
console.log(attributeValue); // 'test'
}
}
<div color="red" my-attribute="test">
My element
</div>
If we change my-attribute value we will trigger OnChange which will give us mutation records
FAQs
Custom Attribute Registry
The npm package @rhtml/custom-attributes receives a total of 7 weekly downloads. As such, @rhtml/custom-attributes popularity was classified as not popular.
We found that @rhtml/custom-attributes demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.