The source code

    /**
     * @ignore
     *  custom event target for publish and subscribe
     * @author yiminghe@gmail.com
     */
    KISSY.add('event/custom/api-impl', function (S, api, Event, ObservableCustomEvent) {
        var trim = S.trim,
            _Utils = Event._Utils,
            splitAndRun = _Utils.splitAndRun,
            KS_BUBBLE_TARGETS = '__~ks_bubble_targets';

        return S.mix(api,
            /**
             * @class KISSY.Event.Target
             * @singleton
             * EventTarget provides the implementation for any object to publish, subscribe and fire to custom events,
             * and also allows other EventTargets to target the object with events sourced from the other object.
             *
             * EventTarget is designed to be used with S.augment to allow events to be listened to and fired by name.
             *
             * This makes it possible for implementing code to subscribe to an event that either has not been created yet,
             * or will not be created at all.
             */
            {

                /**
                 * Fire a custom event by name.
                 * The callback functions will be executed from the context specified when the event was created,
                 * and the {@link KISSY.Event.CustomEventObject} created will be mixed with eventData
                 * @param {String} type The type of the event
                 * @param {Object} [eventData] The data will be mixed with {@link KISSY.Event.CustomEventObject} created
                 * @return {*} If any listen returns false, then the returned value is false. else return the last listener's returned value
                 */
                fire: function (target, type, eventData) {
                    var self = target, ret = undefined;

                    eventData = eventData || {};

                    splitAndRun(type, function (type) {
                        var r2, customEvent,
                            typedGroups = _Utils.getTypedGroups(type),
                            _ks_groups = typedGroups[1];

                        type = typedGroups[0];

                        if (_ks_groups) {
                            _ks_groups = _Utils.getGroupsRe(_ks_groups);
                            eventData._ks_groups = _ks_groups;
                        }

                        customEvent = ObservableCustomEvent.getCustomEvent(self, type) ||
                            // in case no publish custom event but we need bubble
                            // because bubbles defaults to true!
                            new ObservableCustomEvent({
                                currentTarget: target,
                                type: type
                            });

                        r2 = customEvent.fire(eventData);

                        if (ret !== false) {
                            ret = r2;
                        }
                    });

                    return ret;
                },

                /**
                 * Creates a new custom event of the specified type
                 * @param {String} type The type of the event
                 * @param {Object} cfg Config params
                 * @param {Boolean} [cfg.bubbles=true] whether or not this event bubbles
                 * @param {Function} [cfg.defaultFn] this event's default action
                 * @chainable
                 */
                publish: function (target, type, cfg) {
                    var customEvent;

                    splitAndRun(type, function (t) {
                        customEvent = ObservableCustomEvent.getCustomEvent(target, t, 1);
                        S.mix(customEvent, cfg)
                    });

                    return target;
                },

                /**
                 * Registers another EventTarget as a bubble target.
                 * @param {KISSY.Event.Target} anotherTarget Another EventTarget instance to add
                 * @chainable
                 */
                addTarget: function (target, anotherTarget) {
                    var targets = api.getTargets(target);
                    if (!S.inArray(anotherTarget, targets)) {
                        targets.push(anotherTarget);
                    }
                    return target;
                },

                /**
                 * Removes a bubble target
                 * @param {KISSY.Event.Target} anotherTarget Another EventTarget instance to remove
                 * @chainable
                 */
                removeTarget: function (target, anotherTarget) {
                    var targets = api.getTargets(target),
                        index = S.indexOf(anotherTarget, targets);
                    if (index != -1) {
                        targets.splice(index, 1);
                    }
                    return target;
                },

                /**
                 * all targets where current target's events bubble to
                 * @private
                 * @return {Array}
                 */
                getTargets: function (target) {
                    target[KS_BUBBLE_TARGETS] = target[KS_BUBBLE_TARGETS] || [];
                    return target[KS_BUBBLE_TARGETS];
                },

                /**
                 * Subscribe a callback function to a custom event fired by this object or from an object that bubbles its events to this object.
                 * @method
                 * @param {String} type The name of the event
                 * @param {Function} fn The callback to execute in response to the event
                 * @param {Object} [context] this object in callback
                 * @chainable
                 */
                on: function (target, type, fn, context) {
                    type = trim(type);
                    _Utils.batchForType(function (type, fn, context) {
                        var cfg = _Utils.normalizeParam(type, fn, context),
                            customEvent;
                        type = cfg.type;
                        customEvent = ObservableCustomEvent.getCustomEvent(target, type, 1);
                        if (customEvent) {
                            customEvent.on(cfg);
                        }
                    }, 0, type, fn, context);

                    return target; // chain
                },

                /**
                 * Detach one or more listeners from the specified event
                 * @method
                 * @param {String} type The name of the event
                 * @param {Function} [fn] The subscribed function to un-subscribe. if not supplied, all observers will be removed.
                 * @param {Object} [context] The custom object passed to subscribe.
                 * @chainable
                 */
                detach: function (target, type, fn, context) {
                    type = trim(type);
                    _Utils.batchForType(function (type, fn, context) {
                        var cfg = _Utils.normalizeParam(type, fn, context),
                            customEvents,
                            customEvent;
                        type = cfg.type;
                        if (type) {
                            customEvent = ObservableCustomEvent.getCustomEvent(target, type, 1);
                            if (customEvent) {
                                customEvent.detach(cfg);
                            }
                        } else {
                            customEvents = ObservableCustomEvent.getCustomEvents(target);
                            S.each(customEvents, function (customEvent) {
                                customEvent.detach(cfg);
                            });
                        }
                    }, 0, type, fn, context);

                    return target; // chain
                }
            });
    }, {
        requires: ['./api', 'event/base', './observable']
    });
    /*
     yiminghe: 2012-10-24
     - implement defaultFn for custom event

     yiminghe: 2011-10-17
     - implement bubble for custom event
     */